Enable KMS encryption
You can enable external Key Management Service (KMS) encryption for etcd data to centralize key management and meet compliance requirements.
-
You have access to the cluster as a user with the
cluster-adminrole. -
You have enabled the
TechPreviewNoUpgradefeature set to enable theKMSEncryptionfeature gate. -
You have a
HashiCorp Vault Enterpriseinstance accessible from your control plane nodes. Vault Community Edition is not available. -
Your control plane nodes have network access to the Vault server.
-
You have configured your Vault instance with:
-
Transit secrets engine enabled at the default
transit/mount path -
Encryption key created with type
aes256-gcm96(recommended for FIPS 140-3 compliance) -
Vault policy allowing the Kubernetes KMS v2 plugin to encrypt, decrypt, and read key information
-
Authentication method (token,
AppRole, oruserpass) configured with the policy attached
-
The Kubernetes KMS v2 plugin requires a Vault policy with the following capabilities:
path "transit/encrypt/kms-key" {
capabilities = ["update"]
}
path "transit/decrypt/kms-key" {
capabilities = ["update"]
}
path "transit/keys/kms-key" {
capabilities = ["read"]
}
path "auth/token/lookup-self" {
capabilities = ["read"]
}
Replace kms-key with your Vault Transit key name if using a different name.
|
|
Create an etcd backup before enabling KMS encryption. |
-
Deploy the KMS plugin on each control plane host as a static pod:
-
Configure the plugin to listen at
unix:///var/run/kmsplugin/kms.sock -
For your static pod, mount
/var/run/kmspluginashostPath -
Configure KMS provider connection details and authentication credentials
The following steps show how to deploy the
HashiCorpVault KMS plugin as a static pod. -
-
Create a static pod manifest file:
$ cat > /tmp/vault-kms-plugin.yaml <<'EOF' apiVersion: v1 kind: Pod metadata: name: vault-kms-plugin namespace: kube-system labels: app: vault-kms-plugin tier: control-plane spec: priorityClassName: system-node-critical hostNetwork: true containers: - name: vault-kms-plugin image: quay.io/redhat-isv-containers/698df066f8d1ddf179c15ef9:<version> command: - /vault-kubernetes-kms - -vault-address=<https://vault.example.com:8200> - -<vault_namespace>=admin - -auth-method=userpass - -userpass-username=<vault_username> - -userpass-password=<vault_password> - -transit-mount=transit - -transit-key=kms-key - -socket=unix:///var/run/kmsplugin/kms.sock volumeMounts: - name: kmsplugin mountPath: /var/run/kmsplugin resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi securityContext: privileged: true volumes: - name: kmsplugin hostPath: path: /var/run/kmsplugin type: DirectoryOrCreate EOFReplace the following values to match your environment:
- <version>
-
The Vault KMS plugin image version tag. Use
0.1.0-beta-ubi. - <vault_username>
-
Your Vault username for authentication.
- <vault_password>
-
Your Vault password for authentication.
- https://vault.example.com:8200
-
The Vault address. Update this field to match your Vault server URL.
- <vault_namespace>
-
Optional field.
The manifest uses the Red Hat certified container image from Quay.io (
quay.io/redhat-isv-containers/698df066f8d1ddf179c15ef9), which is the recommended image for Red Hat OpenShift Container Platform.Alternatively, you can use the HashiCorp image from Docker Hub by replacing the image field with
docker.io/hashicorp/vault-kube-kms:0.1.0-beta-ubi.
-
Deploy the static pod manifest to each control plane node by running the following commands:
$ MANIFEST=$(cat /tmp/vault-kms-plugin.yaml | base64) $ for node in $(oc get nodes --selector=node-role.kubernetes.io/master -o name | cut -d/ -f2); do echo "Deploying to $node..." oc debug node/$node -- chroot /host bash -c \ "echo '$MANIFEST' | base64 -d > /etc/kubernetes/manifests/vault-kms-plugin.yaml" doneThe kubelet automatically detects and starts static pods from
/etc/kubernetes/manifests/. -
Verify the static pods are running by entering the following command:
$ oc get pods -n kube-system -o wide | grep vault-kmsExample outputvault-kms-plugin-ip-10-0-16-7.compute.internal 1/1 Running 0 2m 10.0.16.7 vault-kms-plugin-ip-10-0-32-93.compute.internal 1/1 Running 0 2m 10.0.32.93 vault-kms-plugin-ip-10-0-69-106.compute.internal 1/1 Running 0 2m 10.0.69.106Static pod names include the node name as a suffix. You should see one pod per control plane node.
-
Verify the socket exists on a control plane node by entering the following command:
$ oc debug node/<node_name> -- chroot /host ls -la /var/run/kmsplugin/kms.sockExample outputsrwxr-xr-x. 1 root root 0 <timestamp> /var/run/kmsplugin/kms.sockStatic pods are managed by kubelet on each node and cannot be deleted with
oc delete pod. To remove a static pod, delete the manifest file from/etc/kubernetes/manifests/on each control plane node. -
Edit the
APIServercustom resource by entering the following command:$ oc edit apiserver cluster -
Add the KMS configuration to the
spec.encryptionsection:apiVersion: config.openshift.io/v1 kind: APIServer metadata: name: cluster spec: encryption: type: KMS -
Save and exit.
Migration begins automatically. The
kube-apiserver,openshift-apiserverandoauth-apiserverOperators will restart and roll out new revisions.The
openshift-apiserverandauthenticationoperators typically complete migration in 5-10 minutes. Thekube-apiserveroperator uses a conservative rollout strategy, updating one control plane node at a time and waiting for health checks before proceeding to the next node. This process can take 30 minutes or longer depending on cluster load.
-
Verify the encryption type by entering the following command:
$ oc get apiserver cluster -o jsonpath='{.spec.encryption.type}'Output should show
KMS. -
Monitor the
kube-apiserverrollout progress by entering the following command:$ oc get kubeapiserver cluster -o jsonpath='{.status.nodeStatuses}' | jq -r '.[] | "\(.nodeName | split(".")[0]): current=\(.currentRevision) target=\(.targetRevision)"'Example output during rolloutip-10-0-16-166: current=10 target=0 ip-10-0-32-93: current=9 target=10 ip-10-0-69-106: current=9 target=0The operator rolls out one node at a time. When all nodes show the same
currentrevision andtargetis0, the rollout is complete. -
Check the encryption migration status by entering the following command:
$ oc get kubeapiserver cluster -o jsonpath='{.status.conditions[?(@.type=="Encrypted")]}' | jq .Example output when complete{ "lastTransitionTime": "2026-05-15T17:39:02Z", "message": "All resources encrypted: secrets, configmaps", "reason": "EncryptionCompleted", "status": "True", "type": "Encrypted" }Wait for
reasonto showEncryptionCompletedbefore proceeding to verify encryption in etcd. -
Verify secrets are encrypted in etcd:
Wait for the
kube-apiserverrollout to complete on all control plane nodes before verifying encryption. During the rollout, API requests are distributed across nodes, and secrets created while some nodes are still on an earlier revision will not be encrypted with Kubernetes KMS v2.-
Create a test secret by entering the following command:
$ oc create secret generic test-secret --from-literal=key=value -n default -
Get an etcd pod name by entering the following command:
$ oc get pods -n openshift-etcd -l app=etcd -o name | head -1 -
Check the secret data in etcd by entering the following command:
$ oc exec -n openshift-etcd <etcd_pod_name> -- etcdctl get /kubernetes.io/secrets/default/test-secret --print-value-only | hexdump -C | head -1Output should begin with
k8s:enc:kms:v2:followed by encrypted binary data. -
Delete the test secret by entering the following command:
$ oc delete secret test-secret -n default
-