Tanzu with HashiCorp Vault Part 2

Introduction

This is Part 2 of my multi series blog post. In this post, I’m going to show how to create a static secret in Vault and bring it into Kubernetes, utilizing the Vault Secret Operator.

  • Part One provided an overview of HashiCorp Vault, its terminology, and instructions on how to install it in Kubernetes using HELM
  • Part Two will focus on the HashiCorp Vault Secret Operator. I will demonstrate how to create a static secret in Vault and bring it into Kubernetes
  • Part three will cover Vault being used as a Public Key Infrastructure (PKI) as Intermediate CA, providing certificates in Kubernetes using cert-manager with the Vault plugin

How it works in general

Before we dive into the actual HowTo, let’s do a little theory session first.

The picture above shows the general workflow and the main components involved into it.

The Vault-part

Let’s zoom into the vault part first.

From left to right, the first thing we are going to create is an authentication method from type Kubernetes (1). In there, we will create a role (2) which contains the actual K8s Service information (3) and a policy reference (4).
The policy itself (5) will define the access for that role (6).
Lastly we will create a secret engine of type kv (7) and create a static secret below it in the path demo/config (8).

The K8s-part

After installing the Vault Secret Operator (not shown in that picture), we will create a VaultConnection object (9). That object contains basic information about the Vault, we are using.
Next we will create a VaultAuth object (10), which contains information about the serviceAccount to be used (it has to match, whatever we configured in the Vault role).

Configuring Vault

The last post showed how to install Vault. The last step we performed, was unsealing the Vault. Now we are going to use it.
All the steps shown below could also be performed in the GUI. But its much faster done in the CLI. ALso, I’m jumping into the vault Pod itself to perform the commands, simple because its already there. You could also download the vault binary to you jump host.

Login to Vault

Jump into the Vault Pod, using the root Token from when we fist initialised the Vault.

❯ kubectl -n vault exec -it vault-0 -- /bin/sh

/ $ vault login
Token (will be hidden): 
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                hvs.xTu3ObTcYZDMGRDiAfq08nKc
token_accessor       IPdYNRrWdY50DroEJSQLBCFP
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]
/ $ 

Create Authentication Method and Role

We will beginn with creating a authentication method of type Kubernetes and a path of k8s-demo

/ $ vault auth enable -path k8s-demo kubernetes
Success! Enabled kubernetes auth method at: k8s-demo/

Next, we configure our Kubernetes Cluster API address as Kubernetes Host for this authentication method:

/ $ vault write auth/k8s-demo/config kubernetes_host="https://172.31.192.5:6443"
Success! Data written to: auth/k8s-demo/config

Finally (for authentication), configure a role and bind it to the Kubernetes ServiceAccount+Namespace and a Vault policy (both not existing yet):

/ $ vault write auth/k8s-demo/role/r-demo \
    bound_service_account_names=sa-demo \
    bound_service_account_namespaces=ns-demo \
    policies=p-demo
Success! Data written to: auth/k8s-demo/role/r-demo

Let’s double-check the role:

/ $ vault read auth/k8s-demo/role/r-demo
Key                                 Value
---                                 -----
alias_name_source                   serviceaccount_uid
bound_service_account_names         [sa-demo]
bound_service_account_namespaces    [ns-demo]
policies                            [p-demo]
token_bound_cidrs                   []
token_explicit_max_ttl              0s
token_max_ttl                       0s
token_no_default_policy             false
token_num_uses                      0
token_period                        0s
token_policies                      [p-demo]
token_ttl                           0s
token_type                          default

Create Secret Engine and Secret in Vault

With the authentication method in place, we can proceed and enable a secret engine. We are going to use a simple key-value secret, so we will use the kv secret engine (in version 2) called kv-secrets-demo.

/ $ vault secrets enable -path=kv-secrets-demo kv-v2
Success! Enabled the kv-v2 secrets engine at: kv-secrets-demo/

Next, we can put a secret into that engine. It’s a simple key value pair with username=admin and password=Password123!

/ $ vault kv put kv-secrets-demo/demo/config username="admin" password="Password123!"
========== Secret Path ==========
kv-secrets-demo/data/demo/config

======= Metadata =======
Key                Value
---                -----
created_time       2023-08-22T08:46:32.056062115Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

We can double-check the secret with the following command:

/ $ vault kv get kv-secrets-demo/demo/config
========== Secret Path ==========
kv-secrets-demo/data/demo/config

======= Metadata =======
Key                Value
---                -----
created_time       2023-08-22T08:46:32.056062115Z
custom_metadata    <nil>
deletion_time      n/a
destroyed          false
version            1

====== Data ======
Key         Value
---         -----
password    Password123!
username    admin

Create the policy

Now with both, secret engine and authentication method in place, we only need to glue them together by using a policy.

/ $ vault policy write p-demo - <<EOF
 path "kv-secrets-demo/*" {
    capabilities = ["read"]
 }
EOF
Success! Uploaded policy: p-demo

This policy basically allows read access to the path kv-secrets-demo and all its subdirectories.
Since we referenced this policy already during the role creation earlier, we are now done with the vault part and can move on to Kubernetes.

Installing Vault Secret Operator

Again, we will use the official HELM Chart to install the Secret Operator.

❯ helm repo list
NAME            URL                                               
hashicorp       https://helm.releases.hashicorp.com               

❯ helm search repo hashicorp/vault-secrets-operator
NAME                                    CHART VERSION   APP VERSION     DESCRIPTION                          
hashicorp/vault-secrets-operator        0.1.0           0.1.0           Official Vault Secrets Operator Chart

Installing the HELM Chart is pretty straight forward. We are not going to configure a default connection as part of the installation, so we pretty much run the installer with default values:

❯ helm install vault-secrets-operator hashicorp/vault-secrets-operator --version 0.1.0 -n vault-secrets-operator-system --create-namespace

NAME: vault-secrets-operator
LAST DEPLOYED: Tue Aug 22 09:09:58 2023
NAMESPACE: vault-secrets-operator-system
STATUS: deployed
REVISION: 1

Let’s check the status

❯ kubectl -n vault-secrets-operator-system get pods
NAME                                                         READY   STATUS    RESTARTS   AGE
vault-secrets-operator-controller-manager-6c4776b984-wqp4q   2/2     Running   0          36s
 
❯ kubectl get crd | grep -i vault
vaultauths.secrets.hashicorp.com                         2023-08-22T14:24:49Z
vaultconnections.secrets.hashicorp.com                   2023-08-22T14:24:49Z
vaultdynamicsecrets.secrets.hashicorp.com                2023-08-22T14:24:49Z
vaultpkisecrets.secrets.hashicorp.com                    2023-08-22T14:24:49Z
vaultstaticsecrets.secrets.hashicorp.com                 2023-08-22T14:24:49Z

As we can see, the Pod is Ready and new CRDs are installed.

Configure Authentication towards Vault

Authentication is namespace based. That means we need to create the namespace (ns-demo), the serviceAccount (sa-demo) and the vault objects VaultConnection (vault-con-demo) and VaultAuth (vault-auth-demo).

Create the Namespace and ServiceAccount

❯ kubectl create namespace ns-demo
namespace/ns-demo created

❯ kubectl -n ns-demo create serviceaccount sa-demo
serviceaccount/sa-demo created

❯ kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: token-sa-demo
  namespace: ns-demo
  annotations:
    kubernetes.io/service-account.name: sa-demo
type: kubernetes.io/service-account-token
EOF
secret/token-sa-demo created

Create the VaultConnection and VaultAuth objects

First, we are going to create the VaultConnection object. That basically specifies which Vault to be used.
Note – We are going to use the K8s internal Vaul address, instead of going through the LoadBalancer or Ingress Object. The later would require additonal configuration, since Vault is not listening (thus not serving) any requests on those IPs.
Its the Vault Service, we are aiming for:

❯ kubectl -n vault get svc
NAME                       TYPE        CLUSTER-IP        EXTERNAL-IP   PORT(S)             AGE
vault                      ClusterIP   192.168.225.225   <none>        8200/TCP,8201/TCP   19h
vault-agent-injector-svc   ClusterIP   192.168.235.156   <none>        443/TCP             19h
vault-internal             ClusterIP   None              <none>        8200/TCP,8201/TCP   19h
vault-ui                   ClusterIP   192.168.233.230   <none>        8200/TCP            19h

This means, our endpoint URL is http://vault.vault.svc.cluster.local:8200

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  namespace: ns-demo
  name: vault-con-demo
spec:
  address: http://vault.vault.svc.cluster.local:8200
  skipTLSVerify: true

Now simply create it:

❯ kubectl create -f VaultConnection.yaml
vaultconnection.secrets.hashicorp.com/vault-con-demo created

Before we move on, let’s check if the connection is working:

❯ kubeclt -n ns-demo get vaultconnections.secrets.hashicorp.com vault-con-demo -o yaml

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultConnection
metadata:
  creationTimestamp: "2023-08-23T09:47:00Z"
  finalizers:
  - vaultconnection.secrets.hashicorp.com/finalizer
  generation: 1
  name: vault-con-demo
  namespace: ns-demo
  resourceVersion: "430752"
  uid: 05a8611c-6ee8-410a-bc80-f3ac9712f834
spec:
  address: http://vault.vault.svc.cluster.local:8200
  skipTLSVerify: true
status:
  valid: true

The very last line shows the status which is valid=true. That means, the general connection is working.
Let’s move on to the authentication.
The VaultAuth object is now glueing it all together:

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: vault-auth-demo
  namespace: ns-demo
spec:
  kubernetes:
    role: r-demo
    serviceAccount: sa-demo
  method: kubernetes
  mount: k8s-demo
  vaultConnectionRef: vault-con-demo

Wen can see, that the it is refering to the previous created vault-con-demo oject (line 7).
Its utilising the the authentication method kubernetes (line 8) at mount path k8s-demo (line 9). It’s using the configure role r-demo (line 11) and its authenticating itself as serviceAccount sa-demo (line 12).
This of course has to match everything we configured in Vault before!

❯ kubectl create -f demos/Notes/vault/vaultAuth.yaml
vaultauth.secrets.hashicorp.com/vault-auth-demo created

Let’s also check it’s status:

❯ kubectl -n ns-demo get vaultauths.secrets.hashicorp.com vault-auth-demo -o yaml

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  creationTimestamp: "2023-08-23T09:54:53Z"
  finalizers:
  - vaultauth.secrets.hashicorp.com/finalizer
  generation: 1
  name: vault-auth-demo
  namespace: ns-demo
  resourceVersion: "432740"
  uid: 1effdb02-f60f-48f1-80f4-f93373fb9476
spec:
  kubernetes:
    role: r-demo
    serviceAccount: sa-demo
    tokenExpirationSeconds: 600
  method: kubernetes
  mount: k8s-demo
  vaultConnectionRef: vault-con-demo
status:
  error: ""
  valid: true

Again, the very last line show the status valid=true, which is obiously good 🙂

Creating the static Secret in Kubernets

This and the last post have been leading to this very moment – we now want to make the secret created in Vault available in Kubernetes. We are doing this by creating a staticSecret object in Kubernetes.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: vault-secret-demo
  namespace: ns-demo
spec:
  destination:
    create: true
    name: k8s-secret-demo
  hmacSecretData: true
  mount: kv-secrets-demo
  path: demo/config
  refreshAfter: 30s
  type: kv-v2
  vaultAuthRef: vault-auth-demo

If everything works as expected, this object will cause a query to the actual secret data in Vault and create a Kubernetes Secret (called “k8s-secret-demo”) with its information.

❯ kubectl -n ns-demo get secret k8s-secret-demo -o yaml
apiVersion: v1
data:
  _raw: eyJkYXRhIjp7InBhc3N3b3JkIjoiUGFzc3dvcmQxMjMhIiwidXNlcm5hbWUiOiJhZG1pbiJ9LCJtZXRhZGF0YSI6eyJjcmVhdGVkX3RpbWUiOiIyMDIzLTA3LTE0VDIwOjMyOjA4LjI5NjQ2MzExNVoiLCJjdXN0b21fbWV0YWRhdGEiOm51bGwsImRlbGV0aW9uX3RpbWUiOiIwMDAxLTAxLTAxVDAwOjAwOjAwWiIsImRlc3Ryb3llZCI6ZmFsc2UsInZlcnNpb24iOjF9fQ==
  password: UGFzc3dvcmQxMjMh
  username: YWRtaW4=
kind: Secret
metadata:
  creationTimestamp: "2023-08-23T20:37:37Z"
  labels:
    app.kubernetes.io/component: secret-sync
    app.kubernetes.io/managed-by: hashicorp-vso
    app.kubernetes.io/name: vault-secrets-operator
    secrets.hashicorp.com/vso-ownerRefUID: 6dc10987-d978-4af3-b4e4-accf398c1ba8
  name: k8s-secret-demo
  namespace: ns-demo
  ownerReferences:
  - apiVersion: secrets.hashicorp.com/v1beta1
    kind: VaultStaticSecret
    name: vault-secret-demo
    uid: 6dc10987-d978-4af3-b4e4-accf398c1ba8
  resourceVersion: "6391"
  uid: 50b99a00-1eab-44a6-a04a-dacfd0acb84b
type: Opaque

If the secret hasn’t been created after a minutes or so, check the secret operator pod logs for some hints.
The secret can now be be used like any other secret in K8s.

Next

Coming next – How to create a PKI using Vault.

Leave a Reply

Your email address will not be published. Required fields are marked *