목록으로

Programming Notes

Kubernetes에서 프로덕션 디버깅 보안 확보하기

프로덕션 디버깅 중에는 `cluster-admin` (관리자 수준의 접근 권한을 부여하는 ClusterRole), 공유 배스천(bastion)/점프 박스(jump box), 또는 장기 SSH 키와 같은 광범위한 접근 권한이 가장 빠른 해결책인 경우가 많습니다. 이는 당장에는...

프로덕션 디버깅 중에는 `cluster-admin` (관리자 수준의 접근 권한을 부여하는 ClusterRole), 공유 배스천(bastion)/점프 박스(jump box), 또는 장기 SSH 키와 같은 광범위한 접근 권한이 가장 빠른 해결책인 경우가 많습니다. 이는 당장에는 효과적이지만, 감사가 어려워지고 임시 예외 사항이 일상적인 절차가 되는 두 가지 일반적인 문제점을 안고 있습니다.

이 게시물에서는 기존 Kubernetes 환경에 최소한의 도구 변경으로 적용할 수 있는 좋은 사례에 대한 제 권장 사항을 제공합니다:

  • RBAC를 통한 최소 권한
  • 단기, ID 기반 자격 증명
  • 클라우드 네이티브 디버깅을 위한 SSH 방식의 핸드셰이크 모델

프로덕션 디버깅 워크플로 보안을 강화하는 좋은 아키텍처는 Just-In-Time 보안 셸 게이트웨이(일반적으로 클러스터 내에서 온디맨드 파드로 배포됨)를 사용하는 것입니다. 이 게이트웨이는 임시 접근을 실제로 임시로 만들어주는 SSH 방식의 “정문” 역할을 합니다. 단기, ID 기반 자격 증명으로 인증하고 게이트웨이에 세션을 설정하면, 게이트웨이는 Kubernetes API와 RBAC를 사용하여 `pods/log`, `pods/exec`, `pods/portforward`와 같이 사용자가 수행할 수 있는 작업을 제어합니다. 세션은 자동으로 만료되며, 게이트웨이 로그와 Kubernetes 감사 로그는 공유 배스천 계정이나 장기 키 없이 누가, 무엇을, 언제 접근했는지 기록합니다.

1) Kubernetes RBAC 위에 접근 브로커 사용하기

RBAC는 Kubernetes에서 누가 무엇을 할 수 있는지를 제어합니다. 많은 Kubernetes 환경은 주로 RBAC에 의존하여 권한을 부여하지만, Kubernetes는 웹훅(Webhook) 권한 부여와 같은 다른 권한 부여 모드도 지원합니다. Kubernetes RBAC로 직접 접근을 강제하거나, 내부적으로는 Kubernetes 권한에 의존하는 접근 브로커를 클러스터 앞에 둘 수 있습니다. 두 모델 모두에서 Kubernetes RBAC는 Kubernetes API가 허용하는 사항과 범위에 대한 진실의 원천(source of truth)으로 남습니다.

접근 브로커는 RBAC가 잘 다루지 못하는 제어 기능을 추가합니다. 예를 들어, 요청이 자동 승인될지 수동 승인이 필요할지, 사용자가 특정 명령을 실행할 수 있는지, 세션에서 어떤 명령이 허용되는지 등을 결정할 수 있습니다. 또한 그룹 멤버십을 관리하여 개별 사용자 대신 그룹에 권한을 부여할 수 있습니다. Kubernetes RBAC는 `pods/exec`와 같은 작업을 허용할 수 있지만, `exec` 세션 내부에서 실행되는 명령을 제한할 수는 없습니다.

이 모델에서는 Kubernetes RBAC가 사용자 또는 그룹(예: 단일 네임스페이스 내의 온콜 팀)에 대해 허용되는 작업을 정의합니다. 접근 규칙은 그룹이나 ServiceAccount에만 권한을 부여하도록 정의하고, 개별 사용자에게는 절대로 부여하지 않을 것을 권장합니다. 브로커 또는 ID 공급자는 필요에 따라 해당 그룹에 사용자를 추가하거나 제거합니다.

브로커는 또한 대화형 세션에서 허용되는 명령, 자동 승인될 수 있는 요청과 수동 승인이 필요한 요청 등 추가 정책을 적용할 수 있습니다. 이 정책은 JSON 또는 XML 파일로 존재하며 코드 검토를 통해 유지 관리될 수 있으므로, 업데이트는 정식 풀 리퀘스트를 거쳐 다른 프로덕션 변경 사항과 마찬가지로 검토됩니다.

예시: 네임스페이스 온콜 디버그 역할(Role)

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: oncall-debug
 namespace: <namespace>
rules:
 # 실행 중인 내용 파악
 - apiGroups: [""]
 resources: ["pods", "events"]
 verbs: ["get", "list", "watch"]

 # 로그 읽기
 - apiGroups: [""]
 resources: ["pods/log"]
 verbs: ["get"]

 # 대화형 디버깅 작업
 - apiGroups: [""]
 resources: ["pods/exec", "pods/portforward"]
 verbs: ["create"]

 # 롤아웃/컨트롤러 상태 이해
 - apiGroups: ["apps"]
 resources: ["deployments", "replicasets"]
 verbs: ["get", "list", "watch"]

 # 선택 사항: kubectl debug 임시 컨테이너 허용
 - apiGroups: [""]
 resources: ["pods/ephemeralcontainers"]
 verbs: ["update"]

Role을 (개별 사용자 대신) 그룹에 바인딩하여 ID 공급자를 통해 멤버십을 관리할 수 있도록 합니다:

apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: oncall-debug
 namespace: <namespace>
subjects:
 - kind: Group
 name: oncall-<team-name>
 apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: Role
 name: oncall-debug
 apiGroup: rbac.authorization.k8s.io

2) 단기, ID 기반 자격 증명

목표는 세션을 실제 개인에게 명확하게 연결하고 빠르게 만료되는 단기, ID 기반 자격 증명을 사용하는 것입니다. 이 자격 증명에는 사용자 ID와 허용된 작업 범위가 포함될 수 있습니다. 이들은 일반적으로 엔지니어에게 보관되는 개인 키(예: YubiKey와 같은 하드웨어 지원 키)를 사용하여 서명되므로, 해당 키에 접근하지 않고는 위조할 수 없습니다.

이는 Kubernetes 네이티브 인증(예: 클라이언트 인증서 또는 OIDC 기반 흐름)으로 구현하거나, 이전 섹션의 접근 브로커가 사용자를 대신하여 단기 자격 증명을 발급하도록 할 수 있습니다. 많은 설정에서 Kubernetes는 인증된 ID와 그룹/클레임(claims)을 기반으로 권한을 적용하기 위해 여전히 RBAC를 사용합니다. 접근 브로커를 사용하는 경우, 해당 세션이 적용되는 클러스터나 네임스페이스, 파드 또는 노드에 대해 허용되는 작업(또는 승인된 명령)과 같이 자격 증명에 추가 범위 제약 조건을 인코딩하고 세션 중에 이를 적용할 수도 있습니다. 어떤 경우든 자격 증명은 CA(인증 기관)에 의해 서명되어야 하며, 장기적인 위험을 제한하기 위해 해당 CA는 정기적으로(예: 분기별) 교체되어야 합니다.

옵션 A: 단기 OIDC 토큰

많은 관리형 Kubernetes 클러스터는 이미 단기 토큰을 제공합니다. 중요한 것은 장기 토큰을 파일에 복사하는 대신, kubeconfig가 자동으로 이를 새로 고치도록 하는 것입니다.

예를 들어:

users:
- name: oncall
 user:
 exec:
 apiVersion: client.authentication.k8s.io/v1
 command: cred-helper
 args: ["--cluster=prod", "--ttl=30m"]

옵션 B: 단기 클라이언트 인증서(X.509)

API 서버(또는 이전 섹션의 접근 브로커)가 클라이언트 CA를 신뢰하도록 설정된 경우, 디버깅 접근을 위해 단기 클라이언트 인증서를 사용할 수 있습니다. 아이디어는 다음과 같습니다:

  • 개인 키는 엔지니어의 컴퓨터(이상적으로는 YubiKey/PIV 토큰의 내보내기 불가능한 키와 같이 하드웨어 지원)에서 생성 및 보관됩니다.
  • 단기 인증서가 발급됩니다([CertificateSigningRequest](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/#certificate-signing-requests) API 또는 이전 섹션의 접근 브로커를 통해 TTL과 함께 발급되는 경우가 많습니다).
  • RBAC는 인증된 ID를 최소한의 Role에 매핑합니다.

이는 Kubernetes CertificateSigningRequest API를 통해 쉽게 운영할 수 있습니다.

로컬에서 키와 CSR 생성:

# 개인 키를 생성합니다.
# 이는 하드웨어 토큰 내에서 대신 생성될 수도 있습니다;
# OpenSSL 및 여러 유사 도구는 이를 지원합니다.
openssl genpkey -algorithm Ed25519 -out oncall.key

openssl req -new -key oncall.key -out oncall.csr \
 -subj "/CN=user/O=oncall-payments"

짧은 만료 기간으로 CertificateSigningRequest 생성:

apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
 name: oncall-<user>-20260218
spec:
 request: <base64-encoded oncall.csr>
 signerName: kubernetes.io/kube-apiserver-client
 expirationSeconds: 1800 # 30분
 usages:
 - client auth

CSR이 승인되고 서명된 후, 발급된 인증서를 추출하고 개인 키와 함께 사용하여 예를 들어 `kubectl`을 통해 인증합니다.

3) 디버깅 명령을 실행하기 위해 Just-In-Time 접근 게이트웨이 사용

단기 자격 증명을 확보했다면, 이를 사용하여 SSH를 통해 노출되고 온디맨드로 생성되는 Just-In-Time 접근 게이트웨이에 보안 셸 세션을 열 수 있습니다. 게이트웨이가 SSH를 통해 노출되는 경우, 일반적인 패턴은 엔지니어에게 세션을 위한 단기 OpenSSH 사용자 인증서를 발급하는 것입니다. 게이트웨이는 SSH 사용자 CA를 신뢰하고, 연결 시 엔지니어를 인증한 다음, 사용자 대신 Kubernetes API 호출을 하기 전에 승인된 세션 정책을 적용합니다. OpenSSH 인증서는 Kubernetes X.509 클라이언트 인증서와 별개이므로, 이들은 일반적으로 별개의 계층으로 취급됩니다.

결과 세션 또한 승인된 범위 밖에서는 재사용할 수 없도록 범위가 지정되어야 합니다. 예를 들어, 게이트웨이 또는 브로커는 이를 특정 클러스터 및 네임스페이스로 제한할 수 있으며, 선택적으로 파드 또는 노드와 같은 더 좁은 대상으로도 제한할 수 있습니다. 이러한 방식으로 누군가 접근을 재사용하려고 해도 의도된 범위 밖에서는 작동하지 않습니다. 세션이 설정된 후, 게이트웨이는 허용된 작업만 실행하고 감사(auditing)를 위해 발생한 모든 내용을 기록합니다.

예시: 네임스페이스 범위 Role 바인딩

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
 name: jit-debug
 namespace: <namespace>
 annotations:
 kubernetes.io/description: >
 세미 권한 디버깅을 수행하는 동료들로, 접근 권한은 Just-In-Time 및 온디맨드로 제공됩니다.
rules:
 - apiGroups: [""]
 resources: ["pods", "pods/log"]
 verbs: ["get", "list","watch"]
 - apiGroups: [""]
 resources: ["pods/exec"]
 verbs: ["create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
 name: jit-debug
 namespace: <namespace>
subjects:
 - kind: Group
 name: jit:oncall:<namespace>  # 단기 자격 증명(인증서/OIDC)에서 매핑됨
 apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: Role
 name: jit-debug
 apiGroup: rbac.authorization.k8s.io

이러한 RBAC 객체와 그들이 정의하는 규칙은 지정된 네임스페이스 내에서만 디버깅을 허용하며, 다른 네임스페이스에 접근하려는 시도는 허용되지 않습니다.

예시: 클러스터 범위 Role 바인딩

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
 name: jit-cluster-read
rules:
 - apiGroups: [""]
 resources: ["nodes", "namespaces"]
 verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
 name: jit-cluster-read
subjects:
 - kind: Group
 name: jit:oncall:cluster
 apiGroup: rbac.authorization.k8s.io
roleRef:
 kind: ClusterRole
 name: jit-cluster-read
 apiGroup: rbac.authorization.k8s.io

이러한 RBAC 규칙은 클러스터 전반에 걸친 읽기 접근 권한(예: 노드 및 네임스페이스에 대한)을 부여하며, 클러스터 범위 리소스가 진정으로 필요한 워크플로에만 사용되어야 합니다.

“이 파드/노드만” 또는 “이 명령만”과 같은 더 세분화된 제한은 일반적으로 세션 중에 접근 게이트웨이/브로커에 의해 적용되지만, Kubernetes는 쓰기 제한을 위한 ValidatingAdmissionPolicy 및 동사(verbs) 전반에 걸친 사용자 지정 권한 부여를 위한 웹훅 권한 부여와 같은 다른 옵션도 제공합니다.

더 엄격한 접근 제어 환경에서는 세션 설정과 권한 있는 작업을 분리하기 위해 추가적인 단기 세션 중재 계층을 추가할 수 있습니다. 두 계층 모두 임시적이며, ID 기반 만료 자격 증명을 사용하고, 독립적인 감사 추적을 생성합니다. 중재 계층은 세션 설정/전달을 처리하고, 실행 계층은 RBAC로 권한이 부여된 Kubernetes 작업만 수행합니다. 이러한 분리는 책임을 축소하고, 단계별로 자격 증명의 범위를 지정하며, 종단 간(end-to-end) 세션 만료를 강제함으로써 노출을 줄일 수 있습니다.

참고 자료

  • [권한 부여](https://kubernetes.io/docs/reference/access-authn-authz/authorization/)
  • [RBAC 권한 부여 사용](https://kubernetes.io/docs/reference/access-authn-authz/rbac/)
  • [인증](https://kubernetes.io/docs/reference/access-authn-authz/authentication/)
  • [인증서 및 인증서 서명 요청](https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/)
  • [CertificateSigningRequest를 사용하여 Kubernetes API 클라이언트용 인증서 발급](https://kubernetes.io/docs/tasks/tls/certificate-issue-client-csr/)
  • [역할 기반 접근 제어 모범 사례](https://kubernetes.io/docs/concepts/security/rbac-good-practices/)

고지 사항: 이 게시물에 표현된 견해는 전적으로 저자의 것이며, 저자의 고용주 또는 다른 어떤 조직의 견해를 반영하지 않습니다.