Dynamic credentials and just-in-time access — Victor Agnew

Victor Agnew

Staff AI Platform Engineer · Vancouver, BC

Boring deploys. work/ Safe AI. ai-lab/ Happy teams. virtual-office.md

PIPELINE
>20 min → <5 min
DEPLOYS
weekly → hundreds/week
FAILED PIPELINES
<1%
CREDENTIAL ROTATION
6 months → 24 h
REVIEW ENV
~$9/day
GCP SPEND
−40% YoY
MCP TOOLS
160+ → 23
VIRTUAL OFFICE
~1 h 40 min
virtual-office.md Virtual office three.js · Cloudflare Workers · LiveKit Built in a day; 26 days later, 1,600+ commits and 12 coworkers committing.
Close case-studies/dynamic-credentials.md Back

case-studies/dynamic-credentials.md

Dynamic credentials and just-in-time access

Every service on short-lived credentials from Vault, and every production session requested, approved and audited, behind one command.

ArborXR · Staff Platform Engineer

Credential rotation
6 months → 24 h
Downtime per rotation
zero
Production access
requested · approved · audited
  • HashiCorp Vault
  • Vault Secrets Operator
  • HashiCorp Boundary
  • Kubernetes
  • Pulumi
  • Google OIDC
  • Slack
  • Deno

The problem

Credentials were rotated every six months. Fixing that meant answering four questions at once: where do secrets live, how do they rotate on their own, how do we stop credential files from lying around, and how does all of it work in Kubernetes without a person in the loop?

Options

Sealed Secrets keeps encrypted secrets next to the infrastructure code and is easy to set up. But encrypted values are hard to read, the encryption key is created and stored by hand (lose it and everything is lost), and every change means building a new SealedSecret by hand.

Other common answers: Mozilla SOPS, AWS and GCP Secret Manager, Infisical, Bitwarden, 1Password.

HashiCorp Vault is secure by default, supports many auth methods (OIDC, JWT, LDAP, GitHub, tokens), is production-ready, and has a simple UI and a large ecosystem. Its cost is that it's complicated to set up and run, so most of the work below goes into automating that away.

What I built

Vault, set up by a job

Vault runs in-cluster in high-availability mode with Raft storage, auto-unseals through the cloud KMS, and uses workload identity to reach cloud services. Nobody runs the setup by hand: a Kubernetes Job, part of the infrastructure rollout, waits for Vault to come up, initialises it, stores the recovery keys in Secret Manager, enables the secret engines, policies and roles, and finally revokes the root token.

Secrets into Kubernetes

The Vault Secrets Operator (VSO) syncs secrets from Vault into native Kubernetes Secrets, detects and reconciles drift, and restarts the deployments that use a secret when its value changes. It authenticates with Kubernetes service-account tokens, rotates its own tokens, and each role is scoped to specific namespaces and policies.

[ FIG. 1 ] Vault and the Vault Secrets Operator inside the cluster
Vault and the Vault Secrets Operator inside the cluster Nested boxes. Outer: VPC. Inside it, a PostgreSQL box (top) and a Kubernetes cluster box. In the cluster, a "vault" namespace containing VSO and Vault side by side, with two arrows between them labelled "request credentials" (VSO → Vault) and "return credentials" (Vault → VSO). An arrow from Vault up to PostgreSQL labelled "creation statement". Below, an "app" namespace containing VaultStaticSecret → Secret, VaultDynamicSecret → Database credentials Secret, and a Deployment that reads both Secrets. Arrows from VSO labelled "check for drift" (to VaultStaticSecret), "check for renewal" (to VaultDynamicSecret) and "rollout restart" (to Deployment). VPC PostgreSQL Kubernetes cluster namespace: vault VSO Vault request credentials return credentials creation statement namespace: app VaultStaticSecret Secret VaultDynamicSecret Database credentials Secret Deployment reads check for drift check for renewal rollout restart
Dynamic database credentials

Static secrets were only the first step. Vault's database engine creates a database user per lease from a creation statement and removes it with a revocation statement. Services receive credentials that rotate every 24 hours with zero downtime: VSO renews the lease, writes the new credentials and restarts the deployment.

People, through Boundary

Developers reach databases and clusters through HashiCorp Boundary: a controller with Google sign-in, and a worker inside each environment's network. Vault brokers short-lived credentials into each session, whether that's a database user or a Kubernetes service account for exec-ing into pods.

[ FIG. 2 ] People reach private services through Boundary, with credentials brokered by Vault
People reach private services through Boundary, with credentials brokered by Vault Left: a Developer. Right: two network boxes. Network 1 holds the Boundary controller (with "Google sign-in"). Network 2 is an environment's Kubernetes cluster holding a Boundary worker, Vault and the targets (PostgreSQL, Kubernetes API). Arrows: Developer → controller ("sign in, authorize session"); worker ⇄ controller ("register, session"); worker → Vault ("broker short-lived credentials"); worker → targets ("proxied session"); Developer → worker ("tunnel"). A small side flow: approval service ⇄ Slack ("approve"), approval service → controller ("temporary role, deleted seconds later"). Developer Network 1 Boundary controller Google sign-in Slack approval service approve temporary role, deleted seconds later Network 2 — an environment's Kubernetes cluster Boundary worker Vault targets PostgreSQL Kubernetes API sign in, authorize session tunnel proxied session register, session broker short-lived credentials
Just-in-time approval

Production is protected. You give a reason, the request lands in Slack, and an approver clicks approve. The approval creates a temporary Boundary role that lets that one person authorize a session on that one target, then deletes the role seconds later. The session keeps running; the permission doesn't.

One command

A small CLI wraps all of it: Google sign-in, pick the environment and the target, wait for approval when it's production, open the tunnel, and launch the database client with the brokered credentials already filled in.

[ FIG. 3 ] What one command does
What one command does A left-to-right sequence of seven numbered steps in mono: 1 sign in (Google OIDC) → 2 choose environment → 3 choose target → 4 production only: give a reason, wait for Slack approval → 5 authorize session (Vault brokers credentials) → 6 open the tunnel (local port) → 7 launch the client with the credentials filled in. 1 sign in (Google OIDC) 2 choose environment 3 choose target 4 production only: give a reason, wait for Slack approval 5 authorize session (Vault brokers credentials) 6 open the tunnel (local port) 7 launch the client with the credentials filled in

Results

  • Credentials rotate every 24 hours, with zero downtime, instead of every six months.
  • Production sessions are requested, approved in Slack and audited.
  • Every session gets short-lived credentials brokered by Vault.
  • Vault and Boundary set themselves up as part of the infrastructure rollout, with the root token revoked at the end.
next case study Review environments