--- id: devops-vault-secrets title: Vault — secrets management deep category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [devops, security, vault, vibe-coding] tech_stack: { language: "HCL / CLI", applicable_to: ["DevOps", "Backend"] } applied_in: [] aliases: [HashiCorp Vault, secrets, dynamic credentials, KV store, transit, PKI, AppRole] --- # Vault Secrets Management > Secret 가 git / env file 가 답 X. **Vault: 동적 credential, encryption-as-a-service, PKI, audit**. AWS Secrets Manager / GCP Secret Manager 가 cloud-managed. ## 📖 핵심 개념 - Static secret: API key, DB password. - Dynamic secret: 매번 새 (DB 가짜 user). - Encryption: encrypt-as-a-service. - Auth method: how to access vault. ## 💻 코드 패턴 ### Vault setup (dev) ```bash # Dev mode (memory) vault server -dev # Production vault server -config=/etc/vault/config.hcl ``` ```hcl # config.hcl storage "raft" { path = "/vault/data" node_id = "node1" } listener "tcp" { address = "0.0.0.0:8200" tls_cert_file = "/etc/vault/cert.pem" tls_key_file = "/etc/vault/key.pem" } api_addr = "https://vault.example.com:8200" ``` ### KV (key-value) ```bash vault kv put secret/myapp/db password='my-secret' vault kv get secret/myapp/db # password = my-secret vault kv get -field=password secret/myapp/db # my-secret ``` ### KV v2 (versioned) ```bash vault kv put secret/myapp/db password='v2-secret' vault kv get -version=1 secret/myapp/db # 옛 version vault kv rollback -version=1 secret/myapp/db ``` ### Dynamic DB credential ```bash # Setup vault secrets enable database vault write database/config/postgres \ plugin_name=postgresql-database-plugin \ allowed_roles=readonly \ connection_url='postgresql://{{username}}:{{password}}@db:5432/app' \ username='vault-admin' \ password='admin-pw' vault write database/roles/readonly \ db_name=postgres \ creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \ default_ttl=1h \ max_ttl=24h ``` ```bash # Application 가 호출 vault read database/creds/readonly # username = v-readonly-abc123 # password = ... ``` → 매번 새 DB user. 1 시간 유효. 자동 정리. ### AppRole auth ```bash vault auth enable approle vault write auth/approle/role/myapp \ secret_id_ttl=10m \ token_ttl=1h \ token_max_ttl=24h \ policies=myapp-policy # Get role + secret vault read auth/approle/role/myapp/role-id # role_id = ... vault write -f auth/approle/role/myapp/secret-id # secret_id = ... ``` ```bash # Login vault write auth/approle/login \ role_id=... \ secret_id=... # token = ... ``` → App 가 role_id + secret_id 로 vault token 발급. Short TTL. ### Kubernetes auth ```yaml # Service account apiVersion: v1 kind: ServiceAccount metadata: name: myapp ``` ```bash vault auth enable kubernetes vault write auth/kubernetes/role/myapp \ bound_service_account_names=myapp \ bound_service_account_namespaces=default \ policies=myapp-policy \ ttl=1h ``` ```ts // Pod 안 const jwt = readFileSync('/var/run/secrets/kubernetes.io/serviceaccount/token', 'utf8'); const r = await fetch('https://vault:8200/v1/auth/kubernetes/login', { method: 'POST', body: JSON.stringify({ role: 'myapp', jwt }), }); const token = (await r.json()).auth.client_token; ``` ### Policy (HCL) ```hcl # myapp-policy.hcl path "secret/data/myapp/*" { capabilities = ["read"] } path "database/creds/readonly" { capabilities = ["read"] } path "transit/encrypt/myapp" { capabilities = ["update"] } ``` ```bash vault policy write myapp-policy myapp-policy.hcl ``` ### Transit (encrypt-as-a-service) ```bash vault secrets enable transit vault write -f transit/keys/myapp # Encrypt vault write transit/encrypt/myapp plaintext=$(echo "secret data" | base64) # ciphertext = vault:v1:abcdef... # Decrypt vault write transit/decrypt/myapp ciphertext=vault:v1:abcdef... ``` → App 가 plaintext 가짐 적게. Vault 가 key 보유. ```ts // Postgres column 암호화 async function encryptField(plain: string) { const r = await vault.write('transit/encrypt/myapp', { plaintext: Buffer.from(plain).toString('base64'), }); return r.data.ciphertext; } async function decryptField(cipher: string) { const r = await vault.write('transit/decrypt/myapp', { ciphertext: cipher }); return Buffer.from(r.data.plaintext, 'base64').toString('utf8'); } ``` ### PKI (Certificate Authority) ```bash vault secrets enable pki vault write pki/root/generate/internal \ common_name=example.com ttl=87600h vault write pki/roles/myapp \ allowed_domains=example.com \ allow_subdomains=true \ max_ttl=720h vault write pki/issue/myapp common_name=app.example.com ttl=24h # certificate = ... # private_key = ... ``` → Short-lived TLS cert. ### Lease & renewal ```ts // Get secret const r = await vault.read('database/creds/readonly'); const lease_id = r.lease_id; const ttl = r.lease_duration; // 3600 // 자동 renew setInterval(() => vault.renew(lease_id), ttl * 0.5 * 1000); // 또는 expire 후 다시 read ``` ### Vault Agent (sidecar) ```hcl # vault-agent.hcl auto_auth { method "approle" { config = { role_id_file_path = "/etc/vault/role_id" } } } template { source = "/etc/vault/db.tpl" destination = "/etc/myapp/db.yml" } ``` → Agent 가 vault 접근, app 가 file 만 읽음. ### AWS Secrets Manager (managed) ```ts import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager'; const client = new SecretsManagerClient({ region: 'us-east-1' }); const r = await client.send(new GetSecretValueCommand({ SecretId: 'myapp/db' })); const secret = JSON.parse(r.SecretString!); ``` → 자동 IAM. Lambda / ECS 친화. Rotation 옵션. ### GCP Secret Manager ```ts import { SecretManagerServiceClient } from '@google-cloud/secret-manager'; const client = new SecretManagerServiceClient(); const [version] = await client.accessSecretVersion({ name: 'projects/my-proj/secrets/db-pw/versions/latest', }); const secret = version.payload!.data!.toString(); ``` ### Doppler / Infisical (cross-cloud) ```bash doppler login doppler run -- node app.js # → env var 자동 주입 ``` → Vault 보다 simple. Multi-cloud. ### .env file (only dev) ``` # .env (gitignore) DATABASE_URL=postgres://... API_KEY=... # 로컬 개발 만. Production 에 X. ``` → git 에 commit X. `.env.example` 만. ### Rotation ``` - DB password: 매 7-30 days 자동 - API key: 매 90 days - TLS cert: 매 90 days (Let's Encrypt 식) - Encryption key: 매 1 year + re-encrypt ``` ```bash # Vault transit 가 key rotation 자동 vault write -f transit/keys/myapp/rotate # 옛 ciphertext 도 decrypt 가능 (auto upgrade) ``` ### External Secrets Operator (K8s) ```yaml # K8s 가 vault → secret 자동 sync apiVersion: external-secrets.io/v1beta1 kind: ExternalSecret metadata: name: db-secret spec: refreshInterval: 1m secretStoreRef: name: vault-store target: name: db-secret # K8s Secret data: - secretKey: password remoteRef: key: secret/myapp/db property: password ``` → App 가 K8s secret 만, vault 직접 안 호출. ### Audit log ```bash vault audit enable file file_path=/var/log/vault/audit.log ``` → 매 access = log. Compliance. ### Backup / DR ```bash vault operator raft snapshot save snapshot.bak vault operator raft snapshot restore snapshot.bak ``` ### Cost ``` Vault OSS: 무료 (self-host). Vault Enterprise: $$$ (HSM, performance replication). HCP Vault: managed, $$. AWS Secrets Manager: $0.40/secret/month + $0.05/10k requests. GCP Secret Manager: $0.06/secret/version/month. ``` ### When? ``` - 큰 system: Vault. - AWS-only / 작음: Secrets Manager. - GCP-only: GCP SM. - Multi-cloud: Doppler / Infisical. - 작은 팀 / 작은 secrets: Doppler. ``` ### Seal / unseal ``` Vault start = sealed (data encrypted). Unseal = key share (Shamir secret sharing). - 5 keys total, 3 needed to unseal. → HA / cloud auto-unseal: KMS (AWS / GCP / Azure). ``` ### 함정 ``` - Root token in env: 영구 access. - TTL 무한: 안 expire. - AppRole secret_id 누설: 누구나 access. - Audit log 안 봄: 침해 모름. - Backup 안 함: data lost. - Single-node: SPOF. - Performance replica + write 불가 무시. ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 큰 / multi-region | Vault (Raft + replica) | | AWS only | Secrets Manager | | GCP only | GCP Secret Manager | | 작은 / multi-cloud | Doppler / Infisical | | K8s | External Secrets + Vault | | Encryption | Vault transit | | TLS | Vault PKI | | Dynamic DB cred | Vault DB plugin | ## ❌ 안티패턴 - **.env in git**: 누설. - **Long-lived static secret**: rotation 없음 = exposure. - **Root token in app**: principle of least privilege 위반. - **No audit log**: blind. - **모든 secret 1 path**: 권한 분리 X. - **No backup**: lost. - **Hardcoded vault address**: failover 못 함. - **TTL 무한**: forever access. ## 🤖 LLM 활용 힌트 - Vault = enterprise. AWS / GCP SM = cloud-managed. - Dynamic credential 가 가장 큰 가치. - Transit = encryption-as-a-service. - TTL + rotation 항상. ## 🔗 관련 문서 - [[Security_Secrets_Management]] - [[DevOps_Secrets_Rotation_Automation]] - [[Security_Zero_Trust]]