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

278 lines
12 KiB
Markdown

# 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가 필요하다.
```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-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)