# D-3 재현 가이드 — Secret 이 어디까지 감춰지는지 네 경로로 직접 본다 해설 문서: [`docs/experiment-d3-secret-management.md`](../../experiment-d3-secret-management.md) · 증거 원문: [`docs/evidence/d3-secret-management/`](../../evidence/d3-secret-management/) ## 이 가이드가 끝나면 당신 터미널에서 이것들을 **직접 본다.** | 보게 되는 것 | 어디서 | |---|---| | `describe` 는 `14 bytes` 만 보여주는 것 | `describe secret` | | 같은 값이 **한 줄로** 평문이 되는 것 | `get -o jsonpath \| base64 -d` | | 저장소 암호화가 **꺼져 있는** 것 | `k3s secrets-encrypt status` | | 노드 디스크의 저장 파일 안에 **평문이 있는** 것 | `grep -c` on `state.db` | | **그 grep 이 `0` 을 돌려주는데도 안전하지 않은** 것 | 같은 명령, 다른 키 | | 파드 안에서는 그냥 **환경변수**인 것 | `env` · `/proc/1/environ` | | RBAC 은 실제로 막는 것 | `auth can-i` | ## 전제 - 명령은 **`kc-lab-1` 에서** 친다. `kubectl` 은 `sudo` 로 쓴다. **k3s 서버의 저장 파일도 이 노드에 있다** — 그래서 4-2 를 여기서 칠 수 있다. - 게스트(`kc-lab-1`/`kc-lab-2`)의 `sudo` 는 **무암호**다. 호스트와 다르다. - 네임스페이스는 `keycloak-lab` 이다. - `jq` 는 이 실험대 어디에도 없다. 이 가이드는 `jq` 를 쓰지 않는다. - [`B-6`](../../experiment-b6-key-rotation.md) · [`B-7`](../../experiment-b7-cookie-secret-rotation.md) 를 이미 했다면 이 실험의 결론이 그 key 들에도 그대로 적용된다는 것을 알고 있을 것이다. ## 주의 — 이건 비밀을 화면에 띄우는 실험이다 **이 실험의 명령 몇 개는 비밀번호를 터미널에 그대로 찍는다.** 그게 결론이기 때문에 피할 수 없지만, 그 값은 **스크롤백·화면 공유·터미널 로그**에 남는다. 이 가이드는 그래서 이렇게 한다. - **남의 진짜 비밀은 길이(`wc -c`)와 키 이름까지만 본다.** - **값을 찍어 봐야 하는 곳은 이 실험용으로 직접 만든 카나리아 Secret 을 쓴다** (2절). 지워도 되는 값이므로 찍어도 된다. - 실측으로 실린 값들은 **이 저장소의 매니페스트와 문서에 이미 적혀 있는 실험대 전용 값**이다(`change-me` 가 이름에 들어 있는 이유가 그것이다). 파괴적인 단계는 없다. 만드는 것은 카나리아 Secret 하나뿐이고 [5. 복구](#5-복구) 에서 지운다. 전 구간 약 15분. ## 표시 규약 | 표시 | 뜻 | |---|---| | **실측** | 2026-09-04 15:05–15:06 KST 실행 기록의 **출력 원문**. 증거 파일에 그대로 있다 | | **형태** | 값이 매번 달라지는 출력. 모양만 보이고 숫자는 당신 것과 다르다 | | **미검증** | 손으로 치기 좋게 이 가이드에서 고친 형태이거나, 이 실험이 하지 않은 확장 | --- # 0. 왜 이 실험을 하는가 「비밀번호를 Secret 으로 옮겼습니다」는 리뷰에서 통과 도장을 받는 문장이다. **그 문장이 실제로 무엇을 막아 주는지**를 네 경로로 나눠 판정한다. | # | 경로 | 누가 쓰나 | 예측 | |---|---|---|---| | ① | 쿠버네티스 API (`get secret`) | 클러스터에 접근하는 사람 | ? | | ② | **노드 디스크의 저장 파일** | 디스크·백업·스냅샷을 얻은 사람 | ? | | ③ | **파드 안의 프로세스** | `exec` 권한이 있는 사람, 크래시 덤프 | ? | | ④ | RBAC | 권한이 없는 주체 | ? | **핵심 개념부터 짚는다.** | | 목적 | 되돌리기 | |---|---|---| | **인코딩** (base64) | 바이너리를 텍스트로 안전하게 **옮기기** | **키 없이 누구나** | | 암호화 | 키 없이는 못 **읽게** 하기 | 키가 있어야 | **Secret 이 base64 를 쓰는 이유는 감추려는 것이 아니라 YAML 에 임의 바이트를 담기 위해서다.** 그런데 `kubectl describe` 가 값을 가려서 보여주기 때문에 「가려져 있구나」라는 인상이 남는다 — 이 실험은 그 인상과 사실 사이의 거리를 잰다. --- # 1. 기준선 — 무엇이 있는지부터 본다 ## 1-1. Secret 목록 **확인** ```bash sudo kubectl -n keycloak-lab get secret ``` **실측** — [`01-base64-not-encryption.txt`](../../evidence/d3-secret-management/01-base64-not-encryption.txt) ``` bff-secrets Opaque keys=1 keycloak-lab-secrets Opaque keys=2 oauth2-proxy-secrets Opaque keys=3 ``` > 실측 줄은 원래 실행 스크립트가 정리해 찍은 것이다. 손으로 치면 > `NAME / TYPE / DATA / AGE` 네 칸이 나오고, `DATA` 열이 위의 `keys=` 에 해당한다. **어디를 봐야 하는가** — 이름과 `DATA` 열(키 개수). **`TYPE` 이 `Opaque` 인 것도 본다** — 「불투명」이라는 이름이지만 그건 쿠버네티스가 내용 구조를 모른다는 뜻이지 **감춘다는 뜻이 아니다.** ## 1-2. `describe` 는 값을 감춘다 **확인** ```bash sudo kubectl -n keycloak-lab describe secret bff-secrets ``` **실측** — [`01-base64-not-encryption.txt`](../../evidence/d3-secret-management/01-base64-not-encryption.txt) ``` Type: Opaque Data ==== KEYCLOAK_CLIENT_SECRET: 14 bytes ``` **어디를 봐야 하는가** — **키 이름과 바이트 수만 나온다.** 값이 없다. **이 결과가 의미하는 것** — 이 화면이 「Secret 은 감춰진다」는 인상의 출처다. `describe` 는 **일부러** 값을 안 찍는다. 그런데 그건 `describe` 라는 명령의 동작이지, **저장이나 전송의 성질이 아니다.** ## 1-3. 값을 안 보고 확인하는 법 — 평소에는 이렇게 한다 **남의 비밀을 다룰 때 기본 자세다.** 키 이름과 길이만 본다. **확인** — 키 이름만 ```bash sudo kubectl -n keycloak-lab get secret keycloak-lab-secrets -o jsonpath='{.data}' \ | tr ',' '\n' | grep -o '"[A-Z_]*"' ``` **형태** ``` "KC_BOOTSTRAP_ADMIN_PASSWORD" "POSTGRES_PASSWORD" ``` **확인** — 길이만 ```bash sudo kubectl -n keycloak-lab get secret keycloak-lab-secrets \ -o jsonpath='{.data.POSTGRES_PASSWORD}' | base64 -d | wc -c ``` **형태** ``` 22 ``` **어디를 봐야 하는가** — 숫자 하나. **값이 화면에 없다.** **이 결과가 의미하는 것** — 「Secret 이 제대로 들어갔는가」를 확인하는 데는 길이면 충분한 경우가 대부분이다. 배포가 안 될 때 진짜로 궁금한 것은 대개 **「비었는가 아닌가」**이지 값 자체가 아니다. > `wc -c` 는 개행까지 세므로 `base64 -d` 결과에 개행이 없으면 실제 길이와 > 같다. 값이 비었으면 `0` 이 나온다 — **`0` 은 「Secret 은 있는데 그 키가 > 비었다」는 뜻이고, 배포 실패의 흔한 원인이다.** --- # 2. 주입 — 카나리아 Secret 하나를 만든다 **여기부터 상태가 바뀐다.** 바뀌는 것은 Secret 하나뿐이다. **되돌리기** ```bash sudo kubectl -n keycloak-lab delete secret d3-canary ``` ## 2-1. 왜 카나리아를 쓰나 4절에서 **저장 파일 안을 grep 해야 한다.** 그러려면 **찾을 문자열을 알고 있어야** 하는데, 진짜 비밀번호를 grep 인자로 쓰면 그 값이 셸 히스토리와 프로세스 목록(`ps` 로 다른 사용자에게도 보인다)에 남는다. **그래서 「찾아도 아무 피해가 없는 값」을 하나 심는다.** 실험 대상이 값 자체가 아니라 **경로**이기 때문에 이렇게 해도 결론은 같다. **하기** ```bash sudo kubectl -n keycloak-lab create secret generic d3-canary \ --from-literal=CANARY=d3-canary-zq7v-do-not-use ``` **형태** ``` secret/d3-canary created ``` **어디를 봐야 하는가** — `created`. 이미 있다면 `AlreadyExists` 가 나온다 — 그럼 지우고 다시 만든다. > **이 값은 아무 데도 쓰이지 않는다.** 어떤 파드도 참조하지 않으므로 지워도 > 아무것도 안 깨진다. 값에 `do-not-use` 를 넣어 둔 이유는, 나중에 저장 파일 > 어딘가에서 이 문자열을 다시 만났을 때 **무엇인지 알아보기 위해서**다. --- # 3. 주입이 실제로 걸렸는지 확인한다 **확인** ```bash sudo kubectl -n keycloak-lab get secret d3-canary sudo kubectl -n keycloak-lab describe secret d3-canary ``` **형태** ``` Data ==== CANARY: 26 bytes ``` **어디를 봐야 하는가** — 여기서도 `describe` 는 바이트 수만 준다. **1-2 와 같은 화면이다.** 값을 아는 것은 당신뿐이고, 그래서 다음 절의 비교가 성립한다. --- # 4. 효과를 관찰한다 — 네 경로를 하나씩 연다 ## 4-1. ① API — 한 줄로 읽힌다 **하기** — 카나리아로 먼저 해 본다. 값을 알고 있으므로 무엇이 나올지 예측된다 ```bash sudo kubectl -n keycloak-lab get secret d3-canary \ -o jsonpath='{.data.CANARY}' | base64 -d; echo ``` **형태** ``` d3-canary-zq7v-do-not-use ``` **어디를 봐야 하는가** — **2-1 에서 심은 값이 그대로 나온다.** 같은 명령이 실제 비밀에도 그대로 듣는다. 원래 실행이 네 개를 뽑은 결과가 이것이다. **실측** — [`01-base64-not-encryption.txt`](../../evidence/d3-secret-management/01-base64-not-encryption.txt) ``` 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` 가 `14 bytes` 라고 했던 그 값이 `bff-lab-secret` (14자)이다. **이 결과가 의미하는 것** — ① 은 **막지 않는다.** base64 는 인코딩이고 `base64 -d` 는 누구나 칠 수 있다. 여기서 실질적인 방어선은 **누가 이 명령을 칠 수 있는가**이며, 그건 ④(RBAC)의 문제로 넘어간다. > **이 네 줄을 당신 환경에서 그대로 재현할 필요는 없다.** 카나리아로 한 번 > 확인했으면 기제는 같다. 진짜 비밀은 1-3 의 길이 확인으로 충분하다. ## 4-2. ② 저장소 — 노드 디스크에 평문이 있다 ### 먼저 암호화 설정을 본다 **확인** ```bash sudo k3s secrets-encrypt status ``` **실측** — [`02-at-rest.txt`](../../evidence/d3-secret-management/02-at-rest.txt) ``` Encryption Status: Disabled, no configuration file found ``` **어디를 봐야 하는가** — `Disabled`, 그리고 **`no configuration file found`**. 설정 파일이 아예 없다 — 껐다기보다 **켠 적이 없다**는 뜻이고, 이게 기본값이다. ### 저장 파일이 어디 있는지 본다 **확인** ```bash sudo ls -l /var/lib/rancher/k3s/server/db/ ``` **실측** — [`02-at-rest.txt`](../../evidence/d3-secret-management/02-at-rest.txt) ``` total 23336 drwx------ 2 root root 4096 Sep 2 09:12 . drwx------ 8 root root 4096 Sep 4 03:23 .. -rw-r--r-- 1 root root 13078528 Sep 4 06:05 state.db -rw-r--r-- 1 root root 32768 Sep 4 06:06 state.db-shm -rw-r--r-- 1 root root 10769712 Sep 4 06:06 state.db-wal ``` **어디를 봐야 하는가** — 파일이 **셋**이다. | 파일 | 무엇인가 | |---|---| | `state.db` | 본체 | | `state.db-wal` | **아직 본체에 합쳐지지 않은 최근 쓰기** | | `state.db-shm` | 공유 메모리 인덱스 | **k3s 는 etcd 대신 SQLite 를 쓴다.** 「저장소(at rest)」의 자리는 같다 — etcd 를 쓰는 클러스터라면 이 자리가 etcd 의 데이터 디렉터리다. > **`-wal` 이 10MB 나 되는 것을 봐 둔다.** 방금 만든 카나리아는 **아직 본체에 > 없을 가능성이 높다.** 아래에서 이게 함정이 된다. ### ★ 파일 안을 찾아본다 **하기** — 카나리아부터 ```bash sudo grep -c 'd3-canary-zq7v-do-not-use' /var/lib/rancher/k3s/server/db/state.db sudo grep -c 'd3-canary-zq7v-do-not-use' /var/lib/rancher/k3s/server/db/state.db-wal ``` **미검증** — 이 실험대는 카나리아 대신 실제 값으로 쟀다. 그 결과가 아래다. **실측** — [`02-at-rest.txt`](../../evidence/d3-secret-management/02-at-rest.txt) ``` === ★ 저장 파일에서 비밀번호가 그대로 보이는가 === state.db 안의 평문 일치: 2 === 평문이 저장 파일에 있다는 것을 눈으로 === client secret 평문 등장 횟수: 0 ``` **어디를 봐야 하는가** — **두 줄의 값이 다르다. `2` 와 `0`.** 같은 파일, 같은 명령, 다른 키인데 하나는 두 번 나오고 하나는 안 나온다. **이 결과가 의미하는 것 — 이 절에서 제일 중요한 문장이다.** > **`grep` 이 `0` 을 돌려준 것은 「평문이 없다」가 아니라 「이 파일의 이 시점에 > 이 형태로는 못 찾았다」이다.** `2` 가 나온 순간 ②의 답은 이미 정해졌다 — **저장 파일에 평문이 있다.** `0` 이 나온 키에 대해 「그건 안전한가 보다」라고 읽으면, **같은 파일에 평문이 들어 있는 것을 이미 본 뒤에 그러는 것이다.** **0 이 나왔을 때 다음에 볼 곳** — **미검증**. 이 실험은 원인을 가리지 않았다. ```bash sudo grep -c 'bff-lab-secret' /var/lib/rancher/k3s/server/db/state.db-wal sudo strings /var/lib/rancher/k3s/server/db/state.db | grep -c 'bff-lab-secret' ``` | 왜 안 나올 수 있나 | 확인 | |---|---| | 아직 `-wal` 에만 있다 | `-wal` 을 같이 grep | | 값이 페이지 경계를 넘어 잘렸다 | `strings` 로 한 번 더 | | 그 키가 그 시점에 없었다 | `get secret` 으로 존재 확인 | > **`grep -c` 는 바이너리 파일에도 듣는다.** 평소의 `grep` 은 바이너리를 만나면 > `Binary file ... matches` 한 줄만 찍고 내용을 안 보여주는데, `-c` 는 개수만 > 세므로 그대로 숫자가 나온다. **값 자체를 화면에 안 띄운다는 점에서도 이 > 형태가 맞다** — 여기서 궁금한 것은 「있는가」이지 「무엇인가」가 아니다. **그래서 무엇이 위험한가** | | | |---|---| | 노드 디스크를 얻으면 | **전 클러스터의 비밀** | | 노드 백업/스냅샷 | 같은 것을 복사한다 | | A-4 에서 본 `local-path` PVC | **같은 디스크에 있다** | | D-1 의 덤프 | 같은 기계에 뒀다면 **거기도 같이** | **D-1 에서 「덤프를 같은 장애 도메인에 두면 백업이 아니다」라고 했는데, 여기서는 「노드 디스크 하나가 모든 비밀」이다.** 백업을 잘 챙길수록 비밀도 잘 복사된다. **k3s 는 `--secrets-encryption` 플래그로 켤 수 있다.** 지금은 안 켜져 있고, **이 가이드는 켜지 않는다** — 켜는 것은 서버 재시작과 기존 Secret 재암호화를 수반하고, 이 실험대에서 시험하지 않았다(**미검증**). ## 4-3. ③ 파드 안 — 평범한 환경변수다 **확인** — 어느 파드를 볼지 먼저 정한다 ```bash sudo kubectl -n keycloak-lab get pods -l app=bff ``` **하기** — **미검증**(원래 실행은 파드 이름을 직접 지정했다) ```bash sudo kubectl -n keycloak-lab exec deploy/bff -- sh -c 'env | grep -iE "secret|password"' ``` **실측** — [`02-at-rest.txt`](../../evidence/d3-secret-management/02-at-rest.txt) ``` KEYCLOAK_CLIENT_SECRET=bff-lab-secret BFF_DB_PASSWORD=lab-postgres-change-me ``` **어디를 봐야 하는가** — **`env` 한 번이면 나온다.** 그리고 `bff-lab-secret` 은 4-1 에서 API 로 뽑은 값과 **같다** — 두 경로가 같은 평문에 닿는다. `exec` 이 `deploy/bff` 로 안 되면(파드가 종료 중이거나 여럿이면) 이름을 골라 친다. ```bash sudo kubectl -n keycloak-lab get pod -l app=bff \ --field-selector=status.phase=Running -o jsonpath='{.items[0].metadata.name}'; echo ``` **같은 파드 안의 다른 프로세스도 본다.** 이게 「환경변수」의 진짜 성질이다. **하기** — **미검증** ```bash sudo kubectl -n keycloak-lab exec deploy/bff -- \ sh -c 'tr "\0" "\n" < /proc/1/environ | grep -i secret' ``` **어디를 봐야 하는가** — 같은 값이 나오는가. `/proc//environ` 은 그 프로세스의 환경변수를 그대로 담고 있고, **같은 UID 의 아무 프로세스나 읽는다.** | 새는 경로 | | |---|---| | `kubectl exec` 권한이 있는 사람 | 바로 본다 | | 같은 파드의 다른 프로세스 | `/proc//environ` | | **크래시 덤프 · 오류 리포트** | 환경변수를 함께 담는 도구가 많다 | | 자식 프로세스 | 상속된다 | **볼륨으로 마운트하면 이 중 몇 가지가 줄어든다** — 파일 권한으로 제한할 수 있고, 환경변수 덤프에 안 들어간다. ```yaml volumeMounts: - name: secrets mountPath: /etc/secrets readOnly: true ``` **줄어드는 것이지 없어지는 것이 아니다.** `exec` 권한이 있으면 파일도 읽는다. ## 4-4. ④ RBAC — 유일하게 막는다 **확인** ```bash sudo kubectl auth can-i get secrets -n keycloak-lab \ --as=system:serviceaccount:keycloak-lab:default ``` **실측** — [`02-at-rest.txt`](../../evidence/d3-secret-management/02-at-rest.txt) ``` default SA: no ``` **어디를 봐야 하는가** — **`no`** 한 단어. **어떤 권한이 있는지 통째로 보려면** — **미검증** ```bash sudo kubectl auth can-i --list -n keycloak-lab \ --as=system:serviceaccount:keycloak-lab:default sudo kubectl -n keycloak-lab get role,rolebinding ``` **이 결과가 의미하는 것** — 기본 서비스계정은 Secret 을 못 읽는다. **명시적으로 거부해서가 아니라 아무 권한도 주지 않았기 때문**이다. RBAC 은 기본이 거부이고, Role 을 붙여야 할 수 있게 된다. > **네 가지 중 유일하게 제 역할을 하는 것이 RBAC 다.** 그러므로 실질적인 > 방어선은 「누가 `get secrets` 를 할 수 있는가」이며, > **관리자 권한을 가진 사람에게는 아무 방어가 없다.** > > A-0 의 관측 스택에서 `nodes/proxy` 서브리소스를 따로 줘야 했던 것처럼, > Secret 접근도 **리소스 단위로 나눌 수 있다.** ## 4-5. 네 경로 정리 | # | 경로 | 감춰지는가 | 무엇이 뚫나 | |---|---|---|---| | ① | `get -o jsonpath \| base64 -d` | **아니다** | 클러스터 접근 권한 | | — | `describe secret` | 값을 숨긴다 | **그래서 안전하다고 착각한다** | | ② | 저장 파일(`state.db`) | **아니다.** 암호화 꺼짐 | 노드 디스크·백업·스냅샷 | | ③ | 파드 안 | **아니다.** 평범한 환경변수 | `exec` · `/proc` · 크래시 덤프 | | ④ | RBAC | **막는다** | 관리자 권한 | **「Secret 이니까 안전하다」는 네 가지 중 하나(RBAC)만 맞다.** 그리고 ②·③ 은 **쿠버네티스 API 를 한 번도 거치지 않고** 평문에 닿는다. --- # 5. 복구 ## 5-1. 카나리아를 지운다 **하기** ```bash sudo kubectl -n keycloak-lab delete secret d3-canary sudo kubectl -n keycloak-lab get secret ``` **형태** ``` secret "d3-canary" deleted ``` **어디를 봐야 하는가** — 1-1 의 목록으로 돌아왔는가. 세 개다. ## 5-2. ★ 지웠다고 파일에서 없어지지는 않는다 **확인** — **미검증**. 이 실험은 삭제 후를 재지 않았다 ```bash sudo grep -c 'd3-canary-zq7v-do-not-use' /var/lib/rancher/k3s/server/db/state.db sudo grep -c 'd3-canary-zq7v-do-not-use' /var/lib/rancher/k3s/server/db/state.db-wal ``` **어디를 봐야 하는가** — 0 이 나오면 「이 시점에 이 형태로는 안 보인다」이고, 0 이 아니면 **지운 Secret 의 평문이 아직 파일에 남아 있는 것**이다. 어느 쪽이든 **4-2 의 결론은 안 바뀐다** — 판정은 이미 `2` 에서 났다. **이 결과가 의미하는 것** — 데이터베이스 파일은 지운 행의 자리를 즉시 0으로 덮어쓰지 않는다. **「Secret 을 지웠다」와 「그 값이 디스크에서 사라졌다」는 다른 사건**이며, 비밀이 유출됐을 때 실제로 해야 하는 일은 삭제가 아니라 **회전(rotation)**인 이유가 여기 있다 — B-6·B-7 의 주제다. ## 5-3. 원상복구 확인표 | 항목 | 명령 | 돌아왔을 때 | |---|---|---| | 카나리아 | `sudo kubectl -n keycloak-lab get secret d3-canary` | `NotFound` | | Secret 목록 | `sudo kubectl -n keycloak-lab get secret` | 세 개 | | 파드 | `sudo kubectl -n keycloak-lab get pods` | 전부 `Running` (아무것도 안 건드렸다) | | **터미널** | `history \| tail -40` | **비밀번호가 찍힌 줄이 어디까지 남았는지 본다** | > **이 실험의 진짜 뒷정리는 스크롤백이다.** 4-1 을 실제 비밀로 쳤다면 그 값이 > 터미널 버퍼와 셸 히스토리에 남아 있다. 실험대 값이라 지금은 상관없지만, > **같은 절차를 운영에서 하면 그게 유출 경로가 된다.** --- # 6. 그래서 무엇을 해야 하는가 ``` 지금: 매니페스트에 stringData 평문 → git 에 커밋되면 끝 k3s 저장소 암호화 꺼짐 파드 환경변수 ``` | 단계 | 얻는 것 | 이 실험대 | |---|---|---| | ① 매니페스트에서 값을 빼고 **`.example` 만 커밋** | git 유출을 막는다 | 안 함 | | ② **k3s `--secrets-encryption`** 활성화 | 노드 디스크 유출을 막는다 | 안 함 (**미검증**) | | ③ 환경변수 대신 **볼륨 마운트** | 프로세스·덤프 유출을 줄인다 | 안 함 | | ④ **SealedSecret / 외부 KMS** | 매니페스트에 암호문만 남는다 | 안 함 | | ⑤ **RBAC 최소화** | 유일하게 이미 동작하는 방어선을 좁힌다 | 기본값 그대로 | **이 실험대는 ①~④ 중 아무것도 안 하고 있다.** 실험 목적으로는 의도적이지만, **그 사실을 기록해두지 않으면 그대로 운영에 옮겨간다.** 값 이름에 `change-me` 를 넣어 둔 것이 그 최소한의 표시다. --- # 막히면 | 증상 | 원인 | 확인 | |---|---|---| | `grep` 이 `0` 인데 안전하다고 읽힌다 | **`0` 은 「이 파일의 이 시점에 이 형태로는 못 찾았다」** | `-wal` 과 `strings` 로 한 번 더 — 4-2 | | `grep` 이 `Binary file matches` 만 찍는다 | 바이너리 파일이다 | `-c` 를 쓴다(개수만). 값을 안 띄우는 이점도 있다 | | `k3s secrets-encrypt` 가 없다 | **서버 노드가 아니다** | `kc-lab-1`(control-plane)에서 친다 | | `state.db` 가 `Permission denied` | root 전용 디렉터리 | 게스트 sudo 는 무암호다. `sudo` 를 붙인다 | | `exec deploy/bff` 가 실패한다 | 파드가 종료 중이거나 여럿이다 | `--field-selector=status.phase=Running` 으로 이름을 고른다 — 4-3 | | `auth can-i` 가 `yes` 라고 한다 | 그 SA 에 Role 이 붙어 있다 | `get rolebinding -o wide` 로 누가 줬는지 본다 | | 값이 `0 bytes` 로 나온다 | Secret 은 있는데 키가 비었다 | `describe` 의 바이트 수를 본다 — 배포 실패의 흔한 원인 | | 비밀번호를 화면에 찍어 버렸다 | 4-1 을 실제 값으로 쳤다 | 스크롤백·히스토리를 지우고, **운영이면 회전한다** | --- # 다음 | 실험 | D-3 이 남긴 것 | |---|---| | [B-6](../../experiment-b6-key-rotation.md) key 회전 | **key 를 Secret 에 두면 이 실험의 결론이 그대로 적용된다** | | [B-7](../../experiment-b7-cookie-secret-rotation.md) 쿠키 비밀 회전 | 유출 대응은 삭제가 아니라 **회전**이다 — 5-2 | | [D-1](d1-backup-restore.md) 백업 | **덤프에도 같은 문제가 있다.** 백업을 잘 챙길수록 비밀도 잘 복사된다 | | [D-4](d4-certificate-renewal.md) 인증서 | **개인키(`privkey.pem`)도 같은 비밀 관리 문제다** | | 운영 | **RBAC 이 유일하게 동작하는 방어선이다.** 관리자에게는 아무 방어가 없다 |