12 KiB
12 KiB
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로 암호화된
SealedSecretCRD를 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,kmsv1/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(금지).
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.yamlsecrets-encryption: true. - 기존 Secret은
kubectl get secrets --all-namespaces -o json | kubectl replace -f -로 강제 재암호화. - 키 회전은
kube-apiserverrestart +replace절차를 ADR로 고정.
3. Secret delivery 경로 우선순위
- VSO — 운영 기본. Vault KV v2/dynamic credential → K8s Secret → envFrom/volume. 앱 코드 변경 0.
- ESO — 멀티 클라우드 provider 필요 시. API는 VSO와 유사하지만
SecretStore/ClusterSecretStore+ExternalSecret. - CSI Secret Store Driver — K8s Secret object를 아예 만들고 싶지 않을 때(volume only). SA별 scope가 필요한 sensitive mount.
- Vault Agent Injector — 앱이 template engine을 필요로 할 때(JSON/XML 포맷 렌더링). K8s Secret 없음.
- SealedSecrets / SOPS — GitOps 전용 + 소규모 클러스터 + VSO 미도입 환경. Git에 encrypted blob 커밋.
- 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=falseVaultAuth— auth method(kubernetes, jwt, approle). kubernetes auth 기본.VaultStaticSecret— KV v2 secret → K8s SecretVaultDynamicSecret— Postgres/MySQL/AWS dynamic credentialsVaultPKISecret— PKI engine →kubernetes.io/tlsSecretHCPVaultSecretsApp— 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. - 구조:
{ "auths": { "registry.example.com": { "username": "ci-bot", "password": "<token>", "auth": "<base64(username:password)>" } } } - SA의
imagePullSecrets에 연결 → Deployment마다 반복 선언 불필요. - Registry credential 자체도 VSO로 Vault →
kubernetes.io/dockerconfigjsonSecret sync(VSOVaultStaticSecret.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는reloaderannotation 또는 webhook으로 rollout.
16. 설정 타입과 도메인 타입 분리
@ConfigurationProperties/application.yaml은 설정 계약.- 도메인 Value Object는 config에서 복사하되 config 타입을 도메인에 노출하지 않는다.
- 테스트에서는 config를 직접 주입할 수 있어야 한다(포트 바인딩, spring profile).
17. 환경별 overlay
base/— 공통 ConfigMap/Service/Deployment/RBACoverlays/{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-bootstrapnamespace에 at-rest encrypted Secret으로 저장, 사용 후 삭제
registry
kubernetes.io/dockerconfigjsonSecret은 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/dockerconfigjsonSecret은 SA imagePullSecrets 연결 - Secret 주입은 필요한 컨테이너/필요한 key만, env보다 volume 우선
- RBAC은 namespace Role +
resourceNames+ list/watch controller 전용 - 회전은 VSO/cert-manager + rolloutRestart 자동화