# 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////` (예: `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//environ` 노출 위험. - **volume** — 파일로 마운트(`/var/run/secrets/`). 권장 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 - 패턴: `-` (`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": "", "auth": "" } } } ``` - 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:` 고정. `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 자동화