TCH
SecurityCloud Infrastructure · Zero Trust · DevSecOps · IAM
Published on

From Static Passwords to Dynamic Access: Vault + HCP Boundary on Kubernetes

Authors

From Static Passwords to Dynamic Access: Vault + HCP Boundary on Kubernetes

In my previous article, I validated the core zero-trust flow with HCP Boundary on Kubernetes: no direct exposure of MySQL or SSH, access routed through an in-cluster worker, and credentials handled by Boundary.

Part 1: Zero-Trust Database and SSH Access with HCP Boundary on Kubernetes — A Hands-On POC

This post is the natural next step.

Ephemeral access by design: Vault issues credentials only when a session is authorized, and those credentials expire quickly instead of becoming long-lived secrets.

I replaced static credentials with HashiCorp Vault so credentials are generated (or signed) just in time for each Boundary session:

  • MySQL access now uses Vault's database secrets engine to create ephemeral usernames/passwords
  • SSH access now uses Vault's SSH CA engine to sign short-lived user certificates

All configuration comes from the sibling repository folder ../Boundary, mainly:

  • ../Boundary/terraform/boundary-config/vault.tf
  • ../Boundary/terraform/boundary-config/targets.tf
  • ../Boundary/k8s/vault/
  • ../Boundary/k8s/ssh-target/vault-ca-configmap.yaml

What Changed Since the Previous Article

Previously:

  • SSH used a static username/password stored in Boundary
  • MySQL used a static username/password stored in Boundary

Now:

  • Boundary reads credentials from Vault credential libraries
  • MySQL credentials are created per session and revoked when expired
  • SSH certificates are signed with a short TTL (5m) and trusted by the target SSH daemon via TrustedUserCAKeys

This is the shift from stored secret distribution to ephemeral identity-based access.


Updated Architecture

Architecture: Vault and Boundary integrated on Kubernetes

The diagram shows the end-to-end flow:

  1. SSH flow — Client → HCP authorization → Boundary worker calls Vault to sign a short-lived certificate (5m TTL) → certificate injected into session
  2. Vault credential library — Worker holds a Vault token and calls credential engines through the worker filter
  3. Boundary client authorization — CLI or Desktop authenticates, receives session approval from HCP controller
  4. MySQL flow — Worker proxies to MySQL target with dynamically-generated credentials (1h default TTL, revoked on expiration)

Key point: the HCP controller does not need direct network reachability to Vault or targets. The Boundary worker handles data-plane traffic and Vault API calls through:

worker_filter = "\"k8s\" in \"/tags/type\""

Kubernetes: Add Vault and Trust the SSH CA

Vault runs in-cluster (dev mode for this lab) and is exposed via NodePort only so Terraform on the host can configure it.

../Boundary/k8s/vault/deployment.yaml:

containers:
  - name: vault
    image: hashicorp/vault:1.17
    args:
      - "server"
      - "-dev"
      - "-dev-listen-address=0.0.0.0:8200"

../Boundary/k8s/vault/service.yaml:

spec:
  type: NodePort
  ports:
    - port: 8200
      targetPort: 8200
      nodePort: 30200

For SSH certificate trust, the SSH target uses a ConfigMap mounted as trusted user CA keys:

../Boundary/k8s/ssh-target/Dockerfile includes:

echo "TrustedUserCAKeys /etc/ssh/trusted-user-ca-keys.pem" >> /etc/ssh/sshd_config

../Boundary/k8s/ssh-target/vault-ca-configmap.yaml is updated after Terraform creates the Vault SSH CA public key.


Terraform: Configure Vault Engines and Boundary Integration

1. Configure Vault engines and roles

In ../Boundary/terraform/boundary-config/vault.tf:

  • Enable database/ mount and register MySQL connection
  • Create role boundary-mysql with creation/revocation SQL
  • Enable ssh/ mount and generate SSH CA
  • Create role boundary-ssh for boundary-user with ttl = "5m"
  • Create minimal policy for Boundary to read/sign credentials
  • Create renewable orphan token for Boundary

MySQL dynamic role (excerpt):

resource "vault_database_secret_backend_role" "mysql" {
  backend = vault_mount.db.path
  name    = "boundary-mysql"

  creation_statements = [
    "CREATE USER '{{name}}'@'%' IDENTIFIED BY '{{password}}';",
    "GRANT ALL PRIVILEGES ON demo.* TO '{{name}}'@'%';",
  ]
  revocation_statements = ["DROP USER IF EXISTS '{{name}}'@'%';"]

  default_ttl = 3600
  max_ttl     = 7200
}

SSH CA role (excerpt):

resource "vault_ssh_secret_backend_role" "boundary" {
  backend                 = vault_mount.ssh.path
  name                    = "boundary-ssh"
  key_type                = "ca"
  allow_user_certificates = true
  allowed_users           = "boundary-user"
  default_user            = "boundary-user"
  ttl                     = "5m"
}

2. Register Vault as a Boundary credential store

In ../Boundary/terraform/boundary-config/targets.tf:

resource "boundary_credential_store_vault" "main" {
  name          = "vault"
  scope_id      = boundary_scope.project.id
  address       = "http://vault.boundary.svc.cluster.local:8200"
  token         = vault_token.boundary.client_token
  worker_filter = "\"k8s\" in \"/tags/type\""
}

3. Create Vault-backed credential libraries

SSH signed cert library:

resource "boundary_credential_library_vault_ssh_certificate" "ssh" {
  name                = "ssh-signed-cert"
  credential_store_id = boundary_credential_store_vault.main.id
  path                = "ssh/sign/boundary-ssh"
  username            = "boundary-user"
  ttl                 = "5m"
}

MySQL dynamic credentials library:

resource "boundary_credential_library_vault" "mysql" {
  name                = "mysql-dynamic"
  credential_store_id = boundary_credential_store_vault.main.id
  path                = "database/creds/boundary-mysql"
  http_method         = "GET"
  credential_type     = "username_password"
}

This is where dynamic database credentials are issued on demand for Boundary sessions:

Vault generates dynamic MySQL credentials for Boundary sessions

4. Attach libraries to targets

  • SSH target uses:
    • injected_application_credential_source_ids
  • MySQL target uses:
    • brokered_credential_source_ids

This preserves the same user experience as the previous article while replacing static creds with dynamic Vault-backed flows.


End-to-End Deployment Flow

From ../Boundary:

kubectl apply -f k8s/namespace.yaml
kubectl apply -f k8s/worker/
kubectl apply -f k8s/vault/
kubectl apply -f k8s/ssh-target/
kubectl apply -f k8s/mysql/

Then apply Terraform from ../Boundary/terraform/boundary-config:

cp terraform.tfvars.example terraform.tfvars
terraform init
terraform apply

After apply, inject the generated Vault SSH CA public key into the SSH ConfigMap:

kubectl create configmap ssh-vault-ca -n boundary \
  --from-literal=trusted-user-ca-keys.pem="$(terraform output -raw vault_ssh_ca_public_key)" \
  --dry-run=client -o yaml | kubectl apply -f -

kubectl rollout restart deployment/ssh-target -n boundary

This is mandatory: without the CA public key trusted by sshd, Vault-signed user certs will be rejected.


Session Usage

Authenticate to Boundary:

boundary authenticate \
  -addr=<BOUNDARY_CLUSTER_URL> \
  -auth-method-id=<AUTH_METHOD_ID>

SSH via injected short-lived certificate:

boundary connect ssh -target-id=$(terraform output -raw ssh_target_id)

MySQL via brokered dynamic credentials:

boundary connect \
  -target-id=$(terraform output -raw mysql_target_id) \
  -exec mysql -- \
    -u {{boundary.username}} \
    -p{{boundary.password}} \
    -h {{boundary.ip}} \
    -P {{boundary.port}} \
    demo

Behind the scenes, each session pulls fresh credentials from Vault instead of reusing a long-lived shared secret.


Why This Matters

Moving from static credentials to Vault-backed dynamic credentials gives immediate security benefits:

  • Reduced blast radius: leaked credentials expire quickly
  • No shared DB users: every session can map to unique ephemeral principals
  • Better revocation semantics: Vault revokes generated users/certs automatically
  • Cleaner separation of duties: Boundary brokers access, Vault manages secret issuance

This pattern is much closer to production-grade privileged access than static password storage.


Operational Notes and Caveats

  • The Vault deployment in this lab is dev mode for simplicity. Production requires HA storage, proper unseal strategy, TLS, and hardened auth/token strategy.
  • The MySQL container uses mysql_native_password specifically for compatibility with Vault's Go MySQL driver in this setup.
  • The Boundary token created in Vault is renewable and long-lived (period = "768h"). In production, scope and rotate this aggressively and consider auth methods that avoid static token handling.
  • If SSH cert login fails, verify the ConfigMap update and check that TrustedUserCAKeys points to the mounted file.

Closing

The first article proved Boundary could broker access without exposing services. This sequel proves the stronger model: ephemeral credentials per session powered by Vault.

Same topology, better security posture.

The next logical step is to move from lab-grade Vault auth/token patterns to production-grade trust (Kubernetes auth, policy segmentation, token lifecycle hardening) and add full session recording where required.