Vault on Kubernetes

Hashicorp Vault

I have spent quite a bit of time over the past 6 months setting up Hashicorp Vault at work. Its been a fun experience, and I have learned a lot. I’m always a fan of running whatever I run at work in my homelab, and Vault has really proven to be a useful tool. Recently, I’ve been playing around with Kubernetes again, and figured Vault would be a great place to store my Kubernetes secrets. In this post, I’ll try to outline how I set up Vault on my cluster, and how I use it with other apps on the cluster.


Prerequisites

  • I opted to use AWS KMS to unseal the Vault, this requires creating a KMS key on AWS first. You don’t need to do this if you’re fine manually unsealing the vault. To me this is worth the cost of slightly less than a dollar a month. I’m not going to get into the weeds about how to create a KMS key. You basically just need the key, and an IAM role and credentials. There are plenty of good guides out there like this one.

  • A cluster you have control over (obviously). I’m running a 5 node k3s cluster, I used this repo as a jumping off point to deploy it with Ansible.

  • Helm installed on your machine that can control your cluster.


Helm Chart

Now that we have a cluster and an (optional) KMS key, we can get to deploying the Valut. I opted to use the Helm chart provided by Hashicorp, this is by far the easiest way I have come across to install apps in Kubernetes, and seems to be the industry favorite.

The fist step was to install the Helm repo and update Helm’s repos

helm repo add hashicorp https://helm.releases.hashicorp.com && helm repo update

Now we need to create some Kubernetes secrets for our AWS KMS config. This may seem a bit confusing, as we are creating the Vault to store secrets in, but we don’t have that yet, so a Kubernetes secret will work for now.

First we need to base64 encode the secrets. I did this from WSL on my laptop, any Linux machine will do, and there is probably a way to do it on Windows and MacOS. To encode a string, use the following syntax (make sure to use the -n flag for echo, or it will include a newline character and really mess up your day) echo -n <yourStringHere> | base64 Encode each of the values in the yaml snippet shown below, and create a file like it named secret.yaml

apiVersion: v1
kind: Secret
metadata:
  namespace: vault
  name: vault
type: Opaque
data:
  AWS_REGION: <base64 here>
  AWS_SECRET_ACCESS_KEY: <base64 here>
  AWS_ACCESS_KEY_ID: <base64 here>
  VAULT_AWSKMS_SEAL_KEY_ID: <base64 here>

With that out of the way, we can create our values.yaml. I chose to do an HA setup with raft storage, but you can use standalone if you don’t want the extra overhead. You can find a good overview of what to use in your values.yaml on Hashicorp’s Website

global:
  enabled: true
server:
  extraSecretEnvironmentVars:
    - envName: AWS_REGION
      secretName: vault
      secretKey: AWS_REGION
    - envName: AWS_ACCESS_KEY_ID
      secretName: vault
      secretKey: AWS_ACCESS_KEY_ID
    - envName: AWS_SECRET_ACCESS_KEY
      secretName: vault
      secretKey: AWS_SECRET_ACCESS_KEY
    - envName: VAULT_AWSKMS_SEAL_KEY_ID
      secretName: vault
      secretKey: VAULT_AWSKMS_SEAL_KEY_ID
  dev:
    enabled: false
  standalone:
    enabled: false
  ha:
    enabled: true
    config: |
      ui = true

      listener "tcp" {
        tls_disable = 1
        address = "[::]:8200"
        cluster_address = "[::]:8201"
      }
      seal "awskms" {}
      service_registration "kubernetes" {}
      storage "raft" {
        path = "/vault/data"
      }      
    raft:
      enabled: true
      setNodeId: true
      config: |
        ui = true

        listener "tcp" {
          tls_disable = 1
          address = "[::]:8200"
          cluster_address = "[::]:8201"
        }
        seal "awskms" {}
        service_registration "kubernetes" {}
        storage "raft" {
          path = "/vault/data"
        }        

Now we can install it with helm upgrade --install vault hashicorp/vault -f values.yaml -n vault

Once the containers are created (check with kubectl -n vault get pods), exec into the first node and init the cluster kubectl -n vault exec -ti vault-0 -- vault operator init

Join the second node kubectl -n vault exec -ti vault-1 -- vault operator raft join http://vault-0.vault-internal:8200

And the third node kubectl -n vault exec -ti vault-2 -- vault operator raft join http://vault-0.vault-internal:8200

You should now have a running 3 node Vault cluster in Kubernetes. Nice work! It still needs a TLS cert and ingress created to access the Vault. For my certs, I use CertManager, and for ingress, I use Traefik. I won’t get into the weeds about how those are set up in this post, but I will leave some yaml snippets of them below. Happy Vaulting!

Certificate

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: mischaf-us-prod
  namespace: vault
spec:
  secretName: mischaf-us-production-tls
  issuerRef:
    name: letsencrypt-production
    kind: ClusterIssuer
  commonName: "vault.mischaf.us"
  dnsNames:
  - "vault.mischaf.us"

Ingressroute

apiVersion: traefik.containo.us/v1alpha1
kind: IngressRoute
metadata:
  name: vault-gui
  namespace: vault
  annotations:
    kubernetes.io/ingress.class: traefik-external
spec:
  entryPoints:
    - websecure
  routes:
  - match: Host(`vault.mischaf.us`)
    kind: Rule
    services:
    - name: vault-active
      port: 8200
      namespace: vault
  tls:
    secretName: mischaf-us-production-tls