Skip to main content
HoodCloud uses HashiCorp Vault for secrets management.

Overview

Vault provides:
  • KV v2 Engine: Application secrets (DB passwords, API keys, JWT RSA keys)
  • Transit Engine: DEK encryption without exposing master key
  • AppRole Auth: Machine-to-machine authentication
  • Audit Logging: Complete access audit trail

Architecture

Local Development

docker compose -f docker-compose.dev.yml up handles Vault automatically. No manual setup needed. The dev compose includes:
  1. vault — Dev mode (in-memory, no TLS, token: dev-token, http://localhost:8200)
  2. vault-init — Runs scripts/vault-dev-init.sh to seed engines, policies, AppRole, keys
Shared volume pattern: the sidecar writes AppRole credentials to /vault/config/. Services mount this volume read-only and use an entrypoint wrapper:
E2E tests (tests/e2e/docker-compose.e2e.yml) use the same script on port 8201.

Production Setup

Prerequisites

  • Docker and Docker Compose v2+
  • OpenSSL (TLS cert generation)
  • jq (migration script)

1. Generate TLS Certificates

Creates in infrastructure/docker/config/vault/tls/:
  • vault-ca.crt — CA certificate (distribute to clients)
  • vault-ca.key — CA private key
  • vault.crt — Vault server certificate (includes IP in SAN)
  • vault.key — Vault server private key

2. Prepare Data Volumes

3. Deploy Vault

Persist VAULT_BIND_ADDR=0.0.0.0 in .env for remote access.
Note: The command must be server (no -config= flag). The Docker entrypoint adds -config=/vault/config automatically. Explicit -config causes “address already in use”.

4. Configure Firewall

5. Initialize Vault

Save the output: 5 unseal keys + root token.

6. Run Initialization Script

Creates:
  • KV v2 at secret/, Transit engine with hoodcloud-master key
  • AppRole roles: hoodcloud-control-plane, hoodcloud-auth-server, hoodcloud-payment-service, hoodcloud-admin
  • Policies for each role
  • Audit logging at /vault/logs/audit.log
  • Admin AppRole credentials (printed at the end — save these)

7. Revoke Root Token

The admin AppRole replaces the root token for day-to-day management. To regenerate a root token (emergency):

8. Generate JWT RS256 Keys

Generate the RSA keypair for JWT signing:

9. Generate X25519 Sealed Box Keypair

Generate the NaCl sealed box keypair for user-provided secret encryption. These are mandatory — api-server and orchestrator fail fast at startup without them.
Note: These must be a mathematically related X25519 keypair, not random bytes. For local dev, vault-dev-init.sh seeds them automatically.

10. Populate Secrets

Important: terraform_env_vars and provider_env_vars must be JSON objects, not JSON strings. The application deserializes them as map[string]string.
Incident notification fields are optional. If none are configured, incidents are tracked in the database but no external notifications are sent. Or use the interactive migration script:

11. Deploy AppRole Credentials

Note: Use chmod 644 — containers run as non-root. The volume is mounted :ro.

12. Configure Control Plane Services

Copy the Vault CA certificate:
Add to .env:
All five services (api-server, auth-server, agent-gateway, orchestrator, health-evaluator) need:
  • Vault env vars in docker-compose.yml
  • Volume mount: /opt/hoodcloud/secrets:/secrets:ro
Recreate services:
Important: Use up -d, not restart. restart does not reload .env changes.

13. Save Credentials and Clean Up

Store securely (password manager): Delete from Vault server:

Secret Structure

Note: The payment service’s nats_ctrl_account_pub (CTRL account public key) is not stored in Vault. It comes from the NATS_CTRL_ACCOUNT_PUB environment variable or YAML config. Only the signing seed is a secret and must be in Vault.

Configuration

All Vault-related environment variables (control plane and payment service) are documented in Environment Variables.
Note: The payment service uses VAULT_ADDRESS (not VAULT_ADDR). See Environment Variables - Payment Service Vault for details.

Features

Circuit Breaker

  • Failure threshold: 3 consecutive failures
  • Recovery timeout: 30s (exponential backoff up to 5 min)

Secret Caching

  • Default TTL: 5 minutes (VAULT_CACHE_TTL)
  • Expired cache used as fallback when circuit is open
  • Cache cleared on service restart

Token Renewal

  • Renewal attempted at 75% of TTL
  • On failure, re-authenticates with AppRole credentials

Credential Expiry Monitoring

Warnings fire when < 25% of the original TTL remains. Configure alerts to schedule service restarts before expiry.

Operations

Unseal After Restart

Vault seals on every restart. Unseal with 3 of 5 keys:
While sealed, services use cached secrets (circuit breaker). If cache expires before unseal, services fail.

Authenticate as Admin

After root token is revoked:

Rotate Secrets

Rotate AppRole Secret ID

View Audit Logs

Verify Secret Storage


Troubleshooting

Service Cannot Authenticate

  1. Check Vault status: vault status (sealed?)
  2. Verify role exists: vault read auth/approle/role/hoodcloud-control-plane
  3. Regenerate secret_id:

Secret Not Found

Circuit Breaker Open

TLS Connection Errors

Set VAULT_TLS_CA_FILE=/secrets/vault-ca.crt in .env, or for CLI:

Security Model

AppRole Credential Security (Phase 0)

  • secret_id_num_uses=0 — Reusable for re-authentication after Vault restarts
  • secret_id_ttl=0 — No expiry
  • secret_id_bound_cidrs — Usable only from bound server IP
  • token_bound_cidrs — Tokens valid only from bound server IP
Compromised SecretID is useless from any IP other than the bound server.

Phase 1 Upgrade: SecretID Rotation

Add TTL to SecretIDs (secret_id_ttl=7d) and implement rotation via CI/Ansible. See scripts/vault-init.sh for AppRole configuration. Options:
  • Vault Agent sidecar for automated token management
  • Response wrapping (secret_id_num_uses=1) for strongest security