278 lines
12 KiB
Markdown
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)
|