Files
project-infra/docs/standards/infra/config-and-secrets.md
T

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로 암호화된 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(금지).

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.
  • 구조:
    {
      "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 자동화