Skip to content

Vault Secrets Operator

Vault Secrets Operator - HashiCorp Documentation

Challenge

Vault offers a complete solution for secrets lifecycle management, but that requires developers and operators to learn a new tool. Instead, developers want a cloud native way to access the secrets through Kubernetes and have no need to understand Vault in great depth. Vault Secrets Operator (VSO) updates Kubernetes native secrets. The user accesses Kubernetes native secrets managed on the backend by HashiCorp Vault.

Solution

[...] The Vault Secrets Operator syncs the secrets between Vault and the Kubernetes secrets in a specified namespace. Within that namespace, applications have access to the secrets. The secrets are still managed by Vault, but accessed through the standard way on Kubernetes.

The Vault Secrets Operator syncs the secrets between Vault and the Kubernetes secrets in a specified namespace as opposed to Vault CSI Driver which manages secret via mount volumes it manages kubernetes-native secret resources.

Prerequisites

  • Vault installed and running (external)
  • Helm HashiCorp repository added
  • Kubernetes cluster
  • Kubernetes authentication method setup in Vault see this guide

Installation

Part 1.Configuring Vault

Note

We will use pre-existing secret engine in Vault, we will be using kv2 engine named secret, secrets operator also supports kv2 and PKI (certificate) engines.*

Tip

This guide uses Vault Web UI for convinience, however the same steps can be performed easily using Vault CLI or API.

Firstly, we need to create secret we're gonna mount later on. We're gonna create a secret containing username and password for our application (in this case grafana).

  1. Open Vault's Web UI and login.
  2. On the left hand menu click on Secret Engines and then secret.
  3. You should see following screen:

Vault Secret Engine

  1. Click on Create Secret and fill out a follow-up form

Vault Secret Create

  1. Click Save and you should see your secret in the following form. Note the API path.

Vault Secret Overview

  1. Second you will need a policy that will allow your app to access the secret you've just created. Go to Policies > Create ACL Policy and create a policy, likewise:

Vault Policy Create

  1. Click Save and remember the policy name.
  2. Lastly you will need to create a role that will bind the policy to the Kubernetes service account. Go to Access > Kubernetes > Roles and create a role, likewise:

Do not forget about adding the policy to the role.

Vault Role Create

Part 2. Installing Vault Secrets Operator

Now that we have our secret and policies set up in Vault, we can proceed with the installation of the Vault Secrets Operator.

The process is very simple using a Helm chart, we will create a minimal values.yaml file and install the chart.

defaultVaultConnection:
  enabled: true
  address: "http://vault.wmsdev.pl"
  skipTLSVerify: true

You can also enable things like telemetry via Prometheus ServiceMon, or a transit encrpytion.

helm repo add hashicorp https://helm.releases.hashicorp.com
helm install vault-secrets-operator hashicorp/vault-secrets-operator .

Part 3. Deploying a Kubernetes native secret

Now that we have the Vault Secrets Operator installed, we can deploy a Kubernetes secret that will be synced with Vault.

First of all we need to create an VaultAuth CRD that will be used for authenticating our secret.

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: grafana
  name: grafana-web-gui
---
apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultAuth
metadata:
  name: grafana-auth
  namespace: grafana
spec:
  method: kubernetes
  mount: kubernetes
  kubernetes:
    role: grafana
    serviceAccount: grafana-web-gui

Where mount is the name you gave your auth method in vault, and role is the name of the role you created earlier in this guide. It's important that the name of SA matches the name specified in the role in previous steps. You may also use preexisting SA.

Then we need to create a VaultSecret CRD that will represent the secret in Vault.

apiVersion: secrets.hashicorp.com/v1beta1
kind: VaultStaticSecret
metadata:
  name: vault-kv-app
  namespace: app
spec:
  type: kv-v2
  mount: secret
  path: grafana/admin-credentials

  destination:
    name: grafana-admin-credentials-vault
    create: true
  refreshAfter: 30s

  vaultAuthRef: grafana-auth

Where: - type is the type of secret engine, in our case kv-v2 - mount is the name of the secret engine in Vault - path is the path within the secret engine to the secret (so, without the secret/data prefix) - destination is the name of the Kubernetes secret - create is a boolean that specifies whether to create the secret if it doesn't exist - name is the name of the Kubernetes secret - refreshAfter is the time after which the secret will be refreshed - vaultAuthRef is the name of the VaultAuth CRD that will be used for authenticating to Vault it must be in the same namespace as the VaultSecret CRD.

And that's it, after applying the CRD to the cluster Vault Secrets Operator will create a Kubernetes secret in the specified namespace and sync it with Vault. You should be able to descrbe it, and the CRD's decribe should also reflect state of the secret.

# kubectl describe vaultstaticsecret.secrets.hashicorp.com/grafana-creds -n grafana
Name:         grafana-creds
Namespace:    grafana
Labels:       <none>
Annotations:  <none>
API Version:  secrets.hashicorp.com/v1beta1
Kind:         VaultStaticSecret
Metadata:
  Creation Timestamp:  2025-03-29T14:35:53Z
  Finalizers:
    vaultstaticsecret.secrets.hashicorp.com/finalizer
  Generation:        2
  Resource Version:  34176308
  UID:               d2cd0cc3-b77c-4e74-91af-97192dea31e1
Spec:
  Destination:
    Create:     true
    Name:       grafana-admin-credentials-vault
    Overwrite:  false
    Transformation:
  Hmac Secret Data:  true
  Mount:             secret
  Path:              grafana/admin-credentials
  Refresh After:     30s
  Type:              kv-v2
  Vault Auth Ref:    grafana-auth
Status:
  Last Generation:  2
  Secret MAC:       H2HF1r9yY844zTp0Ndjer6+m0qvezonUMzjiEAW77Xs=
Events:             <none>
# kubectl get -n grafana secrets/grafana-admin-credentials-vault -o=yaml
apiVersion: v1
data:
  _raw: <...>
  password: <...>
  username: <...>
kind: Secret
metadata:
  creationTimestamp: "2025-03-29T21:34:39Z"
  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: d2cd0cc3-b77c-4e74-91af-97192dea31e1
  name: grafana-admin-credentials-vault
  namespace: grafana
  ownerReferences:
  - apiVersion: secrets.hashicorp.com/v1beta1
    kind: VaultStaticSecret
    name: grafana-creds
    uid: d2cd0cc3-b77c-4e74-91af-97192dea31e1
  resourceVersion: "34176278"
  uid: 1b43465d-2f2e-4683-b119-8d3b1fb6c476
type: Opaque