SaFi Bank Space : Hashicorp Vault Secret Injection

Content Sections

  1. Procedure

  2. Deployment

Ways to consume vault secret value

There are multiple ways to consume secret from vault, but due to disadvantage of other methods, 2 solutions will only be topic here which are:

  1. Vault Aware - Writing an application to connect directly to a Vault server using Vault REST APIs, needs to include vault client libraries. (Best and current solution we are using) https://www.vaultproject.io/api-docs/libraries

  2. Vault Injection - No changes from the application side, annotation from the deployment will inject sidecar pod that will communicate with vault server. (Backup solution for easy configuration)

Main components for Vault Injection

  1. Kubernetes Authentication - Need to enable kube authentication to vault

  2. Service Account binded to Role and Policy- Create a vault role and policy, then integrate it with kube service account.

Procedure

For demo, install vault in dev mode with helm

helm upgrade --install \
    vault hashicorp/vault \
    --namespace vault \
    --create-namespace \
    --set "server.dev.enabled=true" \
    --set "server.dev.devRootToken=root" 
    
kubectl -n vault get po
NAME                                    READY   STATUS    RESTARTS   AGE
vault-0                                 1/1     Running   0          4h33m
vault-agent-injector-5b5889ffb4-gtnbj   1/1     Running   0          4h33m

3 things to enable kube auth in vault

1. token_reviewer_jwt - tokent issued by service account in the namescpace, ssh into any pod and run cat /var/run/secrets/kubernetes.io/serviceaccount/token
2. kubernetes_host - ${KUBERNETES_PORT_443_TCP_ADDR} value
3. kubernetes_ca_cert ssh into any pod and run cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt to check the value

kubectl -n vault exec -it vault-0 -- sh 

vault login # password token "root"

vault auth enable kubernetes

vault write auth/kubernetes/config \
token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \
kubernetes_host=https://${KUBERNETES_PORT_443_TCP_ADDR}:443 \
kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \
issuer="https://kubernetes.default.svc.cluster.local"

Create a sample secret

kubectl -n vault exec -it vault-0 -- sh 

vault secrets enable -path=secret/ -version=1 kv

#create helloworld secret
vault kv put secret/basic-secret/helloworld username=dbuser password=sUp3rS3cUr3P@ssw0rd

Create a Policy that will be used by the role

kubectl -n vault exec -it vault-0 -- sh 

cat <<EOF > /home/vault/app-policy.hcl
path "secret/basic-secret/*" {
  capabilities = ["read"]
}
EOF
vault policy write basic-secret-policy /home/vault/app-policy.hcl

Create Vault role that will used the Policy

kubectl -n vault exec -it vault-0 -- sh 

#This role is bounded by basic-secret-policy, basic-secret serviceaccount, and foo namespace.
vault write auth/kubernetes/role/basic-secret-role \
   bound_service_account_names=basic-secret \
   bound_service_account_namespaces=foo \
   policies=basic-secret-policy \
   ttl=1h

Deployment

Deploy Sample app that will consume the secret

Add deployment annotations to automatically inject sidecar pod and consume secret
https://www.vaultproject.io/docs/platform/k8s/injector/annotations

annotations:
  vault.hashicorp.com/agent-inject: "true"      # enable sidecar pod inject
  vault.hashicorp.com/tls-skip-verify: "true"   # skip ssl
  vault.hashicorp.com/agent-inject-secret-${SECRET_NAME}: "secret/${SECRET_NAME}" # refer the secret by replacing the varialbe ${SECRET_NAME}
  vault.hashicorp.com/role: "basic-secret-role" # vault role to use

NOTE: Kubernetes service account should match what is configured in the role properties bound_service_account_names and bound_service_account_namespaces.

Deployment sample

kubectl create ns foo

cat <<EOF > deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: basic-secret
  labels:
    app: basic-secret
spec:
  selector:
    matchLabels:
      app: basic-secret
  replicas: 1
  template:
    metadata:
      annotations:
        vault.hashicorp.com/agent-inject: "true"
        vault.hashicorp.com/tls-skip-verify: "true"
        vault.hashicorp.com/agent-inject-secret-helloworld: "secret/basic-secret/helloworld"
        vault.hashicorp.com/role: "basic-secret-role"
      labels:
        app: basic-secret
    spec:
      serviceAccountName: basic-secret
      containers:
      - name: app
        image: jweissig/app:0.0.1
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: basic-secret
  labels:
    app: basic-secret
EOF

kubectl -n foo apply -f deployment.yaml

Verification

Once pod is in ready state, you can check the values by running

kubectl -n example-app exec <pod-name> -- sh -c "cat /vault/secrets/helloworld"

Defaulted container "app" out of: app, vault-agent, vault-agent-init (init)
password: sUp3rS3cUr3P@ssw0rd
username: dbuser