Dynamic credentials for Kubernetes workloads with Vault and VSO
The Vault Secrets Operator (VSO) is a Kubernetes Operator that allows teams to centrally manage secrets in Vault and synchronize them with native Kubernetes Secrets.
Scenario
HashiCups, the world's finest coffee roaster, has recently acquired International Brewing Machines (IBM) who makes smart coffee roasters. The teams at HashiCups and IBM need to make the app compliant with HashiCups security standards.
HashiCups recently implemented HashiCorp Vault as part of their security lifecycle management processes. HashiCups requires applications to use short-lived, time bound credentials to limit potential exposure of credentials.
The application is a Kubernetes native application which consumes Kubernetes native secrets. These secrets are manually provisioned, and do not expire.
Alice from the HashiCups architect team meets with Watson from the IBM development team to figure out how they can support the app.
Challenge
The app runs on Kubernetes and retrieves secrets directly from manually provisioned Kubernetes Secrets. The Kubernetes Secret has a username and password for the app to connect to a PostgreSQL database.
The team at HashiCups has the following requirements:
- Replace static, long lived credentials with short-lived, time bound credentials credentials.
- Allow teams to securely store API keys, passwords, certificates, etc.
- Onboard the app quickly, as the teams do not have time to refactor the app.
Solution
To meet the requirement of on-boarding the app quickly, Alice proposes using the Vault Secrets Operator. Alice and Watson decide to configure a static secret in the Vault K/V v2 secrets engine as a proof-of-concept for deploying the VSO. After a successful test with static secrets, they will configure Vault to generate dynamic credentials for PostgreSQL and configure VSO to work with dynamic secrets.
What is the Vault Secrets Operator
Vault provides a complete solution for modern secrets management. It consolidates identity brokering, secrets management, dynamic secrets, rotation, and security policy compliance in one platform. By coupling Vault’s secret management feature set with a Kubernetes Secrets, developers do not have to refactor their applications and can continue to reference Kubernetes Secrets while security and platform teams can manage the lifecycle of secrets through Vault.
How the Vault Secrets Operator works
As a controller, VSO reconciles the current state of secrets defined in the cluster to the desired state specified by custom resources using standard Kubernetes declarative patterns.
You deploy The Vault Secrets Operator using Helm or Kustomize.
The Vault Secrets Operator requires the following Kubernetes permissions:
Object | Permission | Reason |
---|---|---|
Secret | create, read, update, delete, watch | Sync operations, Vault auth |
ServiceAccount | read, token creation | Vault auth |
Deployment | read, update, watch | Post secret rotation actions |
Prerequisites
This lab was tested on macOS 14.6.1 and Windows 10 with PowerShell Core 7.4.5.
- Vault binary installed and configured in your system PATH.
- Vault Enterprise license
- Docker installed and running
- minikube
- kubectl
- Helm
- ngrok installed and configured with an auth token
- jq
- base64
- k9s CLI used for lab support and troubleshooting
Set up the lab
To complete this lab, you will deploy the following:
Kubernetes cluster using minikube.
Vault cluster external to the Kubernetes cluster configured to authenticate with the Kubernetes API.
PostgreSQL with Vault configured to create on-demand PostgreSQL roles.
If you are unable to install the required tools to complete this lab, you can follow this embeded lab for a similar experience.
Launch Terminal
This tutorial includes a free interactive command-line lab that lets you follow along on actual cloud infrastructure.
Open a terminal and create a working directory to store files created in the lab.
Change to the the lab directory.
Leave this terminal open for the remainder of the lab.
Start and configure Kubernetes
You will use minikube, a CLI tool that provisions and manages the lifecycle of single-node Kubernetes cluster, to set up a Kubernetes cluster on your system.
Start a Kubernetes cluster.
The initialization process takes several minutes as it retrieves any necessary dependencies and executes various container images.
Verify the status of the Kubernetes cluster.
Open a new terminal and start a proxy to expose the Kubernetes API.
This command exposes the Kubernetes API to your local machine. This terminal must remain open during the lab.
Open another new terminal, and create a tunnel to the proxy listening on port
8001
.Warning
ngrok is used to mimic connectivity of complex network configurations to expose the Kubernetes API. Using
--scheme=http
exposes the API without encryption to avoid TLS certificate errors.For production workloads, connect to a trusted network with valid TLS certificates.
Example output:
This terminal must remain open during the lab.
ngrok is used for simplicity in exposing the Kubernetes API from minikube which is meant to serve as a local environment. In a production Kubernetes cluster, you might use a load balancer, or managed services such as AKS or EKS expose the API by default.
Copy the forwarding address and return to the terminal where you started minikube.
Export an environment variable for the Kubernetes API address using the ngrok forwarding address. This address will be unique to your lab.
The Kubernetes cluster is now accessible by an external Vault cluster.
Start Vault
Export an environment variable with a valid Vault Enterprise license.
This license expires at the end of today.
Start Vault Enterprise in a container. Vault will operate outside the Kubernetes cluster and provides a similar experience to using HCP Vault Dedicated or HCP Vault Secrets with VSO.
Example output:
The Vault dev server listens on all addresses using port
8200
. The server is initialized and unsealed.Insecure operation
Do not run a Vault dev server in production. This approach starts a Vault server with an in-memory database and runs in an insecure way.
Verify the Vault Enterprise container is in a
STATUS
isUp
.1234
Export an environment variable for the
vault
CLI to address the Vault server.Export an environment variable for the
vault
CLI to authenticate with the Vault server.Note
For these tasks, you can use Vault's
root
token. However, it is recommended that root tokens are only used for enough initial setup or in emergencies. As a best practice, use an authentication method or token that meets the policy requirements.Verify Vault Enterprise is running and unsealed.
The Vault cluster is now ready.
Install the Vault Secrets Operator
Install and update the HashiCorp Helm repository.
Example output:
Install the Vault Secrets Operator in a new Kubernetes namespace.
Example output:
In addition to installing the Vault Secrets Operator controller, the installation includes the supported custom resource definitions used to create Kubernetes resources to support the Vault Secrets Operator.
Create a connection to Vault
VSO uses the VaultConnection as a reference to connect to the external Vault cluster.
Create a Kubernetes namespace for Vault resources.
A good practice is to use Kubernetes namespaces as a logical security boundary for different resources.
The
vault
Kubernetes namespace stores the connection to Vault, and a service account used by Vault to authenticate with the Kubernetes API.Connect to minikube and export an environment variable with the address for the external Vault container.
In a production environment, this would be the address of your Vault cluster accessible by the Kubernetes API.
Create a connection to Vault.
Example output:
Verify the configuration.
Create a service account for Vault
Create a Kubernetes service account named
vault-to-k8s-sa
with a service account token. Vault authenticates with the Kubernetes API using this token.Example output:
Create a Kubernetes role for the
vault-to-k8s-sa
service account to allow access to the Kubernetes API.Example output:
Retrieve the
vault-auth-secret
secret and store it as an environment variable.Example output:
The secret includes the Kubernetes public key
ca.crt
and thetoken
as base64 encoded strings.Decode the ca.crt certificate and store it as an environment variable.
Example output:
Decode the token and store it as an environment variable.
Example output:
You have collected the necessary information to configure the Vault Kubernetes auth method.
Enable the Kubernetes auth method
Enable the Kubernetes auth method.
Configure the Kubernetes auth method to connect to the Kubernetes API using the
vault-to-k8s-sa
service account token.Example output:
The Kubernetes auth method is configured and ready to proceed with the lab.
Enable KV secrets engine
Configure the KV secrets engine to verify the Vault Secrets Operator is working with a basic static secret. The KV secrets engine is useful for storing API keys, and credentials that can not be automatically generated.
Enable the KV secrets engine.
Alice and Watson use the KV secrets engine to validate the IBM app can get static secrets managed by Vault and synchronized to a Kubernetes secret.
Create a secret at path
static/exampleapp/creds
with ausername
andpassword
.Example output:
Create a Vault policy that permits read access to
static/exampleapp/creds
.Example output:
The KV secrets engine and policy is ready to proceed with the lab.
Sync static secrets
Before the Vault Secrets Operator can sync secrets from Vault to Kubernetes Secrets, it needs a Vault role to authenticate with. The policy attached to the role permits access to the KV secrets engine created in the previous section.
Create a Vault role and include the
exampleapp-kv-read
Vault policy for the Kubernetes service accountvso-static-exampleapp-sa
. This service account allows VSO to create secrets in the Kubernetes namespacestatic-exampleapp
.Example output:
The
bound_service_account_namespaces
parameter is a list of Kubernetes namespaces allowed to call the Vaultvault-role-static-exampleapp
role.The
bound_service_account_names
parameter is a list of Kubernetes service accounts allowed to call the Vaultvault-role-static-exampleapp
role.You will now create a Kubernetes namespace to deploy the app. This namespace will also include a Kubernetes service account. This service account must exist in the Kubernetes namespace where you want VSO to write the secret to.
Create a Kubernetes namespace to create the secret in and deploy the app.
Create a Kubernetes service account for VSO to use to create a secret in the
static-exampleapp
Kubernetes namespace.Example output:
Service accounts can perform all operations in the namespace in which it is created, such as creating secrets.
Configure authentication for the Vault Secrets Operator using the
vso-static-exampleapp-sa
Kubernetes service account.Example output:
Configure the Vault Secrets Operator to read from the
secret
KV v2 mount at theexampleapp/creds
path.Example output:
Verify the Vault secret is available as a native Kubernetes Secret.
VSO creates the secret
vso-static-creds-from-vault
and bases the name on thename
parameter provided in theVaultStaticSecret
configuration.Read the Kubernetes Secret value and decode the base64 encoded strings.
Applications can now use the Kubernetes Secret by injecting it through a volume mount or an environment variable.
Create a deployment for the app that reads the Kubernetes Secret and creates an environment variable in the pod.
The app references the Kubernetes Secret
vso-static-creds-from-vault
and injects an environment variable to the running pod.Deploy the
hashicups-exampleapp
using thedeployment.yaml
file.Verify the pod is in a
running
state. It may take a few minutes to download the image and start the pod.Access the applications
secret
endpoint to display the Kubernetes Secret created by VSO.Example output:
The app was able to read the Kubernetes Secret created by VSO. The deployment injected the secrets as environment variables, however you can also can retrieve secrets through the Vault Injector service via annotations, or secrets mounted on ephemeral volumes.
Delete the
exampleapp
deployment.This step is done only for lab purposes to conserve resources on your local machine.
Sync dynamic secrets
Alice and Watson have successfully tested the app with Vault and VSO. VSO creates the Kubernetes Secret from a Vault managed secret, and the app works without having to develop new features to read the Vault managed secret.
The Vault database secrets engine connects to a supported RDBMS and manages the creation and revocation of credentials. When a credentials TTL is reached, Vault revokes the credentials.
Alice and Watson will test Vault's database secrets engine, which creates dynamic, just-in-time credentials. These credentials are short lived, limiting potential exposure of the credentials.
Start and configure PostgreSQL
Start PostgreSQL.
Example output:
Connect to minikube and export an environment variable with the address for the PostgreSQL container.
In a production environment, this would be the address of your PostgreSQL server accessible by Vault.
Enable the database secrets engine.
Configure the database secrets engine to use the
postgresql-database-plugin
, and the PostgreSQLroot
credentials.Configure a Vault role named
vault-role-dynamic-exampleapp
that includes a SQL statement to create the PostgreSQL role.In production environments, a good practice is to include a valid revocation statement which are valid for the database you've configured. If you do not specify statements appropriate to creating, revoking, or rotating users, Vault inserts generic statements which can be unsuitable for your deployment.
Test the Vault role and generate PostgreSQL credentials.
Example output:
Vault uses the
creation_statements
from thevault-role-dynamic-exampleapp
Vault role to create a new PostgreSQLusername
andpassword
.
Create and sync dynamic secrets
With the database secrets engine configured Alice and Watson will configure VSO to create and sync the new credentials to a Kubernetes Secret.
Create a policy that allows access to the database secrets engine.
Example output:
Create a Vault role for the app with the
exampleapp-database-read
policy attached.Example output:
A good practice is to create separate Vault roles for each Kubernetes namespace.
Create a Kubernetes namespace to store the dynamic secrets and run the app.
A new Kubernetes namespace is used to show the steps specific to configuring VSO for dynamic secrets. Certain Kubernetes custom resources such as the
VaultConnection
resource can be shared across different namespaces.Create a Kubernetes service account for VSO to use to create a dynamic secret in the
dynamic-exampleapp
namespace.Example output:
Configure authentication for the Vault Secrets Operator using the Kubernetes
vso-static-exampleapp-sa
service account and thevault-role-dynamic-exampleapp
Vault role.Example output:
The connection resource
vaultConnectionRef
is reused from earlier in the lab.Configure the Vault Secrets Operator to read from the
database
mount using thevault-role-dynamic-exampleapp
role.Example output:
VSO will create a Kubernetes Secret named
vso-dynamic-creds-from-vault
using the secrets engine mounted at the pathdatabase
using thevault-role-dynamic-exampleapp
Vault role.The
rolloutRestartTargets
can be configured whenever the application(s) consuming the Vault secret does not support dynamically reloading a rotated secret. In that case one, or moreRolloutRestartTargets
can be configured. VSO will trigger a "rollout-restart" for each target whenever the Vault secret changes between reconciliation events.Verify the Vault database credentials are available as a native Kubernetes secret.
Example output:
Read the Kubernetes Secret value and decode the base64 encoded strings.
Create a deployment for the app that reads the Kubernetes Secret.
Deploy the
hashicups-exampleapp
using thedeployment.yaml
file.Verify the pod is in a
running
state.Access the applications
secret
endpoint to display the Kubernetes Secret created by VSO.Example output:
Access the applications
status
endpoint to verify the pod can connect to the PostgreSQL database.Example output:
The app connected to the PostgreSQL database using the dynamic secrets created by Vault, and synchronized to a Kubernetes Secret by VSO.
Set up persistent client cache
By default, the Vault client cache does not persist. This can lead to VSO not maintaining dynamic secret leases through restarts and upgrades of the Vault Secrets Operator.
The client cache enables seamless upgrades because Vault can track and renew tokens and dynamic secret leases through leadership changes.
Client cache persistence and encryption is not enabled by default because it requires extra configuration and Vault server setup.
Enable the transit secrets engine.
Create an encryption key.
Create a policy that allows VSO to access the transit secrets engine.
Example output:
Create a role in the Kubernetes auth method used by VSO to authenticate with Vault and attach the
vso-transit-policy
.Example output:
The
vault-secrets-operator-controller-manager
service account andvault-secrets-operator
are the defaults created when deploying VSO.Create a file to update the VSO Helm deployment.
Upgrade the VSO deployment to include the secrets cache.
Example output:
Check the
vault-secrets-operator
namespace for the encrypted client cache. Depending on your environment, this may take 30-60 seconds to update.
VSO stores the client cache as a Kubernetes Secret prefixed with vso-cc
in
the vault-secrets-operator
namespace.
In this example, there is a cache for both Kubernetes namespaces VSO is
managing secrets for - static-exampleapp
and dynamic-exampleapp
.
Set up instant updates with Vault Enterprise
Tip
The instant updates option requires Vault Enterprise 1.16.3+ due to the use of Vault event notifications.
There are times when secrets updated in Vault need to be immediately available to applications to avoid downtime. To ensure the timely update of secrets by the Vault Secrets Operator, you can use Vault event notifications.
Instant update supports the Vault KV v2 secrets engine, and the Kubernetes VaultStaticSecret resource type.
Update the Vault
exampleapp-kv-read
policy to include read access to events, as well aslist
andsubscribe
on theexampleapp-kv-read
policy.Example output:
Add
syncConfig.instantUpdates=true
to thevault-static-secret
resource in thestatic-exampleapp
namespace.Note
It is okay to proceed if you receive the following error message:
Warning: resource vaultstaticsecrets/vault-static-secret is missing the kubectl.kubernetes.io/last-applied-configuration annotation which is required by kubectl apply. kubectl apply should only be used on resources created declaratively by either kubectl create --save-config or kubectl apply. The missing annotation will be patched automatically.
Example output:
Verify the VSO configuration is now watching for events.
Update the
/static/exampleapp/creds
secret in Vault.Example output:
Verify the Kubernetes Secret values match the updated Vault secret value.
Updating the secret value triggered an event in Vault, which initiated updating the Kubernetes Secret.
Summary
In this lab, you deployed the Vault Secrets Operator and configured VSO to work with an external Vault cluster. You then configured VSO to read both static and dynamic secrets and create Kubernetes native secrets in multiple Kubernetes namespaces. Finally, you deployed the application and validated it can read the secrets created by VSO by connecting the application to the PostgreSQL database.
Now that the app is set up in support of the new smart brewing machines, Alice and Watson head to that famous Boston donut shop to enjoy a well deserved iced coffee (Yea, I know, iced coffee in October? It's a Boston thing!)!
Clean up
Stop the Vault Enterprise, and PostgreSQL container.
Stop minikube.
(Optional) Delete the minikube instance.
Return to the terminal running the Kubernetes proxy and type
ctrl-c
to stop the proxy.Return to the terminal running the ngrok type
ctrl-c
to stop ngrok.Delete the HC_LAB directory and lab content.