Back to Blog
Software EngineeringTechnologyJune 18, 20263 min read

Automating Kubernetes Secrets: Using HashiCorp Vault and ESO

Learn how to automate Kubernetes secrets using HashiCorp Vault and the External Secrets Operator. Secure your infrastructure with this DevSecOps best practice.

KubernetesHashiCorp VaultExternal Secrets OperatorDevSecOpsAutomationSecurityDevOpsLinuxServer

Managing secrets in Kubernetes is a headache. If you’re still manually creating Secret objects or base64-encoding YAML files in your Git repositories, you’re doing it wrong. It’s a security risk, it’s unscalable, and frankly, it’s a manual process that doesn't belong in a modern CI/CD pipeline.

I’ve spent the last few years refining our DevSecOps posture, and the gold standard remains the same: use a dedicated secret manager like HashiCorp Vault and bridge it to Kubernetes using the External Secrets Operator (ESO).

Why bother with External Secrets Operator?

Kubernetes Secrets are just base64-encoded strings stored in etcd. Anyone with cluster-admin access—or even a compromised pod with the right RBAC—can read your production database credentials.

HashiCorp Vault provides encryption-at-rest, audit logging, and dynamic secret generation. But Vault doesn't natively speak "Kubernetes Secret." That’s where the External Secrets Operator (ESO) comes in. It acts as the glue, watching for custom resources in your cluster and pulling the latest values from Vault to inject them as native Kubernetes secrets.

The Setup

We’ll assume you have a running Kubernetes cluster (v1.27+) and a functional HashiCorp Vault instance (v1.14+).

1. Install ESO

I recommend using Helm for this. It’s the standard, it’s reliable, and it’s easy to version-control.

Bash
helm repo add external-secrets https://charts.external-secrets.io
helm install external-secrets \
   external-secrets/external-secrets \
    -n external-secrets \
    --create-namespace

2. Configure the SecretStore

The SecretStore is a namespaced resource that tells ESO how to talk to Vault. We need to authenticate using a Kubernetes ServiceAccount.

YAML
apiVersion: external-secrets.io/v1beta1
kind: SecretStore
metadata:
  name: vault-backend
spec:
  provider:
    vault:
      server: "https://vault.example.com:8200"
      path: "secret"
      version: "v2"
      auth:
        kubernetes:
          mountPath: "kubernetes"
          role: "my-app-role"

3. Defining the ExternalSecret

This is where the magic happens. You define an ExternalSecret object that maps a key in Vault to a specific key in a Kubernetes Secret.

YAML
apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
spec:
  refreshInterval: "1h"
  secretStoreRef:
    name: vault-backend
    kind: SecretStore
  target:
    name: app-db-secret # The resulting K8s secret
  data:
  - secretKey: username
    remoteRef:
      key: secret/data/myapp/db
      property: username
  - secretKey: password
    remoteRef:
      key: secret/data/myapp/db
      property: password

Pro-Tips from the Trenches

When you’re implementing this, keep these three lessons in mind:

1. Use Namespace Scoping Don't use a ClusterSecretStore unless you absolutely have to. By sticking to namespaced SecretStore objects, you enforce the principle of least privilege. If one namespace is compromised, the attacker can't necessarily pull secrets from the entire Vault instance.

2. Watch the Refresh Interval ESO defaults to a refresh interval of 1 hour. In a high-security environment, you might be tempted to set this to 30 seconds. Don't. You’ll hammer your Vault API unnecessarily. If you need secrets to rotate instantly, look into the Vault Agent Injector instead, though it’s a different architecture.

3. RBAC is your best friend The ServiceAccount used by ESO needs specific permissions in Vault. Ensure you’ve mapped your Kubernetes ServiceAccount to a Vault policy that only grants read access to the specific paths the application needs. Never give the operator "root" access to your secret engines.

Why this is the Future

By automating Kubernetes secrets, we remove the human element. No more copy-pasting API keys into Slack or pasting them into YAML files. Everything is defined as code. If we need to rotate the database password, we update it in Vault, and within an hour, the cluster updates itself.

This is what DevSecOps is really about: building systems that are secure by default, not by manual checklist.

Similar Posts