224 lines
12 KiB
Markdown
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 자동화
|