# Vault 기준 ## 목적 이 문서는 Kubernetes 환경에서 HashiCorp Vault 1.17+ 를 1000+ 서비스의 secret / PKI / dynamic credential 소스로 운영하기 위한 기준을 고정한다. - Integrated Storage (Raft) HA + auto-unseal을 1차 권장 경로로 둔다 - 공식 Helm chart (`hashicorp/vault`) values.yaml의 핵심 필드를 명시한다 - Vault Secrets Operator (VSO) 0.8+ CRD 경로를 secret delivery 기본값으로 둔다 - Vault Agent Injector는 Kubernetes Secret을 우회하고 싶은 워크로드의 2차 경로로 둔다 - Raft snapshot / audit device / telemetry / TLS / Kubernetes auth role을 운영 필수 요소로 둔다 ## 공식 의미 (Vault 1.17+ 기준) - Vault는 **sealed** 상태로 기동한다. Shamir 수동 unseal 또는 auto-unseal (`awskms`, `gcpckms`, `azurekeyvault`, `transit`)로 unseal한다. - **Integrated Storage (Raft)**는 공식 지원 HA backend다. 기동 시 `storage "raft"` stanza, `cluster_addr`, listener의 `cluster_address`가 모두 필요하다. `ha_storage`와 동시 선언 금지. - Vault는 두 포트를 쓴다: **`8200` (API/client), `8201` (cluster-to-cluster Raft replication)**. Service는 8201을 반드시 expose해야 peer-to-peer Raft가 성립한다. - `/v1/sys/health` 는 단일 endpoint로 상태 코드로 응답한다: `200` active, `429` standby (`standbyok=true`면 200), `472` DR secondary, `473` performance standby, `501` uninitialized, `503` sealed. - **Audit device는 최소 하나 활성화해야 한다.** audit device가 전부 실패하면 Vault는 요청 처리를 멈춘다(블로킹). 여러 개 운영 권장. - Kubernetes auth method는 ServiceAccount JWT를 TokenReview API로 검증한다. Vault 1.17+는 short-lived projected SA token(`audiences`)을 권장한다. - VSO 0.8+는 `secrets.hashicorp.com/v1beta1` API group을 사용하고 `VaultConnection`, `VaultAuth`, `VaultStaticSecret`, `VaultDynamicSecret`, `VaultPKISecret`, `HCPAuth`, `HCPVaultSecretsApp` CRD를 제공한다. - Vault Agent Injector는 `vault.hashicorp.com/agent-inject: "true"` 같은 Pod annotation으로 sidecar/init container를 주입해 secret을 `/vault/secrets/` 파일로 렌더링한다. - DR replication / Performance replication은 **Enterprise 기능**이다. OSS에서는 Raft snapshot restore가 복구 경로다. - Telemetry는 `telemetry { prometheus_retention_time = "24h" disable_hostname = true }` stanza로 활성화하고 `/v1/sys/metrics?format=prometheus`에서 scrape한다. ## 기본 규칙 ### 1. 배포는 공식 Helm chart (`hashicorp/vault`) 기본: - `helm repo add hashicorp https://helm.releases.hashicorp.com` - `server.ha.enabled=true` + `server.ha.raft.enabled=true` - `injector.enabled` 는 secret delivery 전략에 따라 결정 (VSO만 쓰면 `false`) - values.yaml은 Git에 보관 + Helmfile / Argo CD Application로 배포 기본 금지: - 수제 StatefulSet으로 처음부터 조립 - `dev` 모드 운영 - chart 기본 `standalone` 모드 production 사용 (single node + file storage) ### 2. HA topology: Raft 3-node 또는 5-node 기본: - `server.ha.replicas: 3` (과반수 장애 허용: 1 node) - critical path면 `5`로 확장 (2 node 장애 허용) - `server.ha.raft.setNodeId: true` (각 pod의 hostname을 node_id로 자동 주입) - anti-affinity: hostname 기준 required, zone 기준 preferred ### 3. Raft config: listener 8200 + cluster 8201 + service_registration `server.ha.raft.config` HCL에 최소한 아래 stanza가 필요하다. ```hcl ui = true listener "tcp" { address = "[::]:8200" cluster_address = "[::]:8201" tls_disable = 0 tls_cert_file = "/vault/tls/tls.crt" tls_key_file = "/vault/tls/tls.key" } storage "raft" { path = "/vault/data" node_id = "$(HOSTNAME)" } cluster_addr = "https://$(HOSTNAME).vault-internal:8201" api_addr = "https://$(HOSTNAME).vault-internal:8200" service_registration "kubernetes" {} telemetry { prometheus_retention_time = "24h" disable_hostname = true } ``` `cluster_addr`는 headless service(`vault-internal`)의 pod FQDN을 쓴다. 8201 Service expose 필수. ### 4. Auto-unseal 채택 (1차 권장) 기본: - AWS: `seal "awskms" { region = "..." kms_key_id = "..." }` - GCP: `seal "gcpckms" { project = "..." region = "..." key_ring = "..." crypto_key = "..." }` - Azure: `seal "azurekeyvault" { tenant_id = "..." vault_name = "..." key_name = "..." }` - Vault-to-Vault: `seal "transit" { address = "..." token = "..." key_name = "autounseal" mount_path = "transit/" }` 기본 금지: - Shamir key를 CI/CD 환경변수나 Kubernetes Secret에 저장 - seal backend에 lifecycle 보호 없음 (KMS key deletion protection 필수) ### 5. Audit device는 최소 2개 Audit device 전부 실패 시 Vault가 요청을 block한다. redundancy 확보. 기본: - `auth/kubernetes/login` 경로 포함 모든 API 감사 - `file`: `server.auditStorage.enabled: true` → `/vault/audit/audit.log` - `syslog` 또는 `socket`: 중앙 로그 파이프라인 (Loki, Splunk, CloudWatch) - `vault audit enable file file_path=/vault/audit/audit.log` 기본 금지: - audit device 0개 운영 - audit log PVC 용량 무제한 (log rotation + sink 필수) ### 6. TLS는 end-to-end 기본: - cert-manager Certificate로 `vault-tls` Secret 발급 (cluster issuer) - listener에 `tls_cert_file`, `tls_key_file`, `tls_min_version = "tls13"` - client (app, VSO, Injector)는 CA bundle trust - Vault ↔ Storage ↔ seal backend 전 구간 TLS ### 7. Vault는 기본 내부 전용 (ClusterIP) 기본: - Service type: ClusterIP (8200, 8201) - Ingress 기본 금지 - 외부 관리자 접근은 VPN / bastion / port-forward / OIDC-protected admin Ingress ### 8. Probe: `/v1/sys/health` 상태코드 의미 반영 기본: - readiness: `GET /v1/sys/health?standbyok=true&perfstandbyok=true&uninitcode=204` (uninitialized를 200으로 수용 초기 bootstrap 허용) - liveness: `GET /v1/sys/health?standbyok=true&sealedcode=204&uninitcode=204` (sealed + uninit이어도 pod 생존) - startup: initialDelay 10s, failureThreshold 12 (2분 유예) 기본 금지: - `GET /` 단순 probe - sealed 상태에서 liveness 실패 → 무한 재시작 루프 ### 9. Kubernetes auth method 구성 Vault 쪽 (1회 bootstrap): ```bash vault auth enable kubernetes vault write auth/kubernetes/config \ token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ kubernetes_host="https://kubernetes.default.svc.cluster.local" \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ disable_iss_validation=false ``` Role은 ServiceAccount + namespace에 바인딩: ```bash vault write auth/kubernetes/role/auth-server \ bound_service_account_names=auth-server \ bound_service_account_namespaces=auth-prod \ policies=auth-server-read \ ttl=1h \ audience=vault ``` 기본 금지: - `bound_service_account_names=*` 또는 `bound_service_account_namespaces=*` - TTL 무한 또는 24h 이상 ### 10. Secret delivery: VSO가 1차 권장 기본: - 클러스터 전체 1개 `VaultConnection` (namespace: `vault`) - 앱 namespace마다 `VaultAuth` (ServiceAccount 바인딩) - 정적 KV 동기화: `VaultStaticSecret` - 동적 DB credential: `VaultDynamicSecret` - TLS 인증서: `VaultPKISecret` - `destination.create: true`로 K8s Secret 자동 생성, `rolloutRestartTargets`로 consumer 재시작 ### 11. Vault Agent Injector: Kubernetes Secret 우회가 필요할 때 기본 annotation set: ```yaml vault.hashicorp.com/agent-inject: "true" vault.hashicorp.com/role: "auth-server" vault.hashicorp.com/agent-inject-secret-db-creds: "database/creds/auth-server" vault.hashicorp.com/agent-inject-template-db-creds: | {{ with secret "database/creds/auth-server" -}} DATABASE_USERNAME={{ .Data.username }} DATABASE_PASSWORD={{ .Data.password }} {{- end }} vault.hashicorp.com/agent-pre-populate-only: "true" # init-only (앱이 파일 1회 읽음) vault.hashicorp.com/agent-inject-file-db-creds: "db.env" ``` 기본: - etcd에 민감정보를 남기고 싶지 않을 때 선택 - 앱이 파일 기반 secret 소비 가능해야 함 - 장기 실행 sidecar 대신 `agent-pre-populate-only: "true"`로 init container만 사용해 resource overhead 감소 ### 12. Raft snapshot 백업은 운영 필수 기본: - 하루 1회 `vault operator raft snapshot save` CronJob - snapshot을 off-cluster object storage (S3, GCS, MinIO replicated bucket)에 저장 - retention 30일 이상 + 주간 / 월간 snapshot 분리 - restore 절차를 runbook으로 문서화 ### 13. Telemetry + Prometheus scrape 기본: - config: `telemetry { prometheus_retention_time = "24h" disable_hostname = true }` - 내부 Prometheus token policy: ``` path "sys/metrics" { capabilities = ["read"] } ``` - Prometheus scrape: `/v1/sys/metrics?format=prometheus` + Bearer token (unauth-endpoint 가능하지만 권장하지 않음) ### 14. 포트 expose: 8200 + 8201 둘 다 기본: - Pod containerPort: 8200 (api), 8201 (cluster) - Service `vault`: ClusterIP, 8200 - Service `vault-internal`: Headless, 8200 + **8201** (Raft peer discovery 필수) - 8201 누락 시 Raft peer-to-peer 실패, leader election 불가 ### 15. Replication 경계: OSS vs Enterprise DR replication, performance replication, namespace multi-tenancy는 **Vault Enterprise** 전용이다. OSS 기준 복구: - Raft snapshot restore로 state 복원 - 동일 seal backend 요구 (auto-unseal이면 KMS key 필요) 기본 금지: - OSS에서 DR topology를 가정한 설계 - Enterprise 기능을 OSS manifest에 넣기 ### 16. Security context: Restricted PSS 기본: - `runAsNonRoot: true`, `runAsUser: 100` (vault user) - `readOnlyRootFilesystem: true` - `allowPrivilegeEscalation: false`, `capabilities.drop: [ALL]`, `capabilities.add: [IPC_LOCK]` (mlockall을 위함, swap 방지) - `seccompProfile: RuntimeDefault` ### 17. Resource 요청 기본 단일 replica (Raft 3 node 클러스터 중 하나): - requests: `cpu: 250m`, `memory: 256Mi` - limits: `cpu: 1`, `memory: 512Mi` 대규모 PKI / dynamic secret 발급량이 많으면 `memory: 1Gi` 이상. ### 18. Token / root token 취급 기본: - `vault operator init` 출력 root token은 1회성 bootstrap - 초기 설정 완료 후 `vault token revoke ` - 장기 root 필요 시 `vault operator generate-root` 절차로 ephemeral 생성 - app token은 Kubernetes auth login 경로로만 발급 - CLI history에 unseal key, root token 남기지 않음 (`HISTCONTROL=ignorespace`) ### 19. 현재 스택 기본 권장안 - 배포: Helm chart `hashicorp/vault`, `server.ha.enabled=true` + `server.ha.raft.enabled=true` - Replicas: 3 - Storage: Integrated Storage (Raft) + dataStorage PVC + auditStorage PVC - Unseal: auto-unseal (awskms / gcpckms / azurekeyvault / transit) - TLS: end-to-end, cert-manager Certificate - Service: ClusterIP 8200 + Headless 8200/8201 - Ingress: 기본 금지 (관리자 경로만 OIDC-protected 예외) - Probe: `/v1/sys/health` status-code aware - Audit: file + syslog 중복 - Secret delivery: VSO 1차, Injector 2차 - Backup: daily Raft snapshot → off-cluster object storage ## 프로젝트 기준 요약 - Helm chart 공식 배포, HA Raft 3-node, auto-unseal - 8200 (client) + 8201 (cluster) Service expose 필수 - `/v1/sys/health` status-code 기반 probe - audit device 최소 2개, 전체 실패 시 block 특성 인지 - Kubernetes auth role은 SA + namespace 단위, wildcard 금지 - VSO 1차 / Injector 2차 (`agent-pre-populate-only` init-only 선호) - Raft snapshot daily CronJob → off-cluster 보관 - Telemetry `/v1/sys/metrics?format=prometheus` + Prometheus token policy - DR/perf replication은 Enterprise 기능, OSS 경계 분명 - Restricted PSS + IPC_LOCK capability (mlockall)