Akeyless K8s Secrets Injector
Overview
The Akeyless K8s Secrets Injector plugin enables K8s applications and workloads to use Static, Rotated, and Dynamic secrets as well as Certificates sourced from the Akeyless Platform.
This injector leverages the K8s MutatingAdmissionWebhook
to intercept and augment specifically annotated pod configurations for secrets injection. By doing so, the user benefits as the applications remain ״Akeyless unaware״ as the secrets are stored either as an environment variable or as a file at a filesystem path in their container.
Before the application starts, the injector deploys an init container to fetch and inject secrets at pod start-up, after which the init-container shuts down. To apply an automatic rollout restart to your deployments upon any change to your secrets, you can use the Injector with restart-rollout mode, which can track any changes of Static, Rotated and Certificates.
If the application consumes secrets which regularly change, an annotation can be used to deploy an additional Sidecar container which runs alongside the application to monitor changes in secrets. The Sidecar tracks and updates secrets within injected files inside the pods, according to specifically annotated pod configurations, and will remain up for the entire application lifecycle. Relevant for cases where the app can watch for live changes in files.
Although authorization in K8s is intentionally high level, you can configure the injector to support full and flexible segregation using K8s policies together with the Akeyless Platform's Role-based Access Control (RBAC).
For details, see Policy Segregation for Kubernetes.
Note
The documentation, configuration and examples for the plugin are also applicable to Red Hat OpenShift environment.
Prerequisites
-
Helm Installed.
-
K8s Auth or one of the supported Authentication Methods for Kubernetes.
-
K8s v1.19
and above. -
For Azure Kubernetes Service (AKS), managed-identity is enabled on your AKS cluster.
-
For Google Kubernetes Engine (GKE) cluster, port 8443 is opened in your Google Cloud Platform (GCP) firewall rules.
Create a Secret in Akeyless
For example, the following command creates a static secret called my_k8s_secret inside K8s folder.
akeyless create-secret --name /K8s/my_k8s_secret --value myPassword
Alternatively, a secret can contain JSON
structured data, for example:
akeyless create-secret --name /K8s/secret-json --value '{"aws_access_key":"1234","aws_key_id":"abcd"}'
Note
The following example uses a pre-defined K8s Auth called K8s_Auth in K8s folder i.e.
K8s/K8s_Auth
Create an Access Role
Create an Access Role associate the role with an Auth Method and grant access to the secret.
For example, the following command creates K8s_role role, the role is associated to K8s_Auth Auth Method, and grant read and list access to all the secrets in K8s folder
akeyless create-role --name /K8s/K8s_Role
akeyless assoc-role-am --role-name /K8s/K8s_Role --am-name K8s/K8s_Auth
akeyless set-role-rule --role-name /K8s/K8s_Role --path /K8s/'*' --capability read --capability list
Install the Injector
- Add the Akelyess K8s Injector Helm repository from here and update your Helm repositories.
helm repo add akeyless https://akeylesslabs.github.io/helm-charts
helm repo update
- Fetch the values.yaml file locally:
helm show values akeyless/akeyless-secrets-injection > values.yaml
Modify the following values under the env
section as follows:
-
Set
AKEYLESS_ACCESS_ID
to the Access ID of the Auth Method with access to the secret. -
Set
AKEYLESS_ACCESS_TYPE
tok8s
. Or with any other supported Authentication Methods for Kubernetes. -
Set
AKEYLESS_K8S_AUTH_CONF_NAME
with your Gateway Kubernetes Auth name. Relevant only for Access type ofk8s
. -
Set
AKEYLESS_API_GW_URL
with the URL of your Gateway API v1 endpoint:/8000/api/v1
or port8080
-
Optional
AKEYLESS_CRASH_POD_ON_ERROR
Upon any failure, a pod that tries to fetch a secret and fails will crash. By default this option is disabled. Can be controlled globally or at the deployment level using a dedicated annotation. -
Optional
restartRollout
- To apply automatic rollout restart to your deployments upon secret changes. Relevant only for the kinds of:Deployment
,DaemonSet
orStatefulSet
. To control which deployments are not effected by the restart-rollout, you can use a dedicated annotation to disable this on the deployment level. -
AKEYLESS_REGISTRY_CREDS
- A reference to an existing secret that holds your container registry credentials. Relevant when working with Environment variables and a private container registry, to override automatically the docker entrypoint, can be utilized at the deployment level using a dedicated annotation. not required for public registry.
restartRollout:
enabled: false
interval: 1m
env:
AKEYLESS_ACCESS_ID: "<AccessID>"
AKEYLESS_ACCESS_TYPE: "k8s"
AKEYLESS_K8S_AUTH_CONF_NAME: "K8s_Auth_Name"
AKEYLESS_API_GW_URL: "https://Your-Gateway-URL:8080"
# AKEYLESS_CRASH_POD_ON_ERROR: "enable"
Note
When working with Red Hat OpenShift, enable the OpenShift flag in the values.yaml chart file:
openshiftEnabled: true
Injecting secrets into the namespace where the
k8s injector
plugin is installed is unsupported.
- On your K8s cluster, create and label a namespace for Akeyless.
kubectl create namespace akeyless
kubectl label namespace akeyless name=akeyless
Alternatively, for Red Hat OpenShift:
oc create namespace akeyless
oc label namespace akeyless name=akeyless
- Deploy the Helm chart to the selected namespace.
helm install injector akeyless/akeyless-secrets-injection --namespace akeyless -f values.yaml
- Validate the deployment state.
kubectl get all -n akeyless
Alternatively, for Red Hat OpenShift:
oc get all -n akeyless
The following is an example of the output:
kubectl get all -n akeyless
NAME READY STATUS RESTARTS AGE
pod/injector-akeyless-secrets-injection-77c857d496-r5xth 1/1 Running 1 (73s ago) 1d
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/injector-akeyless-secrets-injection ClusterIP 10.97.228.133 <none> 443/TCP 1d
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/injector-akeyless-secrets-injection 1/1 1 1 1d
NAME DESIRED CURRENT READY AGE
replicaset.apps/injector-akeyless-secrets-injection-77c857d496 1 1 1 1d
Launch an Application
The Akeyless Injector supports the following modes of operations, using Environment Variables, File Injection, and SideCar mode which can work only with File Injection.
Environment Variable
Set the following annotations in your deployment YAML
files:
Enable the plugin under the annotations
section, in your app deployment file:
akeyless/enabled: "true"
To inject your secret into your pod environment variable during the init
phase, the value of your env
should be set with akeyless:/Path/to/secret
.
The following example demonstrates Akeyless secret injection as an Environment Variable into an alpine deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
replicas: 1
selector:
matchLabels:
app: hello-secrets
template:
metadata:
labels:
app: hello-secrets
annotations:
akeyless/enabled: "true"
spec:
containers:
- name: alpine
image: alpine
command:
- "sh"
- "-c"
- "echo $MY_SECRET && echo going to sleep... && sleep 10000"
env:
- name: MY_SECRET
value: akeyless:/K8s/my_k8s_secret
Apply:
kubectl apply -f env.yaml
The following example demonstrates Akeyless secret injection as an Environment Variable into MySQL Database and WordPress server deployments:
apiVersion: v1
kind: Service
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
ports:
- port: 3306
selector:
app: wordpress
tier: mysql
clusterIP: None
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress-mysql
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: mysql
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: mysql
annotations:
akeyless/enabled: "true"
spec:
containers:
- image: bitnami/mysql:5.7.33
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: akeyless:/K8s/my_k8s_secret
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pv-claim
apiVersion: v1
kind: Service
metadata:
name: wordpress
labels:
app: wordpress
spec:
ports:
- port: 80
selector:
app: wordpress
tier: frontend
type: LoadBalancer
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: wp-pv-claim
labels:
app: wordpress
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 20Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: wordpress
labels:
app: wordpress
spec:
selector:
matchLabels:
app: wordpress
tier: frontend
strategy:
type: Recreate
template:
metadata:
labels:
app: wordpress
tier: frontend
annotations:
akeyless/enabled: "true"
spec:
containers:
- image: wordpress:4.8-apache
name: wordpress
env:
- name: WORDPRESS_DB_HOST
value: wordpress-mysql
- name: WORDPRESS_DB_PASSWORD
value: akeyless:/K8s/my_k8s_secret
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
persistentVolumeClaim:
claimName: wp-pv-claim
Apply:
kubectl apply -f MySQLWordPress.yaml
kubectl apply -f Wordpress.yaml
Another example demonstrates fetching secret specific versions for example version=2
of the secret my_k8s_secret
in the K8s
folder, decode in base64:
- name: MY_SECRET
value: 'akeyless:/K8s/my_k8s_secret|decode=base64|version=2'
Or to extract a specific key from a secret that contains a JSON
structured data:
- name: MY_JSON_SECRET
value: 'akeyless:/K8s/secret-json|jq=.json_key'
Alternatively, you can parse the entire JSON
keys automatically into environment variables using:
- name: MY_JSON_SECRET
value: 'akeyless:/K8s/secret-json|parse_json_secret=true'
This will create an environment variable per each key that exists within the JSON
using the following format:MY_JSON_SECRET_JSON_KEY_NAME
.
To create the environment variables without the prefix you can use the parse_json_without_prefix
flag instead.
Note
The
parse_json_secret
flag is designed to handle flat JSON structures with single string-values (it does not support nested JSONs, nor array values).
Inject Secret via ConfigMap
For existing environments that currently use ConfigMaps with K8s secrets, you can modify your config maps to fetch the relevant secrets from Akeyless, instead of updating all your deployment manifest files, for example:
kind: ConfigMap
apiVersion: v1
metadata:
name: akeyless-config-map
data:
MYSQL_ROOT_PASSWORD: "akeyless:/K8s/my_k8s_secret"
And the corresponding deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
replicas: 1
selector:
matchLabels:
app: hello-secrets
template:
metadata:
labels:
app: hello-secrets
annotations:
akeyless/enabled: "true"
spec:
containers:
- name: alpine
image: alpine
command:
- "sh"
- "-c"
- "echo $MY_SECRET && echo going to sleep... && sleep 10000"
env:
- name: MY_SECRET
valueFrom:
configMapKeyRef:
name: akeyless-config-map
key: MYSQL_ROOT_PASSWORD
Override Entrypoint Automatically
The injector can be set with credentials of your private registry using a secret reference that exists inside Akeyless, this secret should contain a JSON with creds to your registry, supporting either username & password or a simple token format, for example:
{
"username": "",
"password": ""
}
{
"token": "<value>"
}
this secret can be set globally on the deployment using this variable AKEYLESS_REGISTRY_CREDS = /Path/to/secret
or explicitly on the pod level using this annotation: akeyless/registry_creds: /Path/to/secret
.
Once this secret is provided the manual command is not required, and the Injector will override the entrypoint automatically.
In AWS and GCP environments the node IAM role can be utilized automatically to connect to your registry, hence no secret reference is required.
Public Container Registry
For public container registry no secret is required, the Injector will try to override the entrypoint automatically.
File Injection
Enable the plugin under the annotations
section, in your app deployment file:
akeyless/enabled: "true"
The default location of the Akeyless secrets folder inside your pod file system is /akeyless/secrets/.
To explicitly set a different location you can override this by adding |location=<path>
after your secret name within the annotation.
For example, /K8s/my_k8s_secret
and /K8s/my_k8s_secret2
will be saved inside your pod filesystem under the /tmp/ folder as secret1
and secret2
respectively.
akeyless/inject_file: "/K8s/my_k8s_secret|location=/tmp/secret1,/K8s/my_k8s_secret2|location=/tmp/secret2"
You can specify different destinations for file injection for multiple secrets using the following format: akeyless/inject_file_*
.
In this example, we will inject different secrets into different locations:
akeyless/inject_file_s1: "/K8s/secret-json|jq=.object.first|location=/tmp/secret_json|permission=0777|decode=base64"
akeyless/inject_file_secret2: "/K8s/my_k8s_secret|location=/akeyless/secret2"
akeyless/inject_file_secret1: "/K8s/my_k8s_secret2"
To inject an entire folder of secrets from Akeyless, for example, all secrets under /K8s/my-secrets-folder
will be injected into the pod fs
under /tmp/secrets/K8s/<secrets-full-name>
:
akeyless/inject_folder: "/K8s/my-secrets-folder/|location=/tmp/secrets/"
To inject only the secrets from the source folder without the full folders structure from Akeyless, for example all secrets under /K8s/my-secret-folder
use the following pipe command with the annotation:
akeyless/inject_folder: "/K8s/my-secrets-folder/|folder_location=/tmp/secrets/"
To modify the default target folder location of the Akeyless secrets on your pods' file, you can use this setting on the injector chart values.yaml
file.
AKEYLESS_SECRET_DIR_NAME: "/My/New/Dir" #Path to save secrets inside pod's file systems
The following example demonstrates Akeyless secret injection with file injection into an alpine deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-file
spec:
replicas: 1
selector:
matchLabels:
app: hello-secrets-2
template:
metadata:
labels:
app: hello-secrets-2
annotations:
akeyless/enabled: "true"
akeyless/inject_file: "/K8s/my_k8s_secret"
spec:
containers:
- name: alpine
image: alpine
command:
- "sh"
- "-c"
- "cat /akeyless/secrets/K8s/my_k8s_secret && echo going to sleep... && sleep 10000"
Apply:
kubectl apply -f Injectfile.yaml
Sidecar Mode
To keep track of secret changes while reflecting them into your pods during their lifetime, you can use the Sidecar mode, for example, while working with Dynamic or Rotated secrets.
Enable the plugin under the annotations
section, in your app deployment file and enable the sidecar mode by adding the following annotations:
akeyless/enabled: "true"
akeyless/side_car_enabled: "true"
Set the desired refresh interval for the sidecar where the units are Int
and the supported time units are "s"
, "m"
or "h"
:
akeyless/side_car_refresh_interval: "30m"
To retrieve multiple versions of your secret simultaneously (only for Static and Rotated):
akeyless/side_car_versions_to_retrieve: "2"
The following example demonstrates secret injection with sidecar mode into an alpine deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: test-file-sidecar
spec:
replicas: 1
selector:
matchLabels:
app: file-secrets
template:
metadata:
labels:
app: file-secrets
annotations:
akeyless/enabled: "true"
akeyless/inject_file: "/K8s/my_k8s_secret|location=/secrets/secretsVersion.json"
akeyless/side_car_enabled: "true"
akeyless/side_car_refresh_interval: "30m"
akeyless/side_car_versions_to_retrieve: "2"
spec:
containers:
- name: alpine
image: alpine
command:
- "sh"
- "-c"
- "while true; do [ ! -f /secrets/timestamp ] || [ /secrets/secretsVersion.json -nt /secrets/timestamp ] && touch /secrets/timestamp && cat /secrets/secretsVersion.json && echo ''; sleep 15; done"
Apply:
kubectl apply -f Akeyless_sidecar.yaml
Annotations List
The following table lists the available annotations:
Annotation | Options | Description |
---|---|---|
akeyless/enabled: "true" | "true" or "false" | Enable the K8s plugin |
akeyless/side_car_enabled: "true" | "true" or "false" | Set the K8s plugin to work in sidecar mode |
akeyless/disable_restart_rollout: "true" | "true" or "false" | To disable the restart-rollout on a specific deployment |
akeyless/side_car_refresh_interval: "30m" | Int followed by"s" , "m" or "h" units | Set the desired refresh time interval for the Akeyless sidecar, by default set to 30m |
akeyless/side_car_versions_to_retrieve: "2" | "2" or higher | Fetch the last X versions of your secret |
akeyless/inject_file: "/mysecret/|location=/path to save secret name" | location= /path to save secret name | Set the location for your secrets to be saved within your pod file system. Note: Available for files only |
akeyless/inject_file: "/mysecret|permission=0644" | permission= 0644 | Set the permission of the file that contains your secret value Default is 0644 Note: Available for files only |
akeyless/inject_file: "/mysecret|version=1" | version= {version number} | Fetch a specific version of your secret The default value is set to the latest version Note: Available for Environment variables as well |
akeyless/inject_file: "/mysecret|decode=base64" | decode= none or base64 | Set the decoding for your encoded secret values Default is none Note: Available for Environment variables as well |
akeyless/inject_file: "/mysecret|jq={jq-expresion}" | jq expression to work with conventional JSON data form | jq={jq-expresion} e.g. secret items that contain JSON structure, can be parsed directly |
akeyless/inject_folder: "/prod/my-secrets-folder/|permission=0644" | permission=0644 | Set the permission of the folder that contains your secret value Default is 0644 Note: Available for files only |
akeyless/inject_folder: "/prod/my-secrets-folder/|location=/tmp/secrets/|track-folder-changes=true" | track-folder-changes= true or false | Track injected folder changes to sync new secrets |
akeyless/volume: "<Volume Name>" | Volume name | To work with a custom volume. MountPath should also be set in the prefix of the injected secret location. Volume required permissions: read/write |
akeyless/post_inject_script: | #!/bin/bash echo Hello > /akeyless/secrets/hello.txt | script to execute post-fetching the secret | Note: the execution occurs in the init container and at the sidecar container if set. |
akeyless/crash_on_error: "true" | Crash the pod on injection failures | Can be controlled globally for all deployments, or explicitly. |
akeyless/registry_creds | Path to a secret for Docker registry creds, relevant for environment variable mode | Can be used to override the entrypoint automatically |
akeyless/agent_limits_cpu | Int followed by m units | Limit of CPU usage e.g. 600m where the unit suffix m stands for core thousandth (miliCPU) |
akeyless/agent_requests_cpu | Int followed by m units | Limit of CPU request e.g. 250m where the unit suffix m stands for core thousandth (miliCPU) |
akeyless/agent_limits_mem | Int followed by Mi units | Limit of Memory usage, e.g. 64Mi |
akeyless/agent_requests_mem | Int followed by Mi units | Limit of Memory request usage, e.g. 64Mi |
Troubleshooting
When you are working with a GKE cluster, make sure that port 8443 is opened in your firewall rules, This port is needed by the Akeyless Secret Injection mutating webhook. Update your firewall rule as follows:
- Review the firewall rule for access:
gcloud compute firewall-rules list
- Replace the existing rule and allow access:
gcloud compute firewall-rules update <firewall-rule-name> --allow tcp:10250,tcp:443,tcp:8443
Tutorial
Check out our tutorial video on Injecting Secrets into a Kubernetes Cluster.
Updated 24 days ago