This package contains any CRDs and webhooks GoCardless use to interact with Vault. At present, the only thing we provide is a webhook to automatically configure Kubernetes pods with access to Vault secrets.
GoCardless use Vault to manage application environment variable secrets. We happen to build software in a variety of languages, and make use of process environment variables as a language agnostic method of injecting configuration material.
We needed a way to inject secrets from Vault into application environment variables without requiring developers to write code to speak directly to Vault. Besides the effort involved in writing that interaction, and the risk involved in doing it incorrectly, there's no guarantee each language would be well supported by the Vault ecosystem.
Instead, we've built a webhook that listens for pods with an annotations like:
secrets-injector.vault.crd.gocardless.com/configs: app:config/env.yaml
When our webhook sees this annotation, it tries configuring the app
container
to pull secrets from Vault, using the configuration from the config/env.yaml
file within the container. It resolves these secrets and sets them as
environment variables, finally running the original container process.
The webhook makes use of the theatre-secrets
command to
perform an authentication dance with Vault. Once we've acquired a Vault token,
we translate our simple configuration file format into a Hashicorp
[secrets][secrets] config file, then use secrets to perform the fetching
and lease-management of the secret values.
For this authentication flow to work, we expect Vault to be configured with a Kubernetes authentication backend that points at the Kubernets API server. The authentication exchange works as follows:
- Pods attempt a login with Vault using their service account token
- Vault receives a service account token, and uses this token to perform a token review request against the API server configured on this auth backend. If the request succeeds, we know the token is valid, and we permit the login
The theatre-secrets acceptance tests verify this flow against a Vault server. If anything is unclear, look at the Prepare method for how we configure the test Vault server.
Once installed, the webhook will listen for containers with a specific annotation:
---
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
"secrets-injector.vault.crd.gocardless.com/configs": "app"
"envconsul-injector.vault.crd.gocardless.com/configs": "app"
spec:
containers:
- name: app
command:
- env
Currently we still support the now deprecated envconsul-injector annotation as well. The two should not be used together.
---
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
"envconsul-injector.vault.crd.gocardless.com/configs": "app"
spec:
containers:
- name: app
command:
- env
We will modify this pod to do the following...
Add an init container that installs the theatre-secrets
tool into a temporary
installation path volume. We use a default storage medium. This installation
volume will be mounted into any of the containers that are targeted by the
secrets-injector.vault.crd.gocardless.com/configs
annotation. In our example,
this means the app
container is the only target.
---
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
"secrets-injector.vault.crd.gocardless.com/configs": "app"
spec:
initContainers:
- name: theatre-secrets-injector
image: theatre:latest
imagePullPolicy: IfNotPresent
command:
- theatre-secrets
- install
- --path
- /var/run/theatre
volumeMounts:
- mountPath: /var/run/theatre
name: theatre-secrets-install
containers:
- name: app
command:
- env
volumes:
- name: theatre-secrets-install
emptyDir: {}
Instead of relying on default Kubernetes service account volume mounts, we make use of the projected service account volume mounts. Unlike default tokens, these are managed by the kubelet and automatically rotated at regular intervals, and whenever the pod is destroyed.
We mount the emphemeral token at /var/run/secrets/kubernetes.io/vault
. In
terms of additional pod configuration, this means we add a projected volume and
volume-mount to any targeted containers:
---
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
"secrets-injector.vault.crd.gocardless.com/configs": "app"
spec:
initContainers: ...
containers:
- name: app
command:
- env
volumeMounts:
- name: theatre-secrets-serviceaccount
mountPath: /var/run/secrets/kubernetes.io/vault
volumes:
- name: theatre-secrets-install
emptyDir: {}
- name: theatre-secrets-serviceaccount
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 900
The container must resolve secrets before we run the original command. We use
theatre-secrets to perform the resolution, then exec the original container
command. The application container has access to the theatre binaries via the
init container, having installed them in the theatre-secrets-install
volume:
---
apiVersion: v1
kind: Pod
metadata:
name: app
annotations:
"secrets-injector.vault.crd.gocardless.com/configs": "app"
spec:
initContainers: ...
containers:
- name: app
command:
- /var/run/theatre/theatre-secrets
args:
- exec
- --vault-address=http://vault.vault.svc.cluster.local:8200
- --vault-path-prefix=secret/data/kubernetes/project/namespace/app
- --install-path=/var/run/theatre
- --service-account-token-file=/var/run/secrets/kubernetes.io/vault/token
- --
- env
volumeMounts:
- name: theatre-secrets-install
mountPath: /var/run/theatre
- name: theatre-secrets-serviceaccount
mountPath: /var/run/secrets/kubernetes.io/vault
volumes:
- name: theatre-secrets-install
emptyDir: {}
- name: theatre-secrets-serviceaccount
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 900