Files
keycloak-pattern/docs/experiment-d3-secret-management.md
T
DongHyeonkaandClaude Opus 5 027c24ee27 docs: D-3 — only RBAC actually hides anything
Every secret in the lab prints in four commands, while kubectl describe shows just a byte count and creates the impression that something is hidden. k3s reports encryption at rest disabled and the plaintext password is present in state.db, so one node disk carries the whole cluster's secrets, and inside the pod they are ordinary environment variables visible to exec, /proc and crash dumps.

The default service account cannot read secrets, which makes RBAC the one control doing real work here and the thing worth tightening.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 15:07:35 +09:00

6.8 KiB
Raw Blame History

D-3 — Secret 은 정말 감춰지는가

브랜치 feature/keycloak-d3-secret-management · 증거 docs/evidence/d3-secret-management/ · 2026-09-04 17:1517:25 KST


0. 결론부터

경로 감춰지는가
kubectl get secret -o jsonpath | base64 -d ★ 한 줄로 읽힌다
kubectl describe secret 값을 숨긴다 — 그래서 안전하다고 착각한다
저장소(at rest) ★ 암호화 꺼져 있음. 저장 파일에 평문이 있다
파드 안 ★ 평범한 환경변수다
RBAC 기본값 막는다default 서비스계정은 못 읽는다

"Secret 이니까 안전하다" 는 네 가지 중 하나(RBAC)만 맞다.


1. 한 줄로 읽힌다

kubectl -n keycloak-lab get secret keycloak-lab-secrets \
  -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d
  keycloak-lab-secrets/POSTGRES_PASSWORD            = lab-postgres-change-me
  keycloak-lab-secrets/KC_BOOTSTRAP_ADMIN_PASSWORD  = lab-admin-change-me
  bff-secrets/KEYCLOAK_CLIENT_SECRET                = bff-lab-secret
  oauth2-proxy-secrets/COOKIE_SECRET_A              = lab-cookie-secret-aaaaaaaaaaaaaa

실험대의 모든 비밀이 명령 네 줄로 나온다.

describe 는 감춘다 — 그것이 함정이다

Type:  Opaque

Data
====
KEYCLOAK_CLIENT_SECRET:  14 bytes

바이트 수만 보여준다. 이것만 보면 "가려져 있구나" 싶다. get -o jsonpath 한 번이면 값이 나온다.

개념 — base64 는 인코딩이지 암호화가 아니다

목적 되돌리기
인코딩 (base64) 바이너리를 텍스트로 안전하게 옮기기 키 없이 누구나
암호화 키 없이는 못 읽게 하기 키가 있어야

Secret 이 base64 를 쓰는 이유는 감추려는 것이 아니라 YAML 에 임의 바이트를 담기 위해서다.


2. 저장소에는 평문으로 있다

ssh kc-lab-1 'sudo k3s secrets-encrypt status'
  Encryption Status: Disabled, no configuration file found

k3s 의 저장소 암호화가 꺼져 있다. 기본값이다.

  /var/lib/rancher/k3s/server/db/state.db      13MB
  /var/lib/rancher/k3s/server/db/state.db-wal  10MB
ssh kc-lab-1 'sudo grep -c "lab-postgres-change-me" /var/lib/rancher/k3s/server/db/state.db'
  state.db 안의 평문 일치: 2

저장 파일 안에 비밀번호가 그대로 있다.

그래서 무엇이 위험한가
노드 디스크를 얻으면 전 클러스터의 비밀
노드 백업/스냅샷 같은 것을 복사한다
A-4 에서 본 local-path PVC 같은 디스크에 있다

D-1 에서 "덤프를 같은 장애 도메인에 두면 백업이 아니다" 라고 썼는데, 여기서는 "노드 디스크 하나가 모든 비밀" 이다. 백업을 잘 챙겨도 그 백업 안에 비밀이 평문으로 들어간다.

k3s 는 --secrets-encryption 플래그로 켤 수 있다. 지금은 안 켜져 있다.


3. 파드 안에서는 환경변수다

kubectl -n keycloak-lab exec <bff-pod> -- sh -c 'env | grep -iE "secret|password"'
  KEYCLOAK_CLIENT_SECRET=bff-lab-secret
  BFF_DB_PASSWORD=lab-postgres-change-me

env 한 번이면 나온다.

새는 경로
kubectl exec 권한이 있는 사람 바로 본다
같은 파드의 다른 프로세스 /proc/<pid>/environ
크래시 덤프 · 오류 리포트 환경변수를 함께 담는 도구가 많다
자식 프로세스 상속된다

볼륨으로 마운트하면 이 중 몇 가지가 줄어든다 — 파일 권한으로 제한할 수 있고 환경변수 덤프에 안 들어간다.

volumeMounts:
  - name: secrets
    mountPath: /etc/secrets
    readOnly: true

4. RBAC 은 실제로 막는다

kubectl auth can-i get secrets -n keycloak-lab \
  --as=system:serviceaccount:keycloak-lab:default
  default SA: no

기본 서비스계정은 Secret 을 못 읽는다. 쿠버네티스의 기본값이 제한적이다.

네 가지 중 유일하게 제 역할을 하는 것이 RBAC 다. 그러므로 "누가 get secrets 를 할 수 있는가" 가 실질적인 방어선이며, 관리자 권한을 가진 사람에게는 아무 방어가 없다.

A-0 의 관측 스택에서 nodes/proxy 서브리소스를 따로 줘야 했던 것처럼, Secret 접근도 리소스 단위로 나눌 수 있다.


5. 그래서 무엇을 해야 하는가

   지금:  매니페스트에 stringData 평문  →  git 에 커밋되면 끝
          k3s 저장소 암호화 꺼짐
          파드 환경변수
단계 얻는 것
① 매니페스트에서 값을 빼고 .example 만 커밋 git 유출을 막는다
k3s --secrets-encryption 활성화 노드 디스크 유출을 막는다
③ 환경변수 대신 볼륨 마운트 프로세스·덤프 유출을 줄인다
SealedSecret / 외부 KMS 매니페스트에 암호문만 남는다
RBAC 최소화 유일하게 이미 동작하는 방어선을 좁힌다

이 실험대는 ①~④ 중 아무것도 안 하고 있다. 실험 목적으로는 의도적이지만, 그 사실을 기록해두지 않으면 그대로 운영에 옮겨간다.

이 실험대의 비밀들은 이미 문서에 있다

lab-postgres-change-me, bff-lab-secret 같은 값이 이 저장소의 매니페스트와 문서에 그대로 적혀 있다. 실험대 전용이며 외부에서 접근할 수 없는 값이지만, "실험대니까 괜찮다" 가 습관이 되면 위험하다. 이름에 change-me 를 넣은 것이 그 최소한의 표시다.


6. 재현 절차 (명령어)

# 1. 한 줄로 읽힌다
kubectl -n keycloak-lab get secret keycloak-lab-secrets \
  -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d

# 2. describe 는 감춘다 — 대조
kubectl -n keycloak-lab describe secret bff-secrets

# 3. 저장소 암호화 여부
ssh kc-lab-1 'sudo k3s secrets-encrypt status'

# 4. 저장 파일에 평문이 있는가
ssh kc-lab-1 'sudo grep -c "lab-postgres-change-me" /var/lib/rancher/k3s/server/db/state.db'

# 5. 파드 안에서는 환경변수
kubectl -n keycloak-lab exec <pod> -- sh -c 'env | grep -i secret'

# 6. 누가 읽을 수 있는가
kubectl auth can-i get secrets -n keycloak-lab \
  --as=system:serviceaccount:keycloak-lab:default

7. 다음에 남기는 것

D-4 인증서 갱신 인증서 개인키도 같은 문제다
B-6 암호화 key key 를 Secret 에 두면 이 실험의 결론이 그대로 적용된다
운영 RBAC 이 유일하게 동작하는 방어선이다