k3s 저장소를 열어 Secret 을 지킨다
이 실습은 VM 에서 돕니다
우분투 24.04 VM 한 대입니다. root 이고 systemd 와 커널 기능이 그대로 있습니다. 실습 파드에는 capability 가 없어 이 실습이 다루는 설정을 바꿀 수 없기 때문에 VM 으로 돕니다. 처음 뜨는 데 몇 분이 걸립니다. 세션이 끝나면 VM 은 통째로 사라지므로 남기고 싶은 것은 화면에서 복사해 두세요.
목표
쿠버네티스 Secret 이 어디서 새는지 두 자리를 직접 엽니다. 하나는 저장소(평문으로 저장되는가), 다른 하나는 권한(누가 읽을 수 있는가)입니다. 둘을 막고, 값을 회전하고, 누가 읽으려 했는지 감사 로그로 남깁니다.
왜 중요한가
Secret 은 이름과 달리 기본으로는 비밀이 아닙니다. 매니페스트의 값은 base64 일 뿐이고, 클러스터 저장소(etcd 나 k3s 의 kine)에도 기본으로는 평문 그대로 들어갑니다. 저장소 백업 파일 하나, 노드 디스크 하나가 새면 모든 Secret 이 함께 샙니다.
권한도 같습니다. "목록만 보면 되는" 계정에 편의상 클러스터 전체 읽기를 주면, 그 계정의 토큰 하나가 모든 네임스페이스의 비밀번호를 여는 열쇠가 됩니다. 저장 암호화는 디스크를, RBAC 은 API 를 지킵니다. 둘은 서로를 대신하지 못합니다.
단계
db-cred의 password 를 디코딩해/root/k8s/01-decoded.txt에 저장합니다.- 저장소의 최신 행에서 평문을 찾아
/root/k8s/02-datastore.txt에 적습니다. - 보고용 계정으로 같은 값을 읽어
/root/k8s/03-reporter-read.txt에 저장합니다. - 보고용 계정의 권한을
shop의 pods·configmaps 읽기로 줄입니다. - 저장 암호화를 켜고 기존 Secret 까지 다시 씁니다.
- 값을 회전하고 앱이 새 값을 쓰게 합니다.
- Secret 접근을 Metadata 수준으로 감사하고, 거절된 시도가 남는지 봅니다.
참고
- 이 VM 에는 k3s 한 대가 떠 있습니다.
kubectl이 바로 됩니다. 저장소는/var/lib/rancher/k3s/server/db/state.db입니다. - k3s 를 다시 띄우면 API 가 돌아오기까지 10초 남짓 걸립니다.
kubectl get --raw /readyz로 기다리세요. - 흔한 실수: 5단계에서 켜기만 하고 기존 Secret 을 다시 쓰지 않는 것. 새로 쓰는 것만 암호화됩니다.
- 흔한 실수: 7단계에서 config.yaml 을 통째로 덮어 앞에서 켠 설정을 지우는 것.
base64 는 암호화가 아니다
shop 네임스페이스의 Secret db-cred 에서 password 값을 꺼내 디코딩한 평문을 /root/k8s/01-decoded.txt 에 저장하세요.
Secret 의 data 필드는 base64 로 적혀 있을 뿐입니다. jsonpath 로 값 하나를 꺼내 base64 -d 에 넘기면 됩니다. Secret 을 읽을 권한이 곧 평문을 읽을 권한이라는 뜻입니다.
저장소에는 무엇이 들어 있나
k3s 의 저장소(/var/lib/rancher/k3s/server/db/state.db, kine 테이블)에서 /registry/secrets/shop/db-cred 의 최신 행을 찾아, 키 이름과 그 값 안에 보이는 평문 비밀번호를 /root/k8s/02-datastore.txt 에 적으세요.
kine 테이블에는 name 과 value 열이 있고, 같은 이름의 행이 리비전마다 쌓입니다. 최신 행은 id 가 가장 큰 것입니다. value 는 바이너리라 cast(value as text) 나 파이썬으로 읽으세요.
보고용 계정이 무엇을 읽는가
보고용 서비스 계정(system:serviceaccount:shop:reporter)의 신원으로 db-cred 를 읽어 디코딩한 값을 /root/k8s/03-reporter-read.txt 에 저장하세요. 이 계정은 파드와 설정 목록만 읽으면 되는 계정입니다.
관리자는 --as 로 다른 신원을 흉내 낼 수 있습니다. 이 계정에 묶인 ClusterRoleBinding 을 보면 왜 읽히는지 보입니다.
필요한 것만 남긴다
보고용 계정의 클러스터 전체 읽기 권한을 없애고, shop 네임스페이스에서 pods 와 configmaps 만 get·list 할 수 있는 Role 과 RoleBinding 을 주세요. Secret 은 어느 네임스페이스에서도 읽을 수 없어야 합니다.
ClusterRoleBinding 은 모든 네임스페이스에 적용됩니다. 네임스페이스 하나의 권한은 Role·RoleBinding 으로 줍니다. 확인은 kubectl auth can-i <동사> <자원> -n <ns> --as <신원> 으로 합니다.
저장소에서 암호화한다
k3s 의 Secret 저장 암호화를 켜고, 이미 있는 Secret 까지 다시 써서 db-cred 의 최신 행이 k8s:enc: 로 시작하게 하세요. k3s secrets-encrypt status 가 Enabled 여야 합니다.
암호화는 켠 뒤에 새로 쓰는 것에만 적용되므로, 이미 저장된 Secret 은 다시 써야 암호문이 됩니다. k3s 는 서버 설정(/etc/rancher/k3s/config.yaml 의 secrets-encryption)으로 기능을 켜고 서버를 다시 띄운 뒤, 키를 만들고 기존 Secret 을 다시 쓰는 하위 명령(k3s secrets-encrypt rotate-keys)을 씁니다. enable 하위 명령은 이 버전에서 암호화하지 않는 공급자(identity)만 등록하니 주의하세요.
값을 바꾸고 앱이 따라오게 한다
암호화 전의 평문은 옛 리비전 행과 보고용 계정의 손에 남아 있습니다. db-cred 의 password 를 새 값으로 바꾸고, shop/api 가 새 값을 쓰게 하세요. 파드 안의 /etc/db/password 가 새 값이어야 합니다.
Secret 을 바꾸는 가장 안전한 방법은 매니페스트를 다시 만들어 apply 하는 것입니다(--dry-run=client -o yaml). 마운트된 Secret 은 시간이 지나면 갱신되지만, 앱이 기동할 때만 읽는다면 다시 띄워야 합니다.
누가 Secret 을 읽으려 했는가
감사 정책 /etc/rancher/k3s/audit-policy.yaml 을 만들어 Secret 접근을 Metadata 수준으로 남기고, k3s 가 그 정책으로 /var/log/k3s-audit.log 에 감사 로그를 쓰게 하세요. 그다음 보고용 계정으로 db-cred 를 읽으려 해 보고(거절됩니다) 그 기록이 남는지 확인하세요.
k3s 의 API 서버 인자는 /etc/rancher/k3s/config.yaml 의 kube-apiserver-arg 목록으로 넘깁니다. 앞 단계에서 켠 설정을 지우지 않게 조심하세요. Secret 을 Request 나 RequestResponse 수준으로 남기면 감사 로그가 비밀값을 그대로 적습니다.