Skip to content

Latest commit

 

History

History
215 lines (187 loc) · 6.55 KB

README.md

File metadata and controls

215 lines (187 loc) · 6.55 KB

vault

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.

Summary

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.

Configuring Vault

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.

How does the webhook work

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...

1. Install binaries

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: {}

2. Add service account volume

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

3. Prepend theatre-secrets inject

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