Files
project-infra/docs/standards/infra/vault.md
T

12 KiB

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/<name> 파일로 렌더링한다.
  • 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가 필요하다.

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):

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에 바인딩:

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:

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-token>
  • 장기 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)