Files

224 lines
12 KiB
Markdown

# config / secrets 기준
## 목적
이 문서는 1000+ 서비스 운영 클러스터에서
- 무엇을 ConfigMap에 두고 무엇을 Secret/Vault에 두는지
- 민감정보를 어떤 경로로 Pod에 주입하는지 (VSO / ESO / CSI / SealedSecrets / SOPS)
- Kubernetes Secret at-rest encryption을 어떻게 구성하는지
- Image registry credential은 어떻게 다루는지
를 단일 ground truth로 고정한다.
이 문서의 목표는 다음과 같다.
- 민감정보가 manifest/Git/image/log 어디에도 새지 않는다
- Vault를 single source of truth로 두고 K8s Secret은 **파생 산출물**로만 존재
- 주입 방식(envFrom/volume/CSI)과 source(VSO/ESO/Vault Injector)를 표준화
- GitOps와 비밀 관리를 구조적으로 분리
## 공식 의미 (근거)
- ConfigMap은 **비기밀** 데이터 저장용 API object. 최대 1MiB.
- Secret은 민감정보용 object. **data는 base64 encoded(암호화 아님)**. stringData는 생성 시 자동 base64.
- Secret은 기본적으로 etcd에 평문 저장(base64 decode가 암호화가 아님). Kubernetes는 **at-rest encryption을 운영에서 필수**로 권장.
- Secret 타입: `Opaque`, `kubernetes.io/tls`, `kubernetes.io/dockerconfigjson`, `kubernetes.io/service-account-token`, `bootstrap.kubernetes.io/token`, `kubernetes.io/basic-auth`, `kubernetes.io/ssh-auth`.
- **Vault Secrets Operator(VSO)**: Vault의 secret(KV v2, dynamic DB, PKI, AWS 등)을 Kubernetes Secret으로 sync하는 controller. CRD: `VaultConnection`, `VaultAuth`, `VaultStaticSecret`, `VaultDynamicSecret`, `VaultPKISecret`, `HCPVaultSecretsApp`. 앱은 그냥 K8s Secret을 `envFrom`/`volumeMounts`로 소비.
- **Vault Agent Injector**: Mutating webhook이 Pod에 sidecar/init container를 주입해 tmpfs에 비밀을 렌더링. K8s Secret을 **만들지 않는다**(Vault → file).
- **External Secrets Operator(ESO)**: AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, Vault 등 **외부 provider → K8s Secret sync**. VSO와 유사하지만 멀티 provider.
- **CSI Secret Store Driver**: volume으로만 마운트(K8s Secret 미생성, optionally mirror). Azure Key Vault, AWS Secrets Manager, GCP Secret Manager, Vault provider 존재.
- **Sealed Secrets (Bitnami)**: public key로 암호화된 `SealedSecret` CRD를 Git에 커밋 → controller가 cluster private key로 복호화해 K8s Secret 생성. GitOps 친화적.
- **SOPS**: 파일 수준 암호화(age/GPG/KMS) + kustomize/Helm/Flux plugin. Git 커밋 가능.
- `EncryptionConfiguration`은 API Server `--encryption-provider-config` 플래그로 지정. providers: `identity`(평문), `aescbc`, `aesgcm`, `secretbox`, `kms` v1/v2.
- K3s는 `--secrets-encryption` 플래그로 aescbc provider 활성화.
## 기본 규칙
### 1. 분류: ConfigMap vs Secret vs Vault
#### ConfigMap
- host/port/base path
- feature flag
- timeout/retry/batch size
- 공개 가능한 application config (`application.yaml` 비기밀 부분)
- log level
- probe 관련 non-secret 설정
#### Kubernetes Secret (하지만 **Vault 파생**이 기본)
- DB password, OAuth client secret, signing key, API token
- TLS 인증서 (cert-manager가 자동 생성)
- imagePullSecret(`kubernetes.io/dockerconfigjson`)
- VSO/ESO가 sync한 Secret
#### Vault (source of truth)
- 모든 운영 credential의 1차 저장소
- DB dynamic credentials, PKI, transit encryption keys
- OIDC client secret, SMTP credential
- KV v2 path로 서비스별 격리
**원칙:** "조금이라도 민감하면 Vault/Secret 쪽". ConfigMap에는 절대 비밀 넣지 않는다. base64는 암호화가 아니다.
### 2. Kubernetes Secret at-rest encryption 필수
운영 클러스터는 API Server `--encryption-provider-config`로 Secret 자원을 암호화한다. 권장 순서: **KMS v2 > KMS v1 > aescbc > identity(금지)**.
```yaml
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources: ["secrets"]
providers:
- kms:
apiVersion: v2
name: platform-kms
endpoint: unix:///var/run/kmsplugin/socket.sock
cachesize: 1000
timeout: 3s
- aescbc:
keys:
- name: fallback-2026-q1
secret: <32-byte base64 key>
- identity: {}
```
- KMS 소켓/플러그인은 노드 hardening 대상.
- K3s는 `curl -sfL https://get.k3s.io | INSTALL_K3S_EXEC="server --secrets-encryption" sh -` 또는 config.yaml `secrets-encryption: true`.
- 기존 Secret은 `kubectl get secrets --all-namespaces -o json | kubectl replace -f -`로 강제 재암호화.
- 키 회전은 `kube-apiserver` restart + `replace` 절차를 ADR로 고정.
### 3. Secret delivery 경로 우선순위
1. **VSO** — 운영 기본. Vault KV v2/dynamic credential → K8s Secret → envFrom/volume. 앱 코드 변경 0.
2. **ESO** — 멀티 클라우드 provider 필요 시. API는 VSO와 유사하지만 `SecretStore`/`ClusterSecretStore` + `ExternalSecret`.
3. **CSI Secret Store Driver** — K8s Secret object를 아예 만들고 싶지 않을 때(volume only). SA별 scope가 필요한 sensitive mount.
4. **Vault Agent Injector** — 앱이 template engine을 필요로 할 때(JSON/XML 포맷 렌더링). K8s Secret 없음.
5. **SealedSecrets / SOPS** — GitOps 전용 + 소규모 클러스터 + VSO 미도입 환경. Git에 encrypted blob 커밋.
6. **Plain Secret manifest** — 운영 금지. 로컬/부트스트랩 한정.
선택 기준:
- "Vault가 SoT이고 K8s Secret을 앱이 envFrom으로 소비" → VSO
- "멀티 클라우드/비-Vault provider" → ESO
- "K8s Secret 자체를 만들고 싶지 않음(audit/scope)" → CSI
- "앱이 Vault template로 renderng 필요" → Vault Agent Injector
- "Vault 없음 + Git에 커밋해야 함" → SealedSecrets/SOPS
### 4. VSO CRD 사용 표준
VSO는 Helm으로 `vault-secrets-operator` namespace에 설치되어 있다고 가정한다.
- `VaultConnection` (namespace or cluster) — Vault address, CA bundle, TLS skipVerify=false
- `VaultAuth` — auth method(kubernetes, jwt, approle). kubernetes auth 기본.
- `VaultStaticSecret` — KV v2 secret → K8s Secret
- `VaultDynamicSecret` — Postgres/MySQL/AWS dynamic credentials
- `VaultPKISecret` — PKI engine → `kubernetes.io/tls` Secret
- `HCPVaultSecretsApp` — HCP Vault Secrets 소비
모든 CRD는 같은 namespace 안에서 선언하고, 결과 Secret의 이름은 서비스명 규칙을 따른다.
### 5. Vault path 규칙 + auth policy
- KV v2 path: `kv/data/<team>/<service>/<env>/<component>` (예: `kv/data/identity/auth-server/prod/db`)
- Vault role은 namespace + service account로 제한:
```
bound_service_account_names=auth-server
bound_service_account_namespaces=auth-prod
```
- Vault policy는 `path "kv/data/identity/auth-server/prod/*" { capabilities = ["read"] }` 수준으로 scope.
- dynamic credential TTL은 pod 수명과 맞춘다(예: Postgres role 24h, auto-renew).
### 6. 주입 방식: envFrom vs volume
- **envFrom** — 전체 Secret의 key를 env로 투사. 간단, 12-factor 친화. 하지만 프로세스 env는 sub-process 상속, `/proc/<pid>/environ` 노출 위험.
- **volume** — 파일로 마운트(`/var/run/secrets/<name>`). 권장 in-memory(`readOnly: true`). 민감 key는 volume 우선.
- **envFrom + volume 혼합** 허용(db env는 env, signing key는 volume).
- **subPath**는 사용 금지(Secret 업데이트가 자동 반영 안 됨).
### 7. 한 Pod 내에서도 필요한 컨테이너에만 주입
- sidecar(metrics, proxy)에는 secret 전달 금지.
- Pod `volumes`로 선언하더라도 각 컨테이너 `volumeMounts`는 필요한 컨테이너에만.
### 8. Secret/ConfigMap naming
- 패턴: `<service>-<purpose>` (`auth-server-db`, `auth-server-oidc-client`, `keycloak-db`).
- 금지: `common-*`, `shared-*`, `global-*` (스코프가 불분명하고 권한 팽창 원인).
### 9. immutable Secret/ConfigMap
- 변경 빈도 낮은 `kubernetes.io/tls`, 앱 release-tied config는 `immutable: true` 검토.
- immutable이면 수정 불가 → 삭제 후 재생성 + rollout 필요. VSO가 갱신하는 Secret은 immutable 금지.
### 10. Kustomize generator 사용 기준
- `configMapGenerator` — 비기밀 설정에 허용. hash suffix로 rollout 트리거.
- `secretGenerator` — **운영 금지**. 로컬/테스트/부트스트랩 한정.
- 운영은 VSO/ESO/SealedSecrets 경로.
### 11. Image pull secret
- 타입: `kubernetes.io/dockerconfigjson`.
- 구조:
```json
{
"auths": {
"registry.example.com": {
"username": "ci-bot",
"password": "<token>",
"auth": "<base64(username:password)>"
}
}
}
```
- SA의 `imagePullSecrets`에 연결 → Deployment마다 반복 선언 불필요.
- Registry credential 자체도 VSO로 Vault → `kubernetes.io/dockerconfigjson` Secret sync(VSO `VaultStaticSecret.destination.type: kubernetes.io/dockerconfigjson`).
### 12. image 지정: digest pin 기본
- mutable tag(`latest`, `main`, `dev`)는 `imagePullPolicy: Always` + staging 환경에만.
- 운영은 `image: registry.example.com/auth-server@sha256:<digest>` 고정. `imagePullPolicy: IfNotPresent` 충분.
- digest는 CI가 release 시 생성하고 GitOps manifest(ArgoCD)에 커밋.
- Kyverno/Gatekeeper로 namespace `auth-prod`의 Pod image가 `@sha256:`를 포함하도록 enforce.
### 13. Secret 접근 RBAC
- `secrets` 리소스의 `list`/`watch`는 controller(VSO, cert-manager, argo-cd)에만 허용.
- 일반 workload는 `get` + `resourceNames` 배열로 제한.
- 같은 namespace에서 Pod 생성 권한은 Secret 간접 접근이 될 수 있음을 전제로 RBAC 설계(namespace 분리).
### 14. 민감정보 로깅/에러 보호
- 앱은 비밀을 평문 로그, 예외 메시지, telemetry attribute, debug endpoint에 포함 금지.
- Exception handler는 `password`, `token`, `secret`, `authorization` 포함 필드 자동 redact.
- APM/Logging pipeline에도 scrub rule 추가.
### 15. Secret 회전
- dynamic credential: VSO `VaultDynamicSecret`이 TTL 전에 자동 renew/rotate + Pod rollout trigger(`rolloutRestartTargets`).
- static credential: VSO `refreshAfter` + Vault rotate cron + `rolloutRestartTargets`로 Deployment 자동 rolling.
- TLS cert: cert-manager가 `renewBefore`에 맞춰 회전. Pod는 `reloader` annotation 또는 webhook으로 rollout.
### 16. 설정 타입과 도메인 타입 분리
- `@ConfigurationProperties` / `application.yaml`은 설정 계약.
- 도메인 Value Object는 config에서 복사하되 config 타입을 도메인에 노출하지 않는다.
- 테스트에서는 config를 직접 주입할 수 있어야 한다(포트 바인딩, spring profile).
### 17. 환경별 overlay
- `base/` — 공통 ConfigMap/Service/Deployment/RBAC
- `overlays/{dev,staging,prod}/` — 환경별 patch(`replicas`, `resources`, `image digest`, `ingress host`)
- Secret은 **overlay에 plain 저장 금지**. VSO CRD도 prod overlay에서 Vault mount path만 override.
### 18. 현재 스택 기본 권장안
#### auth-server / test-server / keycloak
- ConfigMap: `application.yaml` 비기밀
- Secret 경로: VSO `VaultStaticSecret`(OIDC client) + `VaultDynamicSecret`(Postgres role)
- 주입: envFrom(DB creds) + volume(signing key 파일)
#### migration-flyway
- short-lived Job
- SA token automount false
- VSO `VaultDynamicSecret`이 migration 전용 Postgres role을 짧은 TTL로 발급
#### vault
- Vault server 자체의 unseal key는 cluster 밖(HSM/KMS/cloud KMS auto-unseal)
- bootstrap token은 `vault-bootstrap` namespace에 at-rest encrypted Secret으로 저장, 사용 후 삭제
#### registry
- `kubernetes.io/dockerconfigjson` Secret은 VSO로 Vault KV에서 sync
- namespace SA `imagePullSecrets`에 연결
## 프로젝트 기준 요약
- ConfigMap = 비기밀, Secret = 민감정보, Vault = source of truth
- Kubernetes Secret at-rest encryption(KMS 우선, aescbc 최소) 필수
- Secret delivery 우선순위: VSO > ESO > CSI > Vault Agent Injector > SealedSecrets/SOPS
- 운영 Secret generator/plain Secret manifest 금지
- image는 digest pin + private registry, `kubernetes.io/dockerconfigjson` Secret은 SA imagePullSecrets 연결
- Secret 주입은 필요한 컨테이너/필요한 key만, env보다 volume 우선
- RBAC은 namespace Role + `resourceNames` + list/watch controller 전용
- 회전은 VSO/cert-manager + rolloutRestart 자동화