본문으로 건너뛰기

시크릿 / 인증서 흐름

GenD의 시크릿은 세 계층입니다 — 애플리케이션 자격증명은 Vault에, Vault 를 여는 부트스트랩 비밀(root 토큰·recovery key)은 Azure Key Vault 에, 그 Azure 계정을 되찾는 수단은 Azure 밖 사람 금고에 둡니다.

애플리케이션 계층은 다시 3가지 주입 경로를 씁니다 — External Secrets Operator (Vault → ESO → K8s Secret, 운영 표준), SealedSecret (레거시 — ESO 이관 후 청산 예정, Epic #1107), cert-manager (TLS 자동 발급). 모든 시크릿은 단일 원본 원칙을 따라 valueFrom 으로 직접 참조되며, ConfigMap 에 평문 복제하지 않습니다.

:::note Vault Agent Injector 는 미설치입니다

Vault 의 비밀은 Agent 파일 마운트(/vault/secrets/*)가 아니라 ESO 가 동기화한 K8s Secret 을 통해 파드에 도달합니다. 실측: prod 에 agent-injector 파드 0개, 리포 전체에 /vault/secrets 마운트 0건. :::

세 계층 — 무엇을 어디에 보관하는가

이 문서의 나머지는 애플리케이션이 쓰는 자격증명을 다룹니다. 그런데 그것들을 보관하는 Vault 자체를 여는 열쇠는 어디에 둘까요? Vault 안에 넣을 수 없습니다 — 순환 참조입니다.

그래서 시크릿은 세 계층으로 나뉩니다.

계층무엇보관처접근 주체
애플리케이션 자격증명DB 비밀번호, S3 키, OIDC client secret, API 토큰Vault (KV v2)워크로드 (ESO 경유)
부트스트랩 비밀Vault root 토큰, recovery key 5개, 제공자 키 암호화 키Azure Key Vault kv-gend-prod-recovery사람 (KV Secrets User 이상)
최종 복구 수단Azure 관리자 계정 비밀번호, MFA 백업 코드사람용 금고(1Password 등) + 인쇄 보관사람 (Owner 소수)

부트스트랩 비밀은 클러스터 안에 두지 않습니다. 클러스터를 장악한 공격자가 Vault 까지 함께 얻게 되기 때문입니다. Azure Key Vault 는 클러스터 밖이고 RBAC 으로 사람에게만 열려 있어 이 조건을 만족합니다.

:::tip 세 번째 줄이 왜 필요한가 — 순환 의존의 끝

prod Vault ──열쇠──▶ Azure Key Vault ──열쇠──▶ Azure 관리자 계정 ──?──▶

Azure Key Vault 가 Vault 를 열고, Azure 계정이 Key Vault 를 엽니다. 그 마지막 고리가 끊기면(계정 분실·MFA 기기 분실) 위의 모든 보관이 무의미해집니다. 그래서 Azure 계정 복구 수단만은 Azure 밖에 두어야 합니다.

사람용 금고를 쓴다면 그 금고의 복구 수단(1Password Emergency Kit 등)도 인쇄해 물리적으로 보관하십시오 — 개인 계정에는 조직 복구 경로가 없습니다. :::

현재 보관 상태 (2026-08-08 실측)

az keyvault secret list --vault-name kv-gend-prod-recovery --query "[].name" -o tsv
# gend-vault-root-token-1172
# gend-vault-recovery-key-1-of-5 … 5-of-5
# gend-provider-key-enc

접근 명부 — 값을 직접 읽을 수 있는 사람 3명(Key Vault Administrator 1, Key Vault Secrets User 2), 스스로 권한을 부여할 수 있는 사람 6명(Owner 5 + User Access Administrator 1). 구독 Contributor 30여 명은 읽지 못합니다(RBAC 모드라 데이터평면이 분리됨). 볼트는 soft delete·purge protection 활성, 90일 보존입니다.

꺼내는 방법과 주의사항은 Vault 재해 복구 §2 를 보십시오.

:::danger 보관보다 기록이 먼저 무너진다

vault operator init 은 root 토큰과 recovery key 를 운영자 홈의 임시 파일 (~/vault-init-output-<TS>.json) 로 내보냅니다. 그것을 금고로 옮기고 원본을 shred 하는 것은 스크립트가 아니라 사람이 합니다.

2026-08-08 에 이 절차의 어느 쪽이 실제로 취약한지가 드러났습니다(#3207). 옮기는 것은 됐습니다 — root 토큰과 recovery key 는 Azure Key Vault kv-gend-prod-recovery 에 안전하게 있었습니다. 빠진 것은 어디에 넣었는지 적는 일 이었습니다. 어떤 런북에도 그 볼트 이름이 없었고, 조사 과정에서 다른 볼트 (kv-gend-prod-unseal) 하나만 확인하고 "Azure 경로는 소진됐다" 고 오판해 몇 시간을 잃었으며, 하마터면 멀쩡한 Vault 를 재초기화할 뻔했습니다.

교훈: 위치는 비밀이 아니라 포인터입니다. 값은 금고에, 포인터는 문서에 적으십시오 — 포인터 없는 금고는 없는 금고와 같습니다.

★ 그리고 여러 개일 수 있는 것(Key Vault·네임스페이스·저장소)은 전수 목록부터 뜨십시오(az keyvault list). 하나를 보고 범주 전체를 판정하면 이 사고가 재현됩니다. :::

운영 규칙

  • init 직후 즉시 옮기고 원본을 shred -u — 미루면 잊습니다.
  • 보관 위치를 이 문서에 적습니다 — 누가·언제·어느 볼트의 어느 이름으로. 값이 아니라 좌표를 적습니다. 새 시크릿을 금고에 넣었다면 위 "현재 보관 상태" 를 갱신하는 것까지가 한 작업입니다.
  • 값을 화면에 출력하지 않습니다. 파일로 받아 stdin 으로 넘기고 shred -u 로 지웁니다 — 명령 인자로 주면 파드 안 ps 와 셸 히스토리에 남습니다.
  • root 토큰은 상시 사용하지 않습니다. 일상 운영은 userpass / Kubernetes auth 로 발급받은 단명 토큰을 씁니다 (admin 정책). root 는 그 경로가 깨졌을 때의 최후 수단입니다.
  • 최후 수단이 하나뿐이면 안 됩니다 — root 토큰을 잃어도 recovery key 3/5 로 vault operator generate-root 가 가능합니다. 둘 다 잃으면 재초기화 외에 길이 없습니다.

시크릿 주입 경로

주입 패턴 비교

패턴사용 사례장점단점
External Secrets (ESO)DB 비밀번호, OAuth·SMTP secret, API 키Vault 단일 원본, 자동 재동기화, GitOps 친화Vault 가용성에 의존 · refreshInterval 만큼 반영 지연
SealedSecret (레거시)ESO 이관 전 잔여분Git 저장 가능회전 수동 · Epic #1107 로 청산 예정
cert-managerTLS 인증서 (외부·내부)자동 갱신DNS-01 또는 HTTP-01 필요

Vault Transit 키

키 이름알고리즘회전용도상태
datax-connectorAES-256-GCM90일 자동커넥터 비밀번호 암호화사용 중 (2026-08-09 활성화, #3210)
gend-chunk-hmacHMAC (exportable=false)수동벡터 청크 서명사용 중 (guard_service, #3210)
datax-ceph-kmsAES-256-GCM수동Ceph RGW SSE-KMSENABLE_CEPH_KMS=true 일 때만 생성

:::caution 키가 있다는 것과 쓰인다는 것은 다르다

05-enable-transit.sh 는 위 키를 만들지만, 암호화를 켜는 것은 애플리케이션 쪽 설정(GEND_VAULT_ENABLED)입니다. 2026-08-09 에 활성화했습니다(#3210) — 그 전까지는 키만 있고 앱이 쓰지 않아 커넥터 비밀번호가 평문으로 저장됐습니다.

★ 켜기 전 전제가 넷이었습니다: gend-api 정책·k8s role 부재, 주소 스킴(http→400), TLS CA 미신뢰, 토큰 전달 경로(정적 토큰만 지원). 특히 정책이 HMAC 경로 2개를 빠뜨리고 있어 그대로 켰다면 커넥터 암호화는 되는데 청크 서명이 403 으로 죽었습니다.

감사 로그 HMAC 체인은 Transit 이 아니라 K8s SealedSecret (gend-audit-hmac-key) 을 사용합니다. :::

단일 원본 원칙 (#490 교훈)

같은 비밀이 여러 ConfigMap/Secret에 복제될수록 동기화 사고 위험이 증가합니다. GenD는 다음 규칙을 강제합니다:

  • 공유 자격증명: K8s Secret 하나만 만들고 valueFrom: secretKeyRef 로 직접 참조
  • ConfigMap 평문 금지: 비밀 값은 ConfigMap에 절대 복제하지 않음
  • bootstrap 스크립트: 2>/dev/null || true 같은 무음 처리 금지, phase 폴링 + envFrom override 패턴 사용

관련 문서