CKS — 쿠버네티스 보안 전문가 · 열린 터미널의 권한과 사고 대응 · 이론
권한 회수와 연결 종료는 같은 사건이 아니다
한 줄 요약
권한 회수는 새 API 요청을 막는 통제이고, 이미 실행 중인 명령의 종료는 별도로 확인할 대응입니다.
왜 이게 필요했나
운영자가 임시로 터미널 권한을 열었습니다. 작업이 끝나 Role에서 exec 권한을 지우고,
새 터미널이 열리지 않는 것까지 확인했습니다. 이때 기존 창도 더 이상 명령을 실행할 수
없을까요? 같은 화면에 보이는 터미널이라도 새 연결을 만드는 요청과 이미 열린 연결을
통해 보내는 입력은 서로 다른 경로입니다. 대응 완료를 너무 빨리 선언하면 실행 중인
세션을 놓칠 수 있습니다.
LabHub의 전용 k3s v1.36.4 탐침에서는 새 exec가 거부된 뒤에도 기존 스트림이 그 이후에
생성한 새 입력에 응답했습니다. 이전 출력이 화면에 남아 있다는 뜻이 아닙니다. 권한 회수
이후 만들어 보낸 서로 다른 난수와 ACK를 맞춰 실제 실행을 관찰했습니다. 이것은 확인한
버전·경로의 결과이며 모든 프록시나 세션 관리자의 동작을 보장하지 않습니다.
어떻게 동작하나
Kubernetes RBAC의 Role은 namespace 범위에서 요청을 허용합니다. 권한은 합산되므로
한 Role을 좁혀도 다른 바인딩이 같은 권한을 주면 요청은 여전히 허용될 수 있습니다.
그래서 YAML만 보지 않고 실제 요청의 성공·거부를 함께 확인해야 합니다.
Pod 조회와 컨테이너 실행도 다른 권한입니다. Pod는 pods, 실행은 pods/exec라는
subresource로 지정합니다. 일반 자원 생성과 달리 exec처럼 대상 이름이 URL에 있는
subresource는 resourceNames로 특정 Pod에 한정할 수 있습니다. 이 실습에서는 target과
control의 GET은 허용하고 exec는 target에만 허용합니다. wildcard를 쓰면 대조군까지
열리므로 문제를 좁게 재현할 수 없습니다. exec에 get·create를 명시하는 것은 클라이언트의
연결 방식 차이를 고려하기 위한 것이며, 모든 일반 GET이 프로세스를 실행한다는 뜻은 아닙니다.
터미널은 연결을 설정한 뒤 stdin·stdout·stderr를 오랫동안 주고받습니다. 공식 스트리밍
문서는 HTTP 연결을 승격해 양방향 채널로 사용하는 구조를 설명합니다. 따라서 새 요청의
거부와 기존 채널의 종료를 같은 사건으로 취급하지 않습니다. auth can-i는 권한을
질문하는 도구이지 기존 연결을 끊는 명령도, 실제 exec 성공을 대신하는 증거도 아닙니다.
이번 실습의 --as는 관리자 인증서로 로그인한 요청을 서비스 계정 신원으로 대리하는
impersonation입니다. API 서버는 원 요청자를 인증하고 대리 권한을 확인한 다음,
대리 신원으로 인가합니다. 이 방식으로 RBAC를 검증할 수 있지만 서비스 계정 bearer
토큰의 만료·폐기를 검증한 것은 아닙니다. 그 주제는 KCSA의 실제 토큰 실습에서 다룹니다.
현장에서 만나는 모습
사고 대응에서는 차단과 보존이 충돌합니다. 먼저 Pod를 지우면 실행 흔적과 임시 파일을
잃을 수 있고, 조사만 하며 기다리면 위험한 실행이 계속될 수 있습니다. 어떤 증거를
어디에 남기고 어떤 업무 영향을 감수할지 먼저 정해야 합니다. 실습의 containment.json은
대상 UID·보존할 대조군 UID·직전 증거 해시·조치 범위를 기록하는 작은 대응 계획입니다.
이 해시는 사고로 파일이 바뀐 것을 찾는 수단이며 root 사용자가 악의적으로 전체 자료를
다시 만든 경우까지 방어하는 전자서명이 아닙니다.
이름만 보고 지우는 것도 위험합니다. 삭제와 재생성 사이에 같은 이름으로 다른 Pod가
들어올 수 있기 때문입니다. UID 전제 조건은 조사한 대상과 실제 변경할 대상을 묶습니다.
실습은 정상 종료 유예를 주고 Pod 부재와 기존 exec 프로세스의 종료를 모두 관찰합니다.
API 객체의 삭제 성공만으로 노드의 실제 프로세스가 끝났다고 단정하지 않습니다.
운영 대응에서는 소유 Deployment가 Pod를 다시 만들거나 GitOps가 권한을 되돌릴 수도
있습니다. 이 실습은 건강한 단일 VM 안의 독립 Pod만 사용하므로 해당 자동 복구까지
모사하지 않습니다. 실제 서비스에서는 원하는 상태를 가진 컨트롤러·Git 설정도 함께
고쳐야 합니다. 대조군을 남기는 이유는 요청 차단이 전체 서비스 고장 때문인지 구분하고,
조치 범위가 넓어지지 않았는지 확인하기 위해서입니다.
복구가 끝나도 권한을 원래대로 전부 열지 않습니다. 새 target의 UID·Ready와 GET 성공을
확인하고, exec는 계속 거부되는지 점검합니다. 서비스 복구와 임시 관리 권한 재부여는
서로 다른 승인입니다. 대응 보고서에는 신규 연결 거부, 기존 연결의 추가 응답, 종료 및
복구 결과를 각각 적어 한 가지 초록불로 나머지를 대신하지 않습니다.
다음 실습에서 할 것
scope를 조사하고 좁은 Role을 작성한 뒤 실제 exec 스트림을 엽니다. 권한을 회수하고
신규 요청의 거부와 기존 연결의 새 응답을 비교합니다. 증거를 보존한 뒤 지정 UID만
종료하며, 대조군과 새 Pod의 권한 경계까지 확인합니다. helper는 무해한 ACK 프로그램만
실행합니다. 검사 파일은 영구 관측을 읽으므로 마지막 단계 이후 재채점해도 과거의
성공을 잃지 않습니다. 중간 실행이 불확실하게 끊기면 자동으로 재시작하지 않고 자료를
내려받아 새 세션에서 재현하도록 안내합니다.
공식 문서
- [RBAC와 subresource](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
- [User impersonation](https://kubernetes.io/docs/reference/access-authn-authz/user-impersonation/)
- [스트리밍 연결의 구조](https://kubernetes.io/blog/2024/08/20/websockets-transition/)