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

# Kubernetes Secrets Manager

> Use Kubernetes Secrets to integrate secret storage with your OpenMetadata, supporting secure access to service credentials.

# Kubernetes Secrets Manager

OpenMetadata can use Kubernetes Secrets as its secrets manager backend, storing sensitive values (passwords, tokens, keys, etc.) as native K8s Secret objects instead of encrypted fields in the database.

## 1. Permissions (Kubernetes RBAC)

The OpenMetadata Server needs RBAC access to Kubernetes Secret objects in the target namespace. The Python ingestion runtime only needs **read** access.

### Required verbs

| Component                    | Verbs                               |
| ---------------------------- | ----------------------------------- |
| OpenMetadata Server (Java)   | `create`, `get`, `update`, `delete` |
| Ingestion / Airflow (Python) | `get`                               |

### Example: Role + RoleBinding (namespace-scoped)

```yaml theme={null}
apiVersion: v1
kind: ServiceAccount
metadata:
 name: openmetadata-secrets-sa
 namespace: <om-namespace>
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: openmetadata-secrets-role
 namespace: <secrets-namespace>
rules:
 - apiGroups: [""]
   resources: ["secrets"]
   verbs: ["create", "get", "update", "delete"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: openmetadata-secrets-rb
 namespace: <secrets-namespace>
subjects:
 - kind: ServiceAccount
   name: openmetadata-secrets-sa
   namespace: <om-namespace>
roleRef:
 apiGroup: rbac.authorization.k8s.io
 kind: Role
 name: openmetadata-secrets-role
```

If the ingestion runtime runs in a separate ServiceAccount, grant it a read-only Role with only `get`.

> If OpenMetadata and the target secrets live in the same namespace, `<om-namespace>` and `<secrets-namespace>` are the same.

***

## 2. Update configuration

### 2.1 OpenMetadata Server (`openmetadata.yaml`)

```yaml theme={null}
secretsManagerConfiguration:
 secretsManager: kubernetes                         # or env var SECRET_MANAGER
 prefix: ${SECRET_MANAGER_PREFIX:-""}               # Optional prefix for secret names
 tags: ${SECRET_MANAGER_TAGS:-[]}                   # Labels added to K8s Secrets, format: [key1:value1,key2:value2,...]
 parameters:
   namespace: ${OM_SM_NAMESPACE:-"default"}
   kubeconfigPath: ${OM_SM_KUBECONFIG_PATH:-""}
   inCluster: ${OM_SM_IN_CLUSTER:-"false"}
```

| Parameter        | Env var                 | Description                                                                                     |
| ---------------- | ----------------------- | ----------------------------------------------------------------------------------------------- |
| `secretsManager` | `SECRET_MANAGER`        | Set to `kubernetes`                                                                             |
| `prefix`         | `SECRET_MANAGER_PREFIX` | Optional prefix prepended to all secret names                                                   |
| `tags`           | `SECRET_MANAGER_TAGS`   | Key-value pairs added as K8s labels on created Secrets. Format: `[key1:value1,key2:value2,...]` |
| `namespace`      | `OM_SM_NAMESPACE`       | Namespace where secrets are stored (default: `default`)                                         |
| `kubeconfigPath` | `OM_SM_KUBECONFIG_PATH` | Path to a kubeconfig file (out-of-cluster only)                                                 |
| `inCluster`      | `OM_SM_IN_CLUSTER`      | Use in-cluster ServiceAccount auth (default: `false`)                                           |

#### Choosing `inCluster` vs `kubeconfigPath`

* **In-cluster (recommended for K8s deployments):** set `OM_SM_IN_CLUSTER=true`. Uses the pod's ServiceAccount and the RBAC from section 1. Leave `kubeconfigPath` empty.
* **Out-of-cluster:** set `OM_SM_IN_CLUSTER=false` and `OM_SM_KUBECONFIG_PATH` to a kubeconfig file readable by the OpenMetadata process. If the kubeconfig path is also empty, the default kubeconfig (`~/.kube/config`) is used.

### 2.2 Pipeline Service Client

In the `pipelineServiceClientConfiguration` section of `openmetadata.yaml`, set the secrets manager loader so the ingestion framework knows how to authenticate:

```yaml theme={null}
pipelineServiceClientConfiguration:
 secretsManagerLoader: ${PIPELINE_SERVICE_CLIENT_SECRETS_MANAGER_LOADER:-"noop"}
```

Supported values: `noop`, `airflow`, `env`.

### 2.3 Airflow configuration (self-managed Airflow)

If you run ingestion via your own Airflow deployment, configure it to resolve K8s secrets.

**Option A: `airflow.cfg`**

```ini theme={null}
[openmetadata_secrets_manager]
kubernetes_namespace = <secrets-namespace>
kubernetes_kubeconfig_path =
kubernetes_in_cluster = true
```

**Option B: Airflow environment variables**

Airflow auto-maps env vars with the pattern `AIRFLOW__<SECTION>__<KEY>`:

```bash theme={null}
AIRFLOW__OPENMETADATA_SECRETS_MANAGER__KUBERNETES_NAMESPACE=<secrets-namespace>
AIRFLOW__OPENMETADATA_SECRETS_MANAGER__KUBERNETES_KUBECONFIG_PATH=
AIRFLOW__OPENMETADATA_SECRETS_MANAGER__KUBERNETES_IN_CLUSTER=true
```

If Airflow runs inside the cluster, use `KUBERNETES_IN_CLUSTER=true` and ensure Airflow's ServiceAccount has `get` permissions on Secrets in the target namespace.

### 2.4 Non-Airflow ingestion (env loader)

When using the `env` secrets manager loader (e.g., standalone ingestion containers), configure via plain environment variables:

```bash theme={null}
KUBERNETES_NAMESPACE=<secrets-namespace>
KUBERNETES_IN_CLUSTER=true
KUBERNETES_KUBECONFIG_PATH=  # leave empty for in-cluster
```

> The Python `env` loader also auto-detects the current namespace from `/var/run/secrets/kubernetes.io/serviceaccount/namespace` when running in-cluster, falling back to `default`.

***

## 3. Migrate secrets and restart services

After updating configuration, migrate existing sensitive values from database encryption to Kubernetes Secrets:

```bash theme={null}
./bootstrap/openmetadata-ops.sh migrate-secrets
```

This migrates secrets from the DB to the configured Secrets Manager. It does **not** support migrating between external Secrets Managers (e.g., from AWS SSM to Kubernetes).

Then restart:

1. **OpenMetadata Server** — so it reads/writes secrets to K8s.
2. **Airflow / ingestion runtime** — so it picks up the secrets manager settings.

***

## 4. Workflow YAML (self-managed Airflow)

If you use your own Airflow to run ingestion workflows, configure the workflow YAML:

```yaml theme={null}
workflowConfig:
 openMetadataServerConfig:
   secretsManagerProvider: kubernetes
   secretsManagerLoader: airflow     # or "env" for non-Airflow
   hostPort: <OpenMetadata host and port>
   authProvider: <OpenMetadata auth provider>
```

***

## 5. How secrets are stored

### Naming convention

Secret names use **hyphens** as separators (not slashes) to comply with Kubernetes DNS naming rules:

```
<prefix>-<clusterName>-<path>-<components>
```

* `prefix` comes from `secretsManagerConfiguration.prefix`
* `clusterName` comes from the top-level `clusterName` in `openmetadata.yaml` (default: `openmetadata`)
* The remaining path components are derived from the entity/connection being stored

**Examples** (assuming default `clusterName: openmetadata`, no prefix):

```
openmetadata-bot-name-config-jwttoken
openmetadata-database-myservice-password
```

Names are sanitized for Kubernetes compatibility:

* Only lowercase alphanumeric characters and hyphens are allowed
* Consecutive hyphens are collapsed to a single hyphen
* Leading and trailing hyphens are stripped
* Truncated to 253 characters (K8s Secret name limit)

### Data format

Each secret is stored as a Kubernetes `Secret` object with:

* A single data key: `value` — containing the secret as UTF-8 bytes
* Labels:
* `app: openmetadata`
* `managed-by: openmetadata-secrets-manager`
* Plus any custom labels from the `tags` configuration

```yaml theme={null}
apiVersion: v1
kind: Secret
metadata:
 name: openmetadata-database-myservice-password
 namespace: default
 labels:
   app: openmetadata
   managed-by: openmetadata-secrets-manager
data:
 value: <base64-encoded-secret>
```
