> ## Documentation Index
> Fetch the complete documentation index at: https://astronomer.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# External Secrets Operator security manifests reference

This document collects the reference Kubernetes manifests for External Secrets Operator (ESO) security in Astro Private Cloud (APC): the backend credentials secret, the secret store options for the shared-identity modes, the Mode 2 hardened cluster RBAC, and the Mode 3 per-namespace RBAC.

<Note>
  The backend-credentials secret and the `ClusterSecretStore` (Mode 1) apply to APC 2.0 and later. The ESO controller ServiceAccount, the Mode 2 cluster RBAC, the Mode 3 per-namespace RBAC, and the leader-election RBAC require APC 2.1 or later.
</Note>

Use it alongside [External Secrets Operator security](/docs/astro-private-cloud/v-2-x/external-secrets-operator-security), which explains the modes and how to choose, and [Configure External Secrets Operator security](/docs/astro-private-cloud/v-2-x/configure-external-secrets-operator-security), the step-by-step setup that applies these manifests.

All examples use AWS Secrets Manager. GCP Secret Manager and Hashicorp Vault (Kubernetes auth) use the same object shapes with a different `provider` block; for the Vault provider block and Vault-side setup, see [Configure Hashicorp Vault for data plane failover](/docs/astro-private-cloud/v-2-x/configure-vault-data-plane-failover). The manifests use two placeholders: `<release-name>` is the Helm release name you install the Astronomer chart with (for example `astronomer`), and `<pool-namespace>` is one of your pre-created namespace pool namespaces.

<Note>
  These manifests assume the platform is installed in the `astronomer` namespace. If you installed it into a different namespace, replace `astronomer` in the manifests and the chart values accordingly.
</Note>

## Install the ESO CRDs yourself

By default the APC chart installs the External Secrets Operator CRDs (`external-secrets.crd.create: true`). If you set `external-secrets.crd.create: false` — for example, when a separate infrastructure team owns CRD installation — install the CRDs yourself before you install or upgrade the platform.

The CRDs are published by the external-secrets project as a single [CRD bundle](https://raw.githubusercontent.com/external-secrets/external-secrets/refs/tags/v2.7.0/deploy/crds/bundle.yaml). Apply the bundle whose tag matches the ESO version the APC chart ships, currently `v2.7.0`:

```bash theme={null}
kubectl apply -f https://raw.githubusercontent.com/external-secrets/external-secrets/refs/tags/v2.7.0/deploy/crds/bundle.yaml --server-side
```

Match the bundle tag to the ESO version your APC chart ships — applying CRDs from a different version can break ESO reconciliation. Apply the bundle before the platform install so the CRDs exist when ESO starts.

## Backend credentials secret (Modes 1 and 2)

Create the backend credentials in the platform (`astronomer`) namespace. Skip this if you authenticate with workload identity instead of a credential pair.

```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
  name: secrets-backend-credentials
  namespace: astronomer
type: Opaque
data:
  access-key: <base64-aws-access-key-id>
  secret-access-key: <base64-aws-secret-access-key>
```

## ESO controller ServiceAccount (Modes 2 and 3)

In Modes 2 and 3, you create the ESO controller ServiceAccount yourself and disable the chart's ServiceAccount creation, so the ServiceAccount and its RBAC exist before ESO starts. In [Configure External Secrets Operator security](/docs/astro-private-cloud/v-2-x/configure-external-secrets-operator-security), you set `external-secrets.serviceAccount.create: false` and `external-secrets.serviceAccount.name` to this ServiceAccount. Create it in the `astronomer` namespace:

```yaml theme={null}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: <release-name>-external-secrets
  namespace: astronomer
```

For workload identity, annotate this ServiceAccount for your provider (`eks.amazonaws.com/role-arn` for AWS, `iam.gke.io/gcp-service-account` for GCP).

## Secret store manifests (Modes 1 and 2)

### Option A — cluster-scoped ClusterSecretStore (Modes 1 and 2)

```yaml theme={null}
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: astronomer-secret-store
spec:
  provider:
    aws:
      secretsManager:
        forceDeleteWithoutRecovery: true
      service: SecretsManager
      region: us-east-2
      auth:
        secretRef:
          accessKeyIDSecretRef:
            key: access-key
            name: secrets-backend-credentials
            namespace: astronomer
          secretAccessKeySecretRef:
            key: secret-access-key
            name: secrets-backend-credentials
            namespace: astronomer
```

The manifest above authenticates with static credentials. To use workload identity instead, reference the ESO controller ServiceAccount from the store's `auth` block. AWS IRSA is shown. GCP and Vault use their provider's equivalent auth:

```yaml theme={null}
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: astronomer-secret-store
spec:
  provider:
    aws:
      secretsManager:
        forceDeleteWithoutRecovery: true
      service: SecretsManager
      region: us-east-2
      auth:
        jwt:
          serviceAccountRef:
            name: <release-name>-external-secrets
            namespace: astronomer
```

The ESO controller ServiceAccount (`<release-name>-external-secrets`) is created during ESO setup — by the chart in Mode 1, or by you in Modes 2 and 3 (see [ESO controller ServiceAccount](#eso-controller-serviceaccount-modes-2-and-3)). For workload identity, annotate it for your provider (`eks.amazonaws.com/role-arn` for AWS, `iam.gke.io/gcp-service-account` for GCP) — in Mode 1 through the chart's `external-secrets.serviceAccount.annotations` value, or in Modes 2 and 3 directly on the ServiceAccount you create.

### Option B — namespaced SecretStore synced by the platform (Mode 2 only)

Both the credentials secret and the `SecretStore` carry the `astronomer.io/commander-sync` annotation, which tells the platform to sync them into every Deployment namespace.

<Note>
  **Static credentials only**

  This synced-`SecretStore` option supports static credentials only. The platform copies the credentials Secret and the `SecretStore` into each Deployment namespace, so it can't carry per-namespace workload-identity references. To use workload identity (AWS IRSA, GKE Workload Identity, or Vault Kubernetes auth) in Mode 2, use the cluster-scoped `ClusterSecretStore` (Option A) instead.
</Note>

```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
  name: secrets-backend-credentials
  namespace: astronomer
  annotations:
    astronomer.io/commander-sync: platform-release=<platform-release-name>
type: Opaque
data:
  access-key: <base64-aws-access-key-id>
  secret-access-key: <base64-aws-secret-access-key>
---
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: astronomer-secret-store
  namespace: astronomer
  annotations:
    astronomer.io/commander-sync: platform-release=<platform-release-name>
spec:
  provider:
    aws:
      secretsManager:
        forceDeleteWithoutRecovery: true
      service: SecretsManager
      region: us-east-2
      auth:
        secretRef:
          accessKeyIDSecretRef:
            key: access-key
            name: secrets-backend-credentials
          secretAccessKeySecretRef:
            key: secret-access-key
            name: secrets-backend-credentials
```

## Force-delete secrets in AWS Secrets Manager

AWS Secrets Manager soft-deletes secrets by default, keeping them recoverable for a 30-day window during which the secret name stays reserved. If the platform then tries to create a secret with the same name, the create conflicts with the still-recoverable secret. This can happen when you use custom release names and reuse a release name for a new Deployment after deleting the previous one.

To make ESO hard-delete secrets instead, set `forceDeleteWithoutRecovery` on the AWS provider in your `SecretStore` or `ClusterSecretStore`:

```yaml theme={null}
spec:
  provider:
    aws:
      secretsManager:
        forceDeleteWithoutRecovery: true
```

With this set, deleted secrets are removed immediately with no recovery window, so a new Deployment can reuse the name right away. If you use custom release names, set this, because reusing a release name after deleting a Deployment is exactly when the name collision occurs. The trade-off is that deleted secrets can't be restored, since there is no recovery window.

This option applies to AWS Secrets Manager only. Add it to whichever store your mode uses — the shared `ClusterSecretStore` or `SecretStore` in Modes 1 and 2, or the per-namespace `SecretStore` in Mode 3. The ESO GCP Secret Manager and Azure Key Vault providers don't expose an equivalent option: GCP Secret Manager deletes secrets immediately, so the conflict doesn't arise, and Azure Key Vault's soft-delete can't be overridden from the store — on Azure you purge a deleted secret through Azure itself.

## Mode 2: hardened ESO cluster RBAC

In Mode 2, you disable the ESO sub-chart's default RBAC (`external-secrets.rbac.create: false`), then apply the `ClusterRole` and `ClusterRoleBinding` below. They grant ESO the same reconcile rules as the Mode 3 per-namespace Role, but cluster-scoped, and pin ServiceAccount token creation with `resourceNames` to ESO's own ServiceAccount in the `astronomer` namespace. That pinned token-create serves the same purpose as in Mode 3: ESO assumes the shared identity by minting its own ServiceAccount's token (the `serviceAccountRef` path) — required for Vault, and used by AWS or GCP workload identity through `serviceAccountRef`. It goes unused if you authenticate with static credentials.

<Note>
  In Mode 2 you apply this `ClusterRole` and `ClusterRoleBinding` yourself. The chart doesn't ship them, since `external-secrets.rbac.create: false`. The rules mirror the external-secrets Role, expressed as a cluster-scoped role with token creation pinned to the ESO ServiceAccount.
</Note>

The `clustersecretstores` rules let ESO read the cluster-scoped store; include them when Mode 2 uses a `ClusterSecretStore`. If you use only the synced `SecretStore` (Option B), you can omit them.

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: <release-name>-external-secrets
rules:
  - apiGroups: ["external-secrets.io"]
    resources: ["secretstores", "externalsecrets", "pushsecrets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets", "externalsecrets/status", "secretstores", "secretstores/status", "pushsecrets", "pushsecrets/status"]
    verbs: ["get", "update", "patch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets"]
    verbs: ["create", "update", "delete"]
  - apiGroups: ["external-secrets.io"]
    resources: ["pushsecrets"]
    verbs: ["create", "update", "delete"]
  - apiGroups: ["generators.external-secrets.io"]
    resources: ["generatorstates"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
  # ClusterSecretStore access (Mode 2 with a ClusterSecretStore)
  - apiGroups: ["external-secrets.io"]
    resources: ["clustersecretstores"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["clustersecretstores/status"]
    verbs: ["get", "update", "patch"]
  - apiGroups: [""]
    resources: ["serviceaccounts", "namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "delete", "patch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
  # Token creation pinned to ESO's own ServiceAccount (Mode 2 hardening)
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create"]
    resourceNames: ["<release-name>-external-secrets"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: <release-name>-external-secrets
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: <release-name>-external-secrets
subjects:
  - kind: ServiceAccount
    name: <release-name>-external-secrets
    namespace: astronomer
```

### config-syncer RBAC (Option B synced SecretStore)

The platform's config-syncer component performs the Option B sync: it copies the `commander-sync`-annotated credentials Secret and `SecretStore` from the `astronomer` namespace into each Deployment namespace, so it needs write access to `secrets` and `secretstores` in every target namespace. Mode 3 doesn't use this component — there, each namespace has its own pre-provisioned `SecretStore` and nothing is synced.

The chart creates this RBAC automatically in a standard install. Provision the Role and RoleBinding below yourself only if you run with cluster-scoped roles disabled, so config-syncer isn't otherwise granted namespace access. Create them in each Deployment namespace, binding the `<release-name>-config-syncer` ServiceAccount in `astronomer`.

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <release-name>-config-syncer
  namespace: <deployment-namespace>
  labels:
    app.kubernetes.io/name: config-syncer
    app.kubernetes.io/instance: <release-name>
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "get", "list", "patch", "update"]
  - apiGroups: ["external-secrets.io"]
    resources: ["secretstores"]
    verbs: ["create", "get", "list", "patch", "update"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <release-name>-config-syncer
  namespace: <deployment-namespace>
  labels:
    app.kubernetes.io/name: config-syncer
    app.kubernetes.io/instance: <release-name>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: <release-name>-config-syncer
subjects:
  - kind: ServiceAccount
    name: <release-name>-config-syncer
    namespace: astronomer
```

## Mode 3: per-namespace RBAC

In Mode 3 (customer-managed isolated identity), each Deployment gets its own backend identity, and you pre-provision per-namespace RBAC for every platform component that operates in the pool namespaces. This document covers the ESO and Commander RBAC. The remaining platform components — kube-state-metrics, the Houston DB bootstrapper hook, Prometheus, and NGINX — are covered in [Component RBAC for restricted mode](/docs/astro-private-cloud/v-2-x/namespace-pools#component-rbac-for-restricted-mode). Create these objects in the `astronomer` namespace and in each pool namespace, in addition to the standard [namespace pools](/docs/astro-private-cloud/v-2-x/namespace-pools) setup, and replicate them with matching namespace names on every cluster a Deployment can fail over to.

Each `RoleBinding` names the component's ServiceAccount in the `astronomer` namespace as a cross-namespace subject. The chart installs those ServiceAccounts release-prefixed: `<release-name>-external-secrets` and `<release-name>-commander`.

Rather than hand-writing every object, you can generate the full set with the provided script. See [Generate the platform RBAC](#generate-the-platform-rbac-mode-3). The manifests below are the reference for what that script produces.

### Per-namespace ServiceAccount and SecretStore (isolated identity)

For isolated backend identity, pre-provision a ServiceAccount and a namespaced `SecretStore` that authenticates as it through workload identity in each pool namespace. In namespace pools you provision this chain when the namespaces are created, before any Deployment exists, so name the ServiceAccount after the pre-created pool namespace (shown here as `<pool-namespace>-eso`) rather than a Deployment ID, which isn't yet known. Each pool namespace hosts one Deployment at a time, so a per-namespace identity is effectively per-Deployment. Annotate the ServiceAccount for your provider. Omit the annotation for Vault Kubernetes auth.

```yaml theme={null}
# AWS IRSA
apiVersion: v1
kind: ServiceAccount
metadata:
  name: <pool-namespace>-eso
  namespace: <pool-namespace>
  annotations:
    eks.amazonaws.com/role-arn: <per-namespace-iam-role-arn>
---
# GKE Workload Identity (alternative)
# metadata.annotations:
#   iam.gke.io/gcp-service-account: <per-namespace-gcp-service-account>
```

Name the `SecretStore` the same in every pool namespace — the value of `global.dataPlaneFailover.externalSecretManagerName` (shown here as `astronomer-secret-store`). The name must be identical across namespaces. The `provider` block and identity can differ per namespace.

```yaml theme={null}
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: astronomer-secret-store
  namespace: <pool-namespace>
spec:
  provider:
    aws:
      secretsManager:
        forceDeleteWithoutRecovery: true
      service: SecretsManager
      region: us-east-2
      auth:
        jwt:
          serviceAccountRef:
            name: <pool-namespace>-eso
```

### ESO Role and RoleBinding

Grants the ESO controller ServiceAccount the reconcile verbs it needs inside the namespace. Create it in the `astronomer` namespace and in each pool namespace.

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <release-name>-external-secrets
  namespace: <pool-namespace>
rules:
  - apiGroups: ["external-secrets.io"]
    resources: ["secretstores", "externalsecrets", "pushsecrets"]
    verbs: ["get", "list", "watch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets", "externalsecrets/status", "secretstores", "secretstores/status", "pushsecrets", "pushsecrets/status"]
    verbs: ["get", "update", "patch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets"]
    verbs: ["create", "update", "delete"]
  - apiGroups: ["external-secrets.io"]
    resources: ["pushsecrets"]
    verbs: ["create", "update", "delete"]
  - apiGroups: ["generators.external-secrets.io"]
    resources: ["generatorstates"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete", "deletecollection"]
  - apiGroups: [""]
    resources: ["serviceaccounts", "namespaces"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["get", "list", "watch", "create", "update", "delete", "patch"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <release-name>-external-secrets
  namespace: <pool-namespace>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: <release-name>-external-secrets
subjects:
  - kind: ServiceAccount
    name: <release-name>-external-secrets
    namespace: astronomer
```

### Token-creation Role and RoleBinding

Required in Mode 3, for any backend: the per-namespace `SecretStore` authenticates as the per-namespace ServiceAccount (`auth.jwt.serviceAccountRef`), so ESO must mint that ServiceAccount's token to assume its identity — whether the backend is AWS (IRSA), GCP (Workload Identity), or Vault (Kubernetes auth). More broadly, ESO needs ServiceAccount-token creation whenever a store authenticates through a `serviceAccountRef`: always for Vault, and for AWS or GCP only when you authenticate with a ServiceAccount token (workload identity) rather than static credentials or the ESO controller's own IRSA or GKE Workload Identity.

The `RoleBinding` subject is the ESO controller ServiceAccount (`<release-name>-external-secrets` in `astronomer`), because the controller is the process that calls the Kubernetes `TokenRequest` API. The rule's `resourceNames` pins which ServiceAccount the controller may mint a token for — the per-namespace SA `<pool-namespace>-eso` — so it can assume that one identity and nothing else. Create the Role and RoleBinding alongside the ESO Role in each pool namespace.

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <release-name>-external-secrets-token
  namespace: <pool-namespace>
rules:
  - apiGroups: [""]
    resources: ["serviceaccounts/token"]
    verbs: ["create"]
    resourceNames: ["<pool-namespace>-eso"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <release-name>-external-secrets-token
  namespace: <pool-namespace>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: <release-name>-external-secrets-token
subjects:
  - kind: ServiceAccount
    name: <release-name>-external-secrets
    namespace: astronomer
```

### Commander Role and RoleBinding

Create in the `astronomer` namespace and in each pool namespace, binding the `<release-name>-commander` ServiceAccount.

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <release-name>-commander
  namespace: <pool-namespace>
  labels:
    tier: houston
    release: <release-name>
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["secrets"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["namespaces"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["serviceaccounts"]
    verbs: ["create", "delete", "get", "patch", "list", "watch"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["roles"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumeclaims"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "update", "watch", "patch"]
  - apiGroups: [""]
    resources: ["pods"]
    verbs: ["create", "delete", "deletecollection", "get", "list", "watch", "patch"]
  - apiGroups: [""]
    resources: ["pods/exec"]
    verbs: ["create", "get"]
  - apiGroups: [""]
    resources: ["pods/log"]
    verbs: ["get", "list"]
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["create", "delete", "get", "list", "update", "watch"]
  - apiGroups: [""]
    resources: ["limitranges"]
    verbs: ["create", "delete", "get", "list", "watch", "patch"]
  - apiGroups: [""]
    resources: ["nodes"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["nodes/proxy"]
    verbs: ["get", "list", "watch"]
  - apiGroups: [""]
    resources: ["persistentvolumes"]
    verbs: ["create", "delete", "get", "list", "watch", "patch"]
  - apiGroups: [""]
    resources: ["replicationcontrollers"]
    verbs: ["list", "watch"]
  - apiGroups: [""]
    resources: ["resourcequotas"]
    verbs: ["create", "delete", "get", "list", "patch", "watch"]
  - apiGroups: [""]
    resources: ["services"]
    verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
  - apiGroups: ["apps"]
    resources: ["statefulsets"]
    verbs: ["create", "delete", "get", "list", "patch", "watch"]
  - apiGroups: ["apps"]
    resources: ["daemonsets"]
    verbs: ["create", "delete", "get", "patch"]
  - apiGroups: ["apps"]
    resources: ["deployments"]
    verbs: ["create", "delete", "get", "list", "patch", "update", "watch"]
  - apiGroups: ["autoscaling"]
    resources: ["horizontalpodautoscalers"]
    verbs: ["list", "watch"]
  - apiGroups: ["batch"]
    resources: ["jobs"]
    verbs: ["list", "watch", "create", "delete", "get", "deletecollection"]
  - apiGroups: ["batch"]
    resources: ["cronjobs"]
    verbs: ["create", "delete", "get", "list", "patch", "watch", "deletecollection"]
  - apiGroups: [""]
    resources: ["events"]
    verbs: ["create", "delete", "patch", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses"]
    verbs: ["get", "create", "delete", "patch", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["ingresses/status"]
    verbs: ["update", "list", "watch"]
  - apiGroups: ["networking.k8s.io"]
    resources: ["networkpolicies"]
    verbs: ["create", "delete", "get", "patch", "list", "watch"]
  - apiGroups: ["rbac.authorization.k8s.io"]
    resources: ["rolebindings"]
    verbs: ["create", "delete", "get", "patch", "list", "watch"]
  - apiGroups: ["authentication.k8s.io"]
    resources: ["tokenreviews"]
    verbs: ["create", "delete", "list", "watch"]
  - apiGroups: ["authorization.k8s.io"]
    resources: ["subjectaccessreviews"]
    verbs: ["create", "delete", "list", "watch"]
  - apiGroups: ["policy"]
    resources: ["poddisruptionbudgets"]
    verbs: ["create", "delete", "get", "list", "patch", "watch"]
  - apiGroups: ["external-secrets.io"]
    resources: ["externalsecrets", "pushsecrets", "secretstores"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
  - apiGroups: ["external-secrets.io"]
    resources: ["generatorstates"]
    verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <release-name>-commander
  namespace: <pool-namespace>
  labels:
    tier: houston
    release: <release-name>
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: <release-name>-commander
subjects:
  - kind: ServiceAccount
    name: <release-name>-commander
    namespace: astronomer
```

## Generate the platform RBAC (Mode 3)

Mode 3 needs RBAC for every platform component that runs in the pool namespaces, not just ESO. Rather than hand-writing each object, generate the complete set with the provided script. It emits a single bundle that covers both the ESO-specific RBAC in this document and the namespace pools platform-component RBAC in [Component RBAC for restricted mode](/docs/astro-private-cloud/v-2-x/namespace-pools#component-rbac-for-restricted-mode):

* *ESO* — the reconcile `Role` and `RoleBinding` and the `resourceNames`-pinned token-creation `Role` and `RoleBinding`.
* *Commander* — the per-namespace `Role` and `RoleBinding`.
* *kube-state-metrics* — the per-namespace `Role` and `RoleBinding`.
* *Houston DB bootstrapper hook* — a `Role` and `RoleBinding` created in the `astronomer` namespace only.
* *Prometheus* — a cluster-scoped `ClusterRole` and `ClusterRoleBinding` for scrape access.
* *NGINX* — a cluster-scoped `ClusterRole` and `ClusterRoleBinding`, plus its namespaced config `Role` and `RoleBinding`.

Generate and apply the full set across the platform and pool namespaces:

```bash theme={null}
python bin/generate-namespace-pools-rbac.py \
  --release-name <release-name> \
  --release-namespace astronomer \
  --namespaces astronomer,<pool-namespace-1>,<pool-namespace-2> | kubectl apply -f -
```

The options are:

* `--release-name` — the Helm release name of the platform. The script uses it to prefix every generated ServiceAccount, `Role`, and `ClusterRole` (for example `<release-name>-commander` and `<release-name>-external-secrets`).
* `--release-namespace` — the platform namespace (`astronomer`), where the component ServiceAccounts live. Every generated `RoleBinding` names them here as a cross-namespace subject.
* `--namespaces` — a comma-separated list of the namespaces to emit per-namespace RBAC for. Include the platform namespace and every pool namespace.

To generate only the limited-privilege installer identity used to run the platform install, rather than the full component set, pass `--installer-user` instead:

```bash theme={null}
python bin/generate-namespace-pools-rbac.py \
  --installer-user <installer> \
  --namespaces astronomer | kubectl apply -f -
```

The per-namespace ESO ServiceAccount and `SecretStore` carry provider-specific identity, so the script doesn't generate them — create those as shown in [Per-namespace ServiceAccount and SecretStore (isolated identity)](#per-namespace-serviceaccount-and-secretstore-isolated-identity).

## Leader-election RBAC (optional ESO high availability)

To run the ESO controller with more than one replica for resiliency, enable leader election so only one replica reconciles secrets at a time. See [Run ESO with multiple replicas](/docs/astro-private-cloud/v-2-x/configure-external-secrets-operator-security#run-eso-with-multiple-replicas). In Modes 2 and 3, where you manage ESO's RBAC, create the leader-election Role and RoleBinding in the `astronomer` namespace, binding the ESO ServiceAccount:

```yaml theme={null}
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: <release-name>-external-secrets-leaderelection
  namespace: astronomer
rules:
  - apiGroups: [""]
    resources: ["configmaps"]
    resourceNames: ["external-secrets-controller"]
    verbs: ["get", "update", "patch"]
  - apiGroups: [""]
    resources: ["configmaps"]
    verbs: ["create"]
  - apiGroups: ["coordination.k8s.io"]
    resources: ["leases"]
    verbs: ["get", "create", "update", "patch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: <release-name>-external-secrets-leaderelection
  namespace: astronomer
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: <release-name>-external-secrets-leaderelection
subjects:
  - kind: ServiceAccount
    name: <release-name>-external-secrets
    namespace: astronomer
```

## Related documentation

* [External Secrets Operator security](/docs/astro-private-cloud/v-2-x/external-secrets-operator-security)
* [Configure External Secrets Operator security](/docs/astro-private-cloud/v-2-x/configure-external-secrets-operator-security)
* [Component RBAC for restricted mode](/docs/astro-private-cloud/v-2-x/namespace-pools#component-rbac-for-restricted-mode)
