쿠버네티스 운영 실무 · ServiceAccount 와 토큰 · 실습
토큰의 수명과 대상 다루기
목표
파드의 신원이 어떻게 발급되고 어떤 수명과 대상을 갖는지 직접 확인하고, 필요 없는 토큰은 아예 주지 않는 설정을 적용합니다.
왜 중요한가
컨테이너가 뚫렸을 때 공격자가 가장 먼저 찾는 것이 /var/run/secrets/kubernetes.io/serviceaccount 의 토큰입니다. 그래서 이 모듈의 핵심 질문은 두 개입니다 — 이 파드가 정말 토큰이 필요한가, 그리고 그 토큰은 언제 만료되는가. 1.24 이전의 방식은 두 질문 모두에 나쁜 답을 갖고 있었습니다. 어카운트를 만들면 만료 없는 토큰이 자동으로 생겼고, 파드는 필요 여부와 무관하게 그것을 마운트받았습니다. 지금은 파드가 받는 토큰이 수명을 갖고 kubelet 이 갱신하며, automountServiceAccountToken: false 로 아예 끌 수도 있습니다. 여기에 audience 가 더해지면 토큰이 "어디에 쓸 것인지"까지 담아, Vault 에 주려고 발급한 토큰이 다른 곳에서 재사용되는 것을 막습니다. 이 구조가 그대로 클라우드 워크로드 아이덴티티로 이어져 정적 키 자체를 없애는 설계가 됩니다. 마지막으로 잊지 마세요. 신원을 만들었다고 권한이 생기지는 않습니다. 권한은 여전히 RBAC 가 정합니다.
단계
1. 네임스페이스 sa-lab 을 만들고 그 안에 ServiceAccount payments 를 만드세요. 이 어카운트에는 자동 생성된 Secret 이 붙어 있으면 안 됩니다. 그리고 /root/ops/sa/out/sa-note.txt 에 왜 요즘은 시크릿이 자동으로 생기지 않는지를 한 줄로 적으세요(1.24 이후 단기 토큰으로 바뀐 배경, 만료·수명 같은 표현이 들어가야 합니다).
2. sa-lab 에 파드 payments-api 를 만들되 spec.serviceAccountName 을 payments 로 지정하세요. 기본 설정이면 서비스 어카운트 토큰이 담긴 projected 볼륨이 자동으로 붙습니다.
3. ServiceAccount locked 를 automountServiceAccountToken: false 로 만드세요. 그리고 파드 hardened 를 만들되 spec.serviceAccountName 은 locked, spec.automountServiceAccountToken 은 false 로 하세요. 이 파드에는 토큰 볼륨이 하나도 붙어 있으면 안 됩니다.
4. payments 어카운트의 토큰을 수명 1시간(3600초), 대상 vault 로 발급하고, 그 JWT 의 페이로드를 디코딩한 JSON 을 /root/ops/sa/out/token.json 에 저장하세요. 이 JSON 에는 sub 가 system:serviceaccount:sa-lab:payments 이고, aud 가 비어 있지 않으며, exp 와 iat 의 차이가 약 3600 초이고, kubernetes.io 클레임이 들어 있어야 합니다.
5. sa-lab 에 파드 projected-demo 를 만드세요. spec.serviceAccountName 은 payments 로 하고, projected 볼륨에 serviceAccountToken 소스를 두되 audience: vault, expirationSeconds: 3600, path: vault-token 으로 지정하세요. 첫 번째 컨테이너는 이 볼륨을 /var/run/secrets/vault 경로로 마운트해야 합니다.
6. sa-lab 에 Secret payments-legacy-token 을 만드세요. type 은 kubernetes.io/service-account-token, 어노테이션 kubernetes.io/service-account.name 은 payments 입니다. 그리고 /root/ops/sa/out/legacy-note.txt 에 이 방식이 왜 권장되지 않는지를 60바이트 이상, 두 문장 정도로 적으세요(만료가 없다는 점과 폐기·로테이션 부담이 들어가야 합니다).
7. payments 어카운트에 최소 권한만 주세요. sa-lab 에서 ConfigMap 을 get 과 list 할 수 있어야 하고, Secret 을 읽거나 ConfigMap 을 삭제할 수는 없어야 합니다. 권한은 payments 를 주체로 하는 RoleBinding 으로 연결하세요.
8. /root/ops/sa/out/sa-audit.json 을 만드세요. pods 는 배열이며 sa-lab 의 실제 파드 개수와 정확히 일치해야 합니다. 각 원소는 name, service_account, automount 세 필드를 갖습니다. payments-api 의 service_account 는 payments 여야 하고, hardened 의 automount 는 false 여야 하며, service_account 가 default 인 파드는 하나도 없어야 합니다. 마지막으로 recommendations 배열에 개선 권고를 2개 이상 적으세요.
참고
- 토큰 발급은
kubectl create token <어카운트> -n sa-lab --duration=3600s --audience=vault입니다. JWT 는헤더.페이로드.서명구조라 두 번째 조각만 떼어 디코딩하면 됩니다. base64url 은 길이를 4의 배수로 패딩해야 하므로python3의base64.urlsafe_b64decode를 쓰면 편합니다. - 파드의 볼륨 구조는
kubectl get pod <이름> -n sa-lab -o jsonpath='{.spec.volumes}'로 확인할 수 있습니다. 자동 주입된 토큰 볼륨은 이름이kube-api-access-로 시작합니다. - 8번의 파드 목록은
kubectl get pods -n sa-lab -o json에서jq로 만드는 편이 안전합니다.automount필드가 명시되지 않은 파드는 기본값(true)으로 적으세요. - 흔한 실수 1: 3번에서 파드에만 끄고 어카운트에는 걸지 않는 것. 이 실습은 두 곳 모두 확인합니다.
- 흔한 실수 2: 5번과 3번의 파드가
default어카운트를 쓰는 것. 8번 감사에서 걸립니다. 파드마다 전용 어카운트를 지정하세요. - 흔한 실수 3: 4번에서 토큰 문자열 자체를 파일에 저장하는 것. 저장할 것은 디코딩된 페이로드 JSON 입니다.
- 실습 파드는 실습마다 새로 뜨므로 앞 실습에서 만든 클러스터 상태는 남아 있지 않습니다. 네임스페이스와 어카운트는 이 실습 안에서 직접 만드세요. 운영 절차를 기억이 아니라 런북과 매니페스트로 남겨야 하는 이유가 바로 이것입니다.
단계 8개
- 전용 서비스 어카운트 만들기
- 파드에 전용 어카운트 지정하기
- 토큰 자동 마운트 끄기
- 단기 토큰 발급하고 페이로드 뜯어보기
- 대상이 지정된 projected 토큰 쓰기
- 레거시 장기 토큰 시크릿 만들고 위험 정리하기
- 어카운트에 최소 권한만 주기
- 어카운트 사용 현황 감사하기