## Kubernetes CRD Provider

External Secrets Operator can read data from arbitrary Kubernetes Custom Resources (CRDs) in the local cluster or in a remote cluster. It uses the same connection model as the [Kubernetes provider](kubernetes.md); remote-cluster access is covered in the [Remote cluster connection](#remote-cluster-connection) section below.

This provider is useful when secrets or secret-like values are already managed inside CRDs (i.e. by an operator) and you want to project them into regular Kubernetes Secrets.

The CRD provider is read-only.

### How it works

1. The provider connects to a Kubernetes API using the same connection model as the Kubernetes provider. To read the local cluster, set `auth.serviceAccount` and point `server.caProvider` at the in-cluster CA (published in every namespace as the `kube-root-ca.crt` ConfigMap); `server.url` can be omitted, as it defaults to the in-cluster API. The CA is required even in-cluster: the API server certificate is not signed by the system roots, so without it the TLS handshake fails. To read a remote cluster, set `server` plus `auth` (`serviceAccount`, `token`, or `cert`) or `authRef` (a kubeconfig Secret).
2. It resolves the configured resource (`group`/`version`/`kind`) via Kubernetes discovery.
3. It reads objects using the dynamic Kubernetes client.
4. It applies optional whitelist rules before returning values.

### Remote cluster connection

Set `server` plus `auth` (or `authRef`) to connect to a remote Kubernetes API directly, exactly like the Kubernetes provider. The identity in `auth`/`authRef` is the identity the provider reads CRDs as; there is no separate impersonation step.

Example (`ClusterSecretStore`) connecting to a remote cluster:

```yaml
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: crd-remote
spec:
  provider:
    crd:
      server:
        url: https://remote-api.example.com
        caProvider:
          type: ConfigMap
          name: remote-cluster-ca
          namespace: external-secrets
          key: ca.crt
      auth:
        serviceAccount:
          name: eso-remote-connector
          namespace: external-secrets
      resource:
        group: example.io
        version: v1alpha1
        kind: Widget
```

Notes:

- `auth` accepts `serviceAccount`, `token`, or `cert`, the same options as the Kubernetes provider.
- `authRef` can be used instead of `auth` when the connection details (a kubeconfig) come from a Secret. In that case `server` is optional because the kubeconfig already carries the API address.

### SecretStore / ClusterSecretStore configuration

```yaml
apiVersion: external-secrets.io/v1
kind: SecretStore
metadata:
  name: crd-store
spec:
  provider:
    crd:
      # In-cluster read: reference the cluster CA so TLS to the API server
      # verifies. kube-root-ca.crt exists in every namespace. For a SecretStore
      # caProvider.namespace must be omitted; server.url defaults in-cluster.
      server:
        caProvider:
          type: ConfigMap
          name: kube-root-ca.crt
          key: ca.crt
      auth:
        serviceAccount:
          name: crd-reader
      resource:
        group: example.io
        version: v1alpha1
        kind: Widget
      # optional. If not empty, at least one rule must match the request
      whitelist:
        rules:
          # Name-only rule: allows objects whose name matches this regexp
          - name: "^app-.*$"

          # Name + properties rule: both must match
          - name: "^team-.*$"
            properties:
              - "^spec\\.password$"
              - "^spec\\.username$"

          # ClusterSecretStore-only rule: allows objects from matching namespaces
          - namespace: "^(prod|staging)$"

          # Properties-only rule: allows a request when the requested property matches
          - properties:
              - "^spec\\.password$"
```

#### Resource fields

- `auth.serviceAccount`: ServiceAccount used for API access. For an in-cluster read, pair it with `server.caProvider` pointing at the `kube-root-ca.crt` ConfigMap so TLS to the API server verifies; `server.url` can be omitted (it defaults to the in-cluster API).
- `server` + `auth`/`authRef`: Kubernetes API connection and authentication. A remote cluster needs the full `server` block; the local cluster needs only `server.caProvider` (the URL defaults in-cluster).
- Setting `server.url` requires `auth` or `authRef`; a store that sets `server.url` without credentials is rejected at admission.
- `resource.group`: API group of the resource (empty for core API resources).
- `resource.version`: API version of the resource.
- `resource.kind`: Kind of the resource.

### Whitelist rules

`whitelist.rules` is an array of allow rules.

If `whitelist.rules` is empty or omitted, requests are allowed.
If `whitelist.rules` is not empty, at least one rule must match.

Each rule can define:

- `name` (regexp, optional): matched against requested object name.
- `namespace` (regexp, optional): matched against requested object namespace for `ClusterSecretStore`.
- `properties` (array of regexps, optional): matched against requested property keys.

Rule behavior:

1. `name` only: request is allowed when object name matches.
2. `namespace` only: request is allowed when object namespace matches (`ClusterSecretStore` only).
3. `properties` only: request is allowed when requested property keys match.
4. any combination of `name`, `namespace`, and `properties`: all configured fields in the same rule must match.

A rule with none of `name`, `namespace`, or `properties` is invalid.

#### Notes about `properties` matching

- For `data[].remoteRef.property`, the requested key is that property path (for example `spec.password`).
- For requests without a specific property (for example `GetAllSecrets`), only rules that do not require `properties` can match.
- `namespace` matching is only enforced for `ClusterSecretStore`; `SecretStore` ignores this field.

### Property Expressions (GJSON)

`remoteRef.property` is evaluated using [GJSON path syntax](https://github.com/tidwall/gjson/blob/master/SYNTAX.md), the same syntax used by the Kubernetes provider and the rest of ESO, so the property dialect is consistent across providers.

Examples:

- `spec.user`
- `spec.password`
- `status.0.key`
- `status.#(key=="rotationDate").val`

If the expression resolves to no value, the provider returns `property not found`.

### Return values

The value returned for a reference depends on how it is requested:

- `remoteRef.property` set: the value at that GJSON path. A string leaf is returned unquoted (`spec.password` -> `hunter2`); objects, arrays, numbers, and booleans are returned as their raw JSON (`spec.replicas` -> `3`).
- `remoteRef.property` omitted: the entire object is returned as JSON.
- Map extraction (`dataFrom`, or a `remoteRef` consumed as a map): the object's top-level keys become secret keys. String values are unwrapped; non-string values keep their raw JSON.
- `dataFrom.find`: each matching object is returned as JSON, keyed by object name (`SecretStore`) or `namespace/objectName` (`ClusterSecretStore`). Use `conversionStrategy` on the `find` block to sanitize keys that are not valid Secret data keys.

A `property` that resolves to no value returns `property not found`; a missing object is reported as "secret not found".

### ExternalSecret examples

#### Fetch a single property

```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: crd-store
    kind: SecretStore
  target:
    name: app-credentials
  data:
    - secretKey: password
      remoteRef:
        key: app-backend
        property: spec.password
    - secretKey: username
      remoteRef:
        key: app-backend
        property: spec.username
```

#### ClusterSecretStore with namespace whitelist

Use a namespace-qualified key (`namespace/objectName`) for namespaced resources when referencing a `ClusterSecretStore`.

```yaml
apiVersion: external-secrets.io/v1
kind: ClusterSecretStore
metadata:
  name: crd-cluster-store
spec:
  provider:
    crd:
      # ClusterSecretStore reading the local cluster: caProvider.namespace is
      # required here (unlike SecretStore). kube-root-ca.crt exists in every
      # namespace, so any namespace the controller can read works.
      server:
        caProvider:
          type: ConfigMap
          name: kube-root-ca.crt
          namespace: external-secrets
          key: ca.crt
      auth:
        serviceAccount:
          name: crd-reader
          namespace: external-secrets
      resource:
        group: example.io
        version: v1alpha1
        kind: Widget
      whitelist:
        rules:
          - namespace: "^(team-a|team-b)$"
            name: "^app-.*$"
            properties:
              - "^spec\\.password$"
---
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: team-a-widget
  namespace: app
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: crd-cluster-store
    kind: ClusterSecretStore
  target:
    name: team-a-widget
  data:
    - secretKey: password
      remoteRef:
        key: team-a/app-backend
        property: spec.password
```

#### Fetch all matching CRD objects

```yaml
apiVersion: external-secrets.io/v1
kind: ExternalSecret
metadata:
  name: app-widgets
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: crd-store
    kind: SecretStore
  target:
    name: app-widgets
  dataFrom:
    - find:
        name:
          regexp: "^app-.*$"
```

`find.name.regexp` filters the listed objects by name.
Whitelist rules are applied in addition to this filter.

### RBAC

The configured ServiceAccount must be allowed to read the target resource.
At minimum, grant `get` on the selected resource. The `list` verb is only required when an `ExternalSecret` uses `dataFrom.find` (which calls `GetAllSecrets()` internally); store bootstrap only checks `get`.

The right scope depends on which kind of store you are using:

- **`SecretStore` (namespaced)**: a namespace-scoped `Role` + `RoleBinding` is enough; the controller only reads from the store's own namespace.
- **`ClusterSecretStore`**: the controller may read across namespaces. `dataFrom.find` lists the target resource across all namespaces, and `remoteRef.key` uses the `namespace/objectName` form, so a `ClusterRole` + `ClusterRoleBinding` is required.

#### SecretStore (namespace-scoped) example

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: crd-reader
  namespace: default
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: crd-reader
  namespace: default
rules:
  - apiGroups: ["example.io"]
    resources: ["widgets"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: crd-reader
  namespace: default
subjects:
  - kind: ServiceAccount
    name: crd-reader
    namespace: default
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: Role
  name: crd-reader
```

#### ClusterSecretStore example

```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: crd-reader
  namespace: external-secrets
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: crd-reader
rules:
  - apiGroups: ["example.io"]
    resources: ["widgets"]
    verbs: ["get", "list"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: crd-reader
subjects:
  - kind: ServiceAccount
    name: crd-reader
    namespace: external-secrets
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: crd-reader
```

### ClusterSecretStore note

When using `ClusterSecretStore`:

- for namespaced resources, `remoteRef.key` must be `namespace/objectName`.
- `whitelist.rules[].namespace` can be used to constrain which namespaces are readable.
- with `auth.serviceAccount`, the `namespace` field is optional. When omitted, the ServiceAccount is resolved in the consuming `ExternalSecret`'s own namespace (referent authentication), so a single store can serve many namespaces, each authenticating as its local ServiceAccount. Set `auth.serviceAccount.namespace` to pin one fixed namespace instead.

#### Referent authentication and RBAC

With referent authentication (no `auth.serviceAccount.namespace`), the named ServiceAccount must exist in **every** namespace that consumes the store, and each must be granted `get` (plus `list` for `dataFrom.find`) on the target resource. Grant this with a `ClusterRole` plus a `RoleBinding` per consuming namespace, or a `ClusterRoleBinding` if the same access is acceptable cluster-wide. When you instead pin `auth.serviceAccount.namespace`, only that one ServiceAccount needs the binding.
