Live in harmony with Kubernetes Secrets using Azure Key Vault
Introduction
If you have worked with Kubernetes you probably know that Secrets are not really secrets. They are just base64 encoded key value pairs stored in etcd. Anyone with access to the cluster will have access to these secrets by running a simple command
kubectl get secrets --all-namespaces
kubectl get secrets/database -n my-namespace -t={{.data.password-key}} | base64 -D
In order to safely store these secrets you can enable encryption at rest or configure role based access control(RBAC). This article won't cover these two alternatives as we'll rather look at a helm chart that will injecting secrets stored in Azure Key Vault as environment variables to a pod running Grafana. That being said, this approach can be used for other scenarios where you'll need secrets from Azure Key vault.
Prerequisites
To follow along you'll need to have the following up and running:
- Helm installed
- An AKS Cluster
- An Azure Key Vault instance in the same subscription
- A User Assigned Managed Identity with the role Key Vault Secrets Officer to the aforementioned Key Vault. This Managed Identity also needs to be associated with the AKS Cluster.
Configure Kubernetes Cluster
We are using a Helm Chart named akv2k8s to get secrets from Azure Key Vault. Once this chart is installed, two pods will run in our cluster fetching and monitoring changes to specific secrets. This chart will only monitor changes in namespaces with the label
azure-key-vault-env-injection: enabled
To label your namespace in an idempotent way, you can run the following command
kubectl create namespace grafana --dry-run=client -o json|jq '.metadata += {"labels":{"azure-key-vault-env-injection":"enabled"}}' | kubectl apply -f -
Now we'll need to add a reference to our Key Vault.
apiVersion: spv.no/v2beta1
kind: AzureKeyVaultSecret
metadata:
name: azure-keyvault-secret-inject
namespace: grafana
spec:
vault:
name: someKeyVaultName # name of key vault
object:
name: someKeyVaultName # name of the akv object
type: secret # akv object type
Apply the changes to our cluster:
kubectl apply -f <name-of-file-above-yml>
Now that we have labeled our namespace and created a reference to a secret in our Azure Key Vault, lets install the Helm Charts!
Installing Helm Charts
Helm is a package managing tool for Kubernets and packages are called Charts. We'll use two Helm Charts to achieve our goal by running Grafana with secrets injected as environment variables to the pod.
Lets start by adding these two charts:
helm repo add grafana https://grafana.github.io/helm-charts
helm repo add spv-charts https://charts.spvapi.no
helm repo update
Lets install these charts
helm upgrade --install akv2k8s spv-charts/akv2k8s --namespace akv2k8s
helm upgrade --install --namespace grafana grafana grafana/grafana
You should now have three pods running. Verify that by running the following commands:
kubectl get pods -n grafana
kubectl get pods -n akv2k8s
Should result in:
- grafana-xyz
- akv2k8s-controller-xyz
- akv2k8s-envinjector-xyz
Configuring Grafana
Lets take a step back and configure Grafana with the values we configured in AzureKeyVaultSecret.
First we'll need to delete Grafana Helm Chart.
helm delete grafana -n grafana
Now we need to customize Grafana by adding the -f option. This option will allow us to pass in a yml file with custom values. We will be using the template found in Grafana's GitHub Repository.
To achieve reading values from Azure Key Vault, we'll be setting the default administrator password for Grafana by passing it in as an environment variable. Find the section for environment variables in the yml file and add the following:
env:
GF_SECURITY_ADMIN_USER: "admin"
GF_SECURITY_ADMIN_PASSWORD: "azure-keyvault-secret-inject@azurekeyvault"
GF_SECURITY_ADMIN_PASSWORD will now refer to the name we specified for AzureKeyVaultSecret.
This is unfortunetly not enough as Grafana won't set this environment variable to the administrator password. We'll need to run a command when the pod has started to set a new password.
Under the specificed environment variables add the following:
command:
- sh
- -c
- grafana-cli admin reset-admin-password ${GF_SECURITY_ADMIN_PASSWORD} && /run.sh
This is a workaround, and it seems that wont be fixed in the near future according to this GitHub Issue.
Depending on your configuring, like Ingress, Persist Volum Claims etc, your values.yml file should look something like this:
env:
GF_SECURITY_ADMIN_USER: "admin"
GF_SECURITY_ADMIN_PASSWORD: "azure-keyvault-secret-inject@azurekeyvault"
command:
- sh
- -c
- grafana-cli admin reset-admin-password ${GF_SECURITY_ADMIN_PASSWORD} && /run.sh
Lets install Grafanas Helm Chart once more with these values:
helm upgrade --install --namespace grafana grafana grafana/grafana -f values.yml
You should now see an instance of Grafana running in your cluster. Lets inspect the pod by running
kubectl describe pod <name-of-pod> -n grafana
Scroll all the way up to Environment and here you should see your secret being injected:
Now you should be able to access grafana depending on your configuration, by port forwarding or via the ingress, with the password stored in Azure Key Vault.
If you face any issues I would recommend looking at the events section when you describe the pod, or alternatively get the logs by running
kubectl logs <pod-name> -n grafana
Hope you found the post useful, and thank you for reading!