LabHub
배우기 러닝패스 코스

KCSA — Kubernetes Security Associate

Open etcd and the Password Falls Out in Plaintext

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

클러스터의 유일한 진실 저장소인 etcd 를 직접 열어, Secret 이 평문으로 저장돼 있음을 눈으로 확인합니다. 그리고 RBAC 가 막아 주는 것은 apiserver 를 지나는 API 경로일 뿐, etcd 에 직접 붙으면 그 경계를 우회한다는 사실을 대비해 관찰합니다.

왜 중요한가

쿠버네티스의 모든 오브젝트는 결국 etcd 에 저장됩니다. Secret 의 data 값이 base64 로 보이니 마치 가려진 듯하지만, base64 는 인코딩이지 암호화가 아닙니다. 저장소 암호화(encryption at rest)를 켜지 않으면 etcd 에 닿을 수 있는 사람은 누구나 비밀번호 원문을 그대로 읽습니다.

한편 RBAC 는 강력하지만 그 통제는 apiserver 를 지나는 요청에만 적용됩니다. 노드나 etcd 데이터 디렉터리, 혹은 인증이 걸리지 않은 etcd 포트에 직접 접근하는 경로에는 RBAC 가 개입하지 못합니다. 그래서 etcd 자체를 TLS·상호인증·저장소 암호화로 별도로 지켜야 합니다. 이 실습은 "왜 그것들이 필요한가" 를 방어가 없을 때의 모습으로 먼저 보여 줍니다.

단계

  1. 네임스페이스 kcsa-etcd 를 만듭니다.
  2. Secret db-cred(password)와 ConfigMap app-cfg(mode)를 만듭니다.
  3. 인증서 없이 etcd 엔드포인트에 붙어 상태를 파일로 남깁니다.
  4. etcd 에서 Secret 을 직접 읽어 비밀번호 원문을 파일로 뽑습니다.
  5. 그 값이 암호화가 아니라 평문임을 판정해 기록합니다.
  6. ConfigMap 만 읽는 최소 권한 ServiceAccount 로 RBAC 경계를 확인합니다.
  7. etcd 를 지키는 세 통제(client TLS·peer TLS·저장소 암호화)를 정리합니다.
  8. 관찰 결과를 /root/kcsa-etcd/report.txt 에 장부로 남깁니다.

참고

관찰 대상 네임스페이스를 연다

네임스페이스 kcsa-etcd 를 만듭니다. 이 안의 Secret 을 etcd 에서 직접 들여다볼 것입니다.

네임스페이스는 kubectl create namespace 로 만듭니다. 여러 번 돌려도 안전하게 하려면 --dry-run=client -o yaml | kubectl apply -f - 를 씁니다.

비밀번호 하나를 클러스터에 맡긴다

네임스페이스 kcsa-etcd 에 generic Secret db-cred 을 만들되 리터럴 password=Pl4inEtcd! 를 담습니다. 같은 네임스페이스에 ConfigMap app-cfg 를 만들고 키 mode=prod 를 담습니다.

kubectl create secret generic <이름> --from-literal=키=값 으로 Secret 을, kubectl create configmap <이름> --from-literal=키=값 으로 ConfigMap 을 만듭니다. Secret 의 data 값은 base64 로 인코딩돼 저장됩니다(암호화가 아닙니다).

인증서 없이 etcd 에 그대로 닿는다

etcdctl 로 etcd 엔드포인트 상태를 표로 뽑아 /root/kcsa-etcd/etcd-status.txt 에 저장합니다. 클라이언트 인증서 없이 127.0.0.1:2379 에 붙는다는 것을 확인하세요.

ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 endpoint status -w table 로 상태를 봅니다. 이 클러스터의 etcd 는 TLS·인증이 걸려 있지 않아 인증서 옵션 없이 바로 응답합니다. 출력을 파일로 리다이렉트하세요.

저장소 바닥에서 비밀번호를 줍는다

etcd 에 저장된 db-cred Secret 을 직접 읽어, 그 안의 비밀번호 문자열만 /root/kcsa-etcd/leaked.txt 에 적습니다(값만, 여분의 개행 없이). 파일 내용은 실제 비밀번호와 정확히 같아야 합니다.

Secret 은 etcd 키 /registry/secrets/<네임스페이스>/<이름> 아래에 있습니다. etcdctl get <키> 결과는 이진이 섞여 있으니 grep -a 로 텍스트만 걸러 냅니다. 값이 무엇인지는 여러분이 직접 찾아내야 합니다 — Secret 의 data 를 base64 디코드해 보면 원문을 알 수 있고, 그 문자열이 etcd 원본에도 그대로 들어 있음을 확인하세요.

이건 암호화가 아니라고 판정한다

디스크(etcd)에 놓인 그 값이 암호화돼 있는지 판정합니다. 암호화돼 있지 않다면 /root/kcsa-etcd/encrypted.txt 에 한 단어 no 만 적습니다.

base64 는 인코딩이지 암호화가 아닙니다. etcd 원본에서 비밀번호 원문이 grep 으로 그대로 잡힌다면, 저장소 암호화(encryption at rest)가 꺼져 있다는 뜻입니다. 판정 결과를 소문자 한 단어로 적으세요.

RBAC 는 API 경로만 지킨다

네임스페이스 kcsa-etcd 에 ServiceAccount apponly 와 Role cfg-only(ConfigMap 에 대한 get·list 만)를 만들고, 이 Role 을 apponly 에 묶습니다. 그러면 apponly 는 ConfigMap 은 읽을 수 있어도 Secret 은 읽지 못해야 합니다.

kubectl create role <이름> --verb=get --verb=list --resource=configmaps 로 최소 권한 Role 을, kubectl create rolebinding 으로 ServiceAccount 에 묶습니다. kubectl auth can-i <동사> <자원> -n <ns> --as=system:serviceaccount:<ns>:<sa> 로 실제 판정을 확인할 수 있습니다.

etcd 를 지키는 세 가지를 적는다

읽기 자료를 근거로, etcd 를 보호하는 세 가지 통제를 /root/kcsa-etcd/mitigations.txt 에 정확히 세 줄로 적습니다 — client-tls=required, peer-tls=required, encryption-at-rest=required.

etcd 보안은 세 축입니다: 클라이언트↔etcd TLS·상호인증, etcd 노드 간(peer) TLS, 그리고 apiserver 의 저장소 암호화(EncryptionConfiguration). 세 키를 모두 required 로 적으세요. 키 이름과 철자를 지시문 그대로 맞춰야 합니다.

무엇을 관찰했는지 장부로 남긴다

/root/kcsa-etcd/report.txt 에 정확히 네 줄을 적습니다 — etcd-auth=none, secret-on-disk=plaintext, rbac-blocks-apponly=yes, etcd-bypasses-rbac=yes. 값은 앞 단계에서 관찰한 실제 상태와 일치해야 합니다.

채점기는 이 네 줄을 실제 상태와 대조합니다. etcd 가 인증 없이 붙었는지(none), Secret 이 평문으로 놓였는지(plaintext), apponly 가 Secret 을 못 읽는지(yes), 그리고 etcd 직접 접근이 그 RBAC 경계를 우회하는지(yes)를 앞 단계 결과로 채우세요.