Use AWS Secrets Manager secrets in Kubernetes
It is possible to automatically mount and use secrets from AWS Secrets Manager in EKS workloads. This documentation page will show you how. Note that this feature is optional for our EKS platform, and it’s disabled by default. If you’d like to enable it for your cluster(s) please reach out to us.
When enabled, two extra components will be deployed on the EKS cluster: the Secrets Store CSI driver and the AWS Secrets Manager provider. The CSI driver provides the in-cluster functionality to be able to mount secrets from external providers into Pods via a Container Storage Interface (CSI) volume, and the AWS Secrets Manager provider integrates it with the AWS service.
Note that besides mounting secrets into the container’s file system, it’s also possible to sync them to actual Kubernetes Secret objects and use them as environment variables in Pods.
Usage
To be able to mount a secret from AWS Secrets Manager, you first need to create a SecretProviderClass in the namespace you’re going to be using it:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: aws-secrets
spec:
provider: aws
parameters:
objects: |
- objectName: "arn:aws:secretsmanager:eu-west-1:[111122223333]:secret:MySecret-00AACC"
jmesPath:
- path: user.username
objectAlias: dbusername
- path: user.password
objectAlias: dbpassword
- objectName: "arn:aws:secretsmanager:eu-west-1:[111122223333]:secret:MySecret2-00AABB"
objectAlias: foobarYou can specify multiple secrets in the same SecretProviderClass, and all of them will be mounted in the same directory when referenced in the Pod. For secrets in JSON format, you can use the jmesPath attribute to filter which attributes from the JSON object to map. Provided the following JSON object, the example SecretProviderClass above would result in two files, dbusername and dbpassword with the respective contents john.doe and 123456, plus the file foobar for the secret MySecret2-00AABB, which will contain the whole secret value, regardless of its format.
{
"user": {
"username": "john.doe",
"password": "123456"
}
}Then the SecretProviderClass needs to be referenced from the Pod as a volume, like:
kind: Pod
apiVersion: v1
metadata:
name: test-secrets
spec:
containers:
- name: busybox
image: registry.k8s.io/e2e-test-images/busybox:1.29
command:
- "/bin/sleep"
- "10000"
volumeMounts:
- name: secrets
mountPath: "/mnt/secrets-store"
readOnly: true
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: aws-secretsThe secretProviderClass in volumeAttributes needs to match the name of the SecretsProviderClass defined above.
Note
The Pod needs to have the proper permissions (via IRSA) to access the referenced secrets from Secrets Manager.
To be able to use a secret value as an environment variable, you need to specify the secretObjects attribute in the SecretProviderClass object, like:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: aws-secrets
spec:
provider: aws
secretObjects:
- secretName: foosecret
type: Opaque
data:
- objectName: foobar # name of the mounted content to sync. this could be the object name or object alias
key: username
parameters:
objects: |
- objectName: "arn:aws:secretsmanager:eu-west-1:[111122223333]:secret:MySecret-00AACC"
jmesPath:
- path: user.username
objectAlias: dbusername
- path: user.password
objectAlias: dbpassword
- objectName: "arn:aws:secretsmanager:eu-west-1:[111122223333]:secret:MySecret2-00AABB"
objectAlias: foobarThis SecretProviderClass will instruct the CSI driver to create and sync the specified Secret objects with the contents of the referenced mounted secrets. To be able to use the Secret you still need to define the volumes and volumeMounts in the Pod specification. It is important that the volume is mounted in at least a container, even if it’s not going to be used, otherwise the controller won’t sync the Secret object.
kind: Pod
apiVersion: v1
metadata:
name: test-secrets
spec:
containers:
- name: busybox
image: registry.k8s.io/e2e-test-images/busybox:1.29
command:
- "/bin/sleep"
- "10000"
volumeMounts:
- name: secrets
mountPath: "/mnt/secrets-store"
readOnly: true
env:
- name: SECRET_USERNAME
valueFrom:
secretKeyRef:
name: foosecret
key: username
volumes:
- name: secrets
csi:
driver: secrets-store.csi.k8s.io
readOnly: true
volumeAttributes:
secretProviderClass: aws-secretsNote that the synced Secret object will only exist as long as there’s a Pod mounting the SecretProviderClass. If no Pods are using the SecretProviderClass, the CSI driver will stop syncing the Secret object and delete it.
Important
Without rotation enabled, the mounted content and the synced Kubernetes Secret are written once, when the Pod starts, and are not updated afterwards. They don’t change when:
- the secret value is updated in Secrets Manager after the Pod was deployed
- the
SecretProviderClassis updated after the Pod was created - objects are added or removed, or keys in existing
secretObjectsare changed
Restart the Pods to pick up the new values, or enable rotation as described below.
More information about this limitation and others can be found in the CSI driver documentation.
Automatic secret rotation
Instead of writing the secrets once, the CSI driver can poll Secrets Manager and update the mounted files and the synced Secret objects in place, without restarting your Pods. This is optional and disabled by default. To enable it, add the following to your cluster definition:
spec:
secretsManagerCSIDriver:
enabled: true
secretRotation:
enabled: true
pollInterval: 2mpollInterval: how often the driver checks Secrets Manager for new values, as a Go duration string (e.g.2m,15m,1h). Defaults to2m.
The driver polls Secrets Manager; it doesn’t get notified of changes. A new secret value takes up to one pollInterval to reach your Pods.
With rotation enabled:
- The mounted files are rewritten with the new value. Your application has to re-read the file; if it only reads the secret at startup, it still needs a restart.
- The synced
Secretobjects are updated, including when you add or remove objects or change keys insecretObjects. - Environment variables are not updated. A container’s environment is set when it starts, so a Pod that consumes the synced
SecretthroughsecretKeyRefkeeps the old value until it restarts. Mount the secret as a file instead, or restart your Deployment after a rotation.
Warning
Rotation multiplies your Secrets Manager API calls: the driver issues roughly pods × keys × cycles GetSecretValue requests. A short pollInterval on a SecretProviderClass with many secrets, mounted by many Pods, increases your Secrets Manager cost and can hit the API rate limits. Keep the 2m default unless you need faster propagation.
The driver records each rotation cycle as an event on the Pod, with reason MountRotationComplete or SecretRotationComplete on success and MountRotationFailed or SecretRotationFailed on failure:
kubectl describe pod <pod>When a rotation fails, the Pod keeps running with the last value that was fetched successfully. To see which secret versions a Pod currently has:
kubectl get secretproviderclasspodstatus <pod>-<namespace>-<secretproviderclass> -o yamlSecret rotation is an alpha feature of the Secrets Store CSI driver. If you’d like to enable it for your cluster(s), or need help picking a pollInterval, please reach out to us.
Documentation references
You can read more on how the Secrets Store CSI driver works in the official documentation page. Similar with the AWS Secrets Manager provider, there’s some more information in the official AWS documentation.