KMS v2 (Kubernetes KMS Provider version 2) is the stable (GA since Kubernetes 1.29, KMS v1 deprecated in 1.28 and disabled by default in 1.29) mechanism for encrypting the contents of the Kubernetes etcd datastore at rest using an external key management service. Without encryption, Kubernetes Secrets stored in etcd are base64-encoded — trivially decodable by anyone with read access to the etcd data files or a snapshot. With KMS v2 enabled, resources written to etcd — Secrets, ConfigMaps, and any other API objects selected by the EncryptionConfiguration — are encrypted before being persisted, using a unique per-object key derived locally. The envelope encryption scheme means those per-object keys are never stored in plaintext: only their encrypted form lives in etcd, and only the external KMS holds the wrapping key. The encryption configuration is declared in a file referenced by the API server’s --encryption-provider-config flag, with kms: apiVersion: v2 selecting the KMS v2 code path, and endpoint: unix:///path/to/plugin.sock pointing at the plugin’s Unix domain socket.
The key architectural improvement of KMS v2 over v1 is how Data Encryption Keys (DEKs) are managed. In KMS v1, a new DEK was generated for every single etcd write, requiring one external KMS API call per write to encrypt that DEK — meaning a write-heavy cluster made thousands of KMS calls per second, with associated latency, cost, and rate-limit risk. In KMS v2, the API server generates a single secret seed at startup, makes one KMS API call to encrypt that seed under the remote Key Encryption Key (KEK), caches the encrypted seed in memory, and then derives all per-object DEKs locally using a Key Derivation Function (KDF) — combining the cached seed with per-object random data. Each derived DEK encrypts exactly one etcd object and is then discarded; only the encrypted seed and the per-object ciphertext are persisted. The KMS is contacted again only at startup and at KEK rotation: the API server polls the plugin’s Status gRPC method approximately every minute to detect a new key version from the KMS, at which point it generates and encrypts a new seed automatically. Verifying encryption is active is straightforward: direct etcdctl get on a resource should return data prefixed with k8s:enc:kms:v2:<plugin-name>: — a k8s:enc:aescbc: prefix or plain JSON indicates the resource predates the current provider and must be force-rewritten via kubectl get <resource> --all-namespaces -o json | kubectl replace -f -.
The plugin deployment model is important and frequently misunderstood. Because the kube-apiserver itself runs as a static pod on control plane nodes (in kubeadm clusters) or as a standalone process, it cannot have a sidecar container — there is no pod to inject into. The KMS plugin must therefore be deployed as a separate static pod in /etc/kubernetes/manifests/ on each control plane node, configured with priorityClassName: system-node-critical so the kubelet starts it before kube-apiserver. The plugin listens on a Unix domain socket on the host filesystem; both the plugin and the kube-apiserver static pod must mount the same host directory to share that socket. If the plugin is not running when kube-apiserver starts, the API server will fail to start — making the plugin a hard dependency of the control plane. The gRPC interface the plugin implements has three methods: Encrypt(plaintext) → ciphertext, Decrypt(ciphertext) → plaintext, and Status() → {version, healthz, keyID}. The keyID returned by Status() is what the API server compares across polls to detect KEK rotation. Available plugin implementations in the upstream ecosystem include: kubernetes-sigs/aws-encryption-provider (maintained by kubernetes-sigs, backed by AWS KMS, the most production-mature option for AWS); azure-kubernetes-kms (Azure Key Vault, maintained in the openshift GitHub org as a fork); ThalesGroup/k8s-kms-plugin (PKCS#11 interface for HSMs including Thales Luna, supports both local and remote HSM via proxy mode); and FalcoSuessgott/vault-kubernetes-kms (HashiCorp Vault Transit engine via AppRole or Token auth — this is a community project, explicitly flagged as early-stage and not yet recommended for production as of this writing; note that since the plugin runs as a static pod before the Kubernetes API is available, Vault Kubernetes auth cannot be used, making AppRole or Token the only viable auth methods). For GCP, the k8s-cloudkms-plugin exists but maintenance status should be verified before production use.
OpenShift’s relationship to KMS v2 must be stated precisely to avoid the errors that appear in many secondary sources. OpenShift’s existing etcd encryption mechanism — available since OCP 4.3 via spec.encryption.type: aesCBC or aescbc in the APIServer CR — uses locally-managed, automatically-rotated keys stored in a Secret on the control plane host filesystem under /etc/kubernetes/static-pod-resources/. This provides encryption at rest but with no external key control: the keys are managed entirely within the cluster. KMS v2 integration with an external KMS was introduced as a new capability in OpenShift 4.21, with AWS KMS as the only supported provider: the APIServer CR’s spec.encryption.kms.type field accepts only the value AWS in 4.21, and the spec.encryption.kms.aws stanza configures the key ARN and region. No other KMS backend — Vault, Azure Key Vault, GCP KMS, or PKCS#11 — is a supported, Red Hat-managed integration for OCP etcd KMS encryption as of this writing. The OCP API Server Operator manages the plugin lifecycle, encryption configuration, and key rotation automatically for the AWS KMS path; self-managed clusters requiring a different KMS backend on vanilla Kubernetes can deploy the appropriate plugin as a static pod manually, but this is outside the OCP supported envelope.
