Kubernetes Secret Encryption Using KMS v2
Kubernetes Secrets contain sensitive data such as passwords, keys, and certificates. Kubernetes provides a KMS encryption provider for encrypting Secret data at rest. The KMS encryption provider uses an envelope encryption scheme to encrypt data stored in etcd. The data is encrypted using a data encryption key (DEK), and a new DEK is generated for each encryption operation. The DEKs are then encrypted with a key encryption key (KEK) that is stored and managed in a remote KMS.
The KMS provider uses gRPC to communicate with a specific KMS plugin. The KMS plugin is implemented as a gRPC server and deployed on the same host or hosts as the Kubernetes control plane components. It is responsible for communicating with the remote KMS and performing the key management operations required to encrypt and decrypt Secret data. Thales provides a KMS plugin that communicates with a remote KMS to manage Secret data encryption. The integration consists of the following components:
-
KMS Plugin: K8S-KMS-Plugin
-
Remote KMS: Thales Luna HSM
The key benefits of using Luna HSM along with K8S-KMS-Plugin to generate encryption keys that protect secret data for Kubernetes Secret encryption are:
-
Secure generation, storage, and protection of encryption keys on FIPS 140-2 Level 3 validated hardware.
-
Full life cycle management of the keys to ensure their integrity and reliability throughout their usage.
-
Maintenance of a comprehensive HSM audit trail for transparency and accountability in key operations.
-
Significant performance enhancements by offloading cryptographic operations from the server.
-
Using cloud services with confidence.
Tested Platforms
The following platforms have been tested with this integration:
| Kubernetes | KMS Plugin | Operating System |
|---|---|---|
| Kubernetes v1.33.11 | K8S-KMS-Plugin | RHEL 9 |
| Kubernetes v1.30.0 | K8S-KMS-Plugin | RHEL 8 |
All Luna HSMs support this integration, provided a supported Luna Client is used.
Prerequisites
The prerequisites for this integration are:
Configure Luna HSM
If you are using a Luna HSM:
Ensure that the HSM is set up, initialized, provisioned, and ready for deployment. Refer to the Luna HSM Product Documentation for more information.
Create a partition on the Luna HSM for use with Kubernetes.
Register a client for the system and assign the client to the partition to create an NTLS connection if you are using a Luna Network HSM. Initialize the Crypto Officer and Crypto User roles for the registered partition.
Ensure that the partition is successfully registered and configured. The command to see the registered partitions is:
# /usr/safenet/lunaclient/bin/lunacm

Enable partition policies 22 and 23 to allow activation and auto-activation for PED-authenticated HSM.
Follow the Luna Network HSM documentation for detailed steps for creating an NTLS connection, initializing partitions, and configuring the various user roles. The screenshot above shows 2 Luna partitions configured in HA.
Configuring Luna HSM HA
Refer to the Luna Network HSM documentation for HA steps and details regarding configuring and setting up two or more HSM appliances on Linux systems. You must enable the HAOnly setting in HA for failover to work, so that if the primary device stops functioning for any reason, all calls automatically route to the secondary device until the primary device starts functioning again.
This integration is tested in both HA and FIPS mode.
Set up Kubernetes Cluster
Refer to the Kubernetes Documentation for installing and running the Kubernetes cluster. For demonstration, the Kubernetes cluster used in this documentation is set up with 1 Master and 2 Worker nodes on VMware. After installation, ensure that the Kubernetes cluster is up and running successfully.

Supported Key Types
Luna HSM and K8S-KMS-Plugin support the following key types using the KMS v2 API:
| Key Name | Key Type | Key Size/Curve | Origin of the Key | Mechanism |
|---|---|---|---|---|
| Key Encryption Key (KEK) | RSA | 2,048-bit / 3,072-bit / 4,096-bit | Luna HSM | RSA OAEP |
| Key Encryption Key (KEK) | AES | 256-bit / 192-bit / 128-bit | Luna HSM | AES GCM / AES CBC / HMAC |
This integration is tested using RSA-2048 and AES-256-bit keys.
Integrating Luna HSM and KMS Plugin for Kubernetes Secret Encryption Using KMS v2
Following are the key stages to integrate Kubernetes Secret encryption with Luna HSM using the K8S-KMS-Plugin. When Kubernetes services perform encryption operations using the KMS v2 API, the KMS API communicates with the K8S-KMS-Plugin via gRPC to perform the requested operation on the Luna HSM.
Generate Key Encryption Key on Luna HSM
Ensure that the Luna Client is installed and NTLS is configured to use the Luna HSM on your master node. To generate the Key Encryption Key:
Execute the following command on your workstation to create the RSA keys:
# /usr/safenet/lunaclient/bin/cmu generatekeypair -modulusBits=2048 -publicExponent=65537 -label=rsa01 -encrypt=1 -decrypt=1 -wrap=1 -unwrap=1 -id=6E85A75DF42ADB4766544ECC78501E75
Where label is your key name (you can provide any label for your key) and id is a 16-byte hex value (GUID).
id or label is the attribute that K8S-KMS-Plugin refers to when retrieving the key from the Luna HSM, so ensure that every KEK has a unique ID or label.
You can use an online GUID generator to generate a unique ID: https://www.guidgenerator.com
You can also use one of the following Linux commands to generate a 16-byte hex value and use it as the ID:# head -c16 </dev/urandom | xxd -p -u# xxd -len 16 -plain /dev/urandom
Provide the partition password when prompted and select PKCS for the mechanism type.

View the generated key pair by running the following command:
# /usr/safenet/lunaclient/bin/cmu list
Provide the partition password when prompted.

For AES key generation, use the CKDEMO utility provided with the Luna Client, and ensure to set the key label and a unique key ID for each AES key.
Configuring Luna HSM with K8S-KMS-Plugin
K8S-KMS-Plugin acts as a bridge between the Kubernetes service and the Luna HSM. Kubernetes Secret encryption uses the KMS provider to handle encryption and decryption requests, and the KMS provider calls K8S-KMS-Plugin to communicate with the Luna HSM.
K8S-KMS-Plugin does not support KMS v1, which is deprecated as of Kubernetes v1.28 and disabled by default in Kubernetes v1.29 and later.
Complete the following tasks on the master node of the Kubernetes cluster. Configuration updates made on the Kubernetes master node are automatically deployed to all worker nodes connected to the master node.
Download the latest GA release of K8S-KMS-Plugin compatible with your Linux distribution from the plugin releases page.
# wget https://github.com/eclipse-keysealer/k8s-kms-plugin/releases/download/v0.8.0-test2-ga-aarch64-riscv64/k8s-kms-plugin-0.8.0.test2_ga_aarch64_riscv64-1.x86_64.rpm
Install the downloaded RPM package using the package manager appropriate for your Linux distribution.
# dnf install ./k8s-kms-plugin-0.8.0.test2_ga_aarch64_riscv64-1.x86_64.rpm

The binary is installed in your PATH. To confirm this, run the following command; it displays the help menu.
# k8s-kms-plugin --help

Create a directory for keeping all plugin-related configuration in one place.
# mkdir -p /etc/kms-plugin
Create the environment configuration file that the K8S-KMS-Plugin service reads at startup.
# vi /etc/kms-plugin/k8s-kms-plugin.yaml
Add the following contents, and adjust the values to match your environment:
k8s-kms-plugin:
log-level: "trace"
serve:
socket: "/tmp/run/hsm-plugin-server.sock"
provider: "luna"
p11-lib: "/usr/safenet/lunaclient/lib/libCryptoki2_64.so"
p11-label: "TPA-HA"
p11-pin: "userpin1"
p11-key-label: "rsa01"
algorithm: "rsa-oaep"
Configuration parameter reference:
| Parameter | Description |
|---|---|
log-level |
Values: info, trace, debug |
socket |
Path to the Unix socket file |
provider |
Values: luna, p11 |
p11-lib |
Full path to the Luna Cryptoki library file, i.e. libCryptoki2_64.so |
p11-label |
Luna token label, i.e. partition label |
p11-slot |
Luna slot ID; use either p11-label or p11-slot |
p11-pin |
Luna HSM partition Crypto Officer password |
p11-key-label |
Key label of your key encryption key (KEK); can be RSA or AES |
p11-key-id |
Key ID of your key encryption key (KEK); use either CKA_ID or CKA_LABEL |
algorithm |
Values: aes-gcm, aes-cbc, rsa-oaep |
p11-hmac-label |
HMAC key label; used only with the aes-cbc algorithm |
p11-hmac-id |
HMAC key ID; used only with the aes-cbc algorithm. Use either CKA_ID or CKA_LABEL |
Create a user and provide the required permissions to run the plugin service. Skip this step if your service runs as the root user.
# useradd kmsuser # gpasswd --add kmsuser hsmusers
Create a directory to store the socket file and set ownership and permissions for kmsuser.
# mkdir -p /tmp/run # chown -R kmsuser:kmsuser /tmp/run # chmod -R 777 /tmp/run/
Create a service to run K8S-KMS-Plugin as a systemd service using a non-privileged user.
# vi /etc/systemd/system/k8s-kms-plugin.service
Add the following contents to the k8s-kms-plugin.service file:
[Unit] Description=k8s-kms-plugin Service After=network.target [Service] Type=simple User=kmsuser Group=kmsuser Restart=always ExecStart=/usr/bin/k8s-kms-plugin serve --config /etc/kms-plugin/k8s-kms-plugin.yaml [Install] WantedBy=multi-user.target
Start the K8S-KMS-Plugin service.
# systemctl daemon-reload # systemctl enable k8s-kms-plugin # systemctl start k8s-kms-plugin
Verify that the K8S-KMS-Plugin service is running.
# systemctl status k8s-kms-plugin -l --no-pager

Verify that the socket file is created under the /tmp/run directory specified in k8s-kms-plugin.yaml.
# ls -ltr /tmp/run

Deploying K8S-KMS-Plugin as KMS Provider
Now deploy K8S-KMS-Plugin as the KMS provider and configure the API server to use the KMS provider for Kubernetes Secret encryption.
Create the encryption configuration file encryption-config.yaml, once K8S-KMS-Plugin is ready to use as an encryption provider, on the master node where the API server is running:
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: hsm-plugin
endpoint: unix:///tmp/run/hsm-plugin-server.sock
timeout: 3s
- identity: {}
Save the encryption configuration file encryption-config.yaml at any location on the master host. For example:
/etc/kubernetes/kms/encryption-config.yaml
Open the kube-apiserver.yaml file from the static pod manifest location and edit it to add the --encryption-provider-config flag to the kube-apiserver command list. The flag must point to the encryption-config.yaml file.
spec:
containers:
- command:
- kube-apiserver
- --encryption-provider-config=/etc/kubernetes/kms/encryption-config.yaml
- --encryption-provider-config-automatic-reload=true
- --advertise-address=10.164.75.18
- --allow-privileged=true
- --authorization-mode=Node,RBAC
...
To allow the kube-apiserver pod to communicate with hsm-plugin-server.sock, add access to the directories where the encryption-config.yaml file and the Unix socket are located. Add mount points in kube-apiserver.yaml as shown below, ensuring correct indentation:
...
volumeMounts:
- mountPath: /etc/kubernetes/kms
name: kms
readOnly: true
- mountPath: /tmp/run
name: socket
...
volumes:
- hostPath:
path: /etc/kubernetes/kms
type: DirectoryOrCreate
name: kms
- hostPath:
path: /tmp/run
type: DirectoryOrCreate
name: socket
...
Save and close the kube-apiserver.yaml file. The API server restarts automatically when you save the changes. Ensure that your cluster is back online without any failure and the API server is READY and Running.
# kubectl get pods --all-namespaces

Confirm that the API server is configured successfully to use hsm-plugin-server.sock and that the connection is established by running the following command on the master node:
# ss -a --unix -p | grep hsm-plugin-server.sock

The API server is now configured to use K8S-KMS-Plugin as the KMS provider, and K8S-KMS-Plugin is ready to serve requests from the API server for Secret encryption and decryption. Continue to Verifying Kubernetes Secret Encryption Using KMS Provider to encrypt and decrypt the Kubernetes Secrets.
Verifying Kubernetes Secret Encryption Using KMS Provider
A Kubernetes Secret is encrypted when written to etcd. After restarting your kube-apiserver, any newly created or updated Secret is encrypted when stored. To verify this, use the etcdctl command-line program to retrieve the contents of your Secret. To verify Secret encryption using the KMS provider:
Create a new Secret called mysecret in the default namespace.
# kubectl create secret generic mysecret -n default --from-literal=mykey=mys3cr3t
The encrypted Secret is saved in etcd.
Read the Secret out of etcd using the etcdctl command line. The command is listed below with parameter values; change all parameter values to match your environment.
# alias etcdctl3="ETCDCTL_API=3 /run/containerd/io.containerd.runtime.v2.task/k8s.io/8e56d11c88ec8c5cb0add95c8add48343ec6f4bbf1a30a82c5c0e85b7abdb540/rootfs/usr/local/bin/etcdctl --endpoints="https://127.0.0.1:2379" --cert=/etc/kubernetes/pki/apiserver-etcd-client.crt --key=/etc/kubernetes/pki/apiserver-etcd-client.key --cacert=/etc/kubernetes/pki/etcd/ca.crt" # etcdctl3 get /registry/secrets/default/mysecret
You will see the kms:v2:hsm-plugin identifier and the encrypted value, as shown below:

Run the following command to confirm that the stored Secret is prefixed with k8s:enc:kms:v2:hsm-plugin, which indicates that the KMS provider has encrypted the resulting data.
# etcdctl3 get /registry/secrets/default/mysecret | hexdump -C

Confirm that the Secret decrypts correctly when retrieved via the API server.
# kubectl get secret mysecret -n default -o yaml

Run the following command to encrypt all pre-existing Secrets. The command reads all Secrets and re-encrypts them using the KMS provider.
# kubectl get secrets --all-namespaces -o json | kubectl replace -f -
If an error occurs due to a conflicting write, retry the command. For larger clusters, it is recommended to subdivide the Secrets by namespace or script an update. All Secrets are now encrypted using the key generated on the Luna HSM via K8S-KMS-Plugin. The Secrets are decrypted automatically by K8S-KMS-Plugin when called by the API server.
Once all Secrets are encrypted, remove the identity provider to prevent the API server from honoring unencrypted data. Edit /etc/kubernetes/kms/encryption-config.yaml, remove the identity provider at the end, and save the file.
---
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: hsm-plugin
endpoint: unix:///tmp/run/hsm-plugin-server.sock
timeout: 3s
This completes the Kubernetes Secret integration with Luna HSM using K8S-KMS-Plugin. All Secrets are encrypted by the key encryption key (KEK) stored on the Luna HSM partition, and decrypted by K8S-KMS-Plugin whenever called by the API server at service start.
Switching From a Local Encryption Provider to the KMS Provider
If you have enabled the native encryption provider and want to migrate to the KMS provider for enhanced security, follow the steps below to switch from a native encryption provider to the KMS provider and re-encrypt all Secrets using the KMS provider. Before proceeding, complete the steps in Integrating Luna HSM and KMS Plugin for Kubernetes Secret Encryption Using KMS v2.
Edit the encryption configuration file encryption-config.yaml on the master host. Add the KMS provider as the first entry in the configuration file, as shown in the example below:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- kms:
apiVersion: v2
name: hsm-plugin
endpoint: unix:///tmp/run/hsm-plugin-server.sock
- aescbc:
keys:
- name: key1
secret: <BASE64 ENCODED SECRET>
Where key1 is the name of your key and <BASE64 ENCODED SECRET> is the actual key of the native encryption provider. Ensure that the Unix socket path matches the actual socket path mounted in the kube-apiserver manifest YAML.
Restart all kube-apiserver processes.
Run the following command to force all Secrets to be re-encrypted using the KMS provider.
# kubectl get secrets --all-namespaces -o json | kubectl replace -f -
This completes the migration from a software-based KEK to a more secure, HSM-backed KEK using K8S-KMS-Plugin as the KMS provider. The Luna HSM secures the encryption keys on FIPS-validated hardware security modules, providing a higher level of security than locally stored encryption keys.
Rotating the Key Encryption Key for KMS Provider
If you have enabled the KMS encryption provider by performing the steps in Integrating Luna HSM and KMS Plugin for Kubernetes Secret Encryption Using KMS v2, all Secrets are now encrypted using the KMS provider, where the Luna HSM is configured as an external KMS. This section describes KEK rotation as supported by K8S-KMS-Plugin. To rotate the key encryption key for the KMS provider, perform the following steps:
Generate a new RSA or AES key on the Luna HSM, following the steps in Generate Key Encryption Key on Luna HSM.
Open the environment configuration file that the K8S-KMS-Plugin service reads at startup.
# vi /etc/kms-plugin/k8s-kms-plugin.yaml
Configure the key rotation settings. Add the newly generated KEK reference to the serve section and the existing KEK reference to the rotation section. The serve section specifies the new KEK used for encryption, while the rotation section specifies the existing KEK used to decrypt data encrypted with the previous key. Adjust the values to match your environment.
k8s-kms-plugin:
log-level: "trace"
serve:
socket: "/tmp/run/hsm-plugin-server.sock"
provider: "luna"
p11-lib: "/usr/safenet/lunaclient/lib/libCryptoki2_64.so"
p11-label: "TPA-HA"
p11-pin: "userpin1"
p11-key-label: "AES01" # Newly generated KEK label
algorithm: "aes-gcm"
# Key rotation settings
rotation:
old-provider: "luna"
old-p11-lib: "/usr/safenet/lunaclient/lib/libCryptoki2_64.so"
old-p11-label: "TPA-HA"
old-p11-pin: "userpin1"
old-p11-key-label: "rsa01" # Existing or old KEK
old-algorithm: "rsa-oaep"
Update the K8S-KMS-Plugin service and add the rotation subcommand to the existing ExecStart command.
# vi /etc/systemd/system/k8s-kms-plugin.service
... ExecStart=/usr/bin/k8s-kms-plugin serve rotation --config /etc/kms-plugin/k8s-kms-plugin.yaml ...
Restart the K8S-KMS-Plugin service.
# systemctl daemon-reload # systemctl restart k8s-kms-plugin
Verify that the K8S-KMS-Plugin service is running.
# systemctl status k8s-kms-plugin -l --no-pager

Restart all kube-apiserver processes to bring the newly configured KEK into effect.
Optionally, run the following command to force all Secrets to be re-encrypted using the KMS provider.
# kubectl get secrets --all-namespaces -o json | kubectl replace -f -
Update the K8S-KMS-Plugin configuration and service files as follows:
Comment out or remove the rotation section of /etc/kms-plugin/k8s-kms-plugin.yaml:
k8s-kms-plugin:
log-level: "trace"
serve:
socket: "/tmp/run/hsm-plugin-server.sock"
provider: "luna"
p11-lib: "/usr/safenet/lunaclient/lib/libCryptoki2_64.so"
p11-label: "TPA-HA"
p11-pin: "userpin1"
p11-key-label: "AES01" # Newly generated KEK label
algorithm: "aes-gcm"
# Key rotation settings
#rotation:
# old-provider: "luna"
# old-p11-lib: "/usr/safenet/lunaclient/lib/libCryptoki2_64.so"
# old-p11-label: "TPA-HA"
# old-p11-pin: "userpin1"
# old-p11-key-label: "rsa01" # Existing or old KEK
# old-algorithm: "rsa-oaep"
Remove the rotation subcommand from the ExecStart command in /etc/systemd/system/k8s-kms-plugin.service:
... ExecStart=/usr/bin/k8s-kms-plugin serve --config /etc/kms-plugin/k8s-kms-plugin.yaml ...
Restart the K8S-KMS-Plugin service.
# systemctl daemon-reload # systemctl restart k8s-kms-plugin
This completes the Kubernetes Secret encryption key rotation using K8S-KMS-Plugin as the KMS provider, with the Luna HSM securing the encryption keys. The newly created KEK on the Luna HSM is used to decrypt the Secrets each time Kubernetes services restart.