목록으로

Programming Notes

Kubernetes v1.35: 세분화된 보조 그룹 제어 기능이 GA로 전환됩니다

Kubernetes SIG Node를 대표하여, Kubernetes v1.35에서 세분화된 보조 그룹 제어(fine-grained supplemental groups control) 기능이 GA(General Availability)로 전환되었음을 발표하게 되어 기쁩니다!...

Kubernetes SIG Node를 대표하여, Kubernetes v1.35에서 세분화된 보조 그룹 제어(fine-grained supplemental groups control) 기능이 GA(General Availability)로 전환되었음을 발표하게 되어 기쁩니다!

새로운 Pod 필드인 supplementalGroupsPolicy는 Kubernetes v1.31에서 선택 사항인 알파 기능으로 도입되었고, v1.33에서 베타로 졸업했습니다. 이제 이 기능은 일반적으로 사용 가능합니다. 이 기능은 Linux 컨테이너에서 보조 그룹에 대한 더 정밀한 제어를 구현하여, 특히 볼륨에 접근할 때 보안 태세를 강화할 수 있습니다. 또한, 컨테이너 내 UID/GID 세부 정보의 투명성을 높여 보안 감독을 개선합니다.

클러스터를 v1.32 또는 이전 버전에서 업그레이드할 계획이라면, 베타(v1.33) 이후 도입된 일부 동작 변경 사항에 유의하시기 바랍니다. 자세한 내용은 베타로의 졸업을 다룬 이전 블로그 게시물의 베타에서 도입된 동작 변경 사항업그레이드 고려 사항 섹션을 참조하십시오.

동기: 컨테이너 이미지 내 /etc/group에 정의된 암묵적 그룹 멤버십

대부분의 Kubernetes 클러스터 관리자/사용자는 인지하지 못할 수 있지만, 기본적으로 Kubernetes는 Pod의 그룹 정보와 컨테이너 이미지 내 /etc/group에 정의된 정보를 병합합니다.

예를 들어, Pod의 보안 컨텍스트에 spec.securityContext.runAsUser: 1000, spec.securityContext.runAsGroup: 3000, spec.securityContext.supplementalGroups: 4000을 지정하는 Pod 매니페스트를 살펴보겠습니다.

apiVersion: v1
kind: Pod
metadata:
  name: implicit-groups-example
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
  containers:
  - name: example-container
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

example-container 컨테이너에서 id 명령을 실행한 결과는 어떨까요? 출력은 다음과 유사할 것입니다.

uid=1000 gid=3000 groups=3000,4000,50000

보조 그룹(groups 필드)의 그룹 ID 50000은 Pod의 매니페스트에 전혀 정의되지 않았음에도 불구하고 어디서 온 것일까요? 답은 컨테이너 이미지 내 /etc/group 파일입니다.

컨테이너 이미지 내 /etc/group의 내용을 확인해보면 다음과 같습니다.

user-defined-in-image:x:1000:
group-defined-in-image:x:50000:user-defined-in-image

이는 컨테이너의 주 사용자 1000이 마지막 항목에서 그룹 50000에 속함을 보여줍니다.

따라서, 컨테이너 이미지 내 /etc/group에 정의된 컨테이너의 주 사용자에 대한 그룹 멤버십은 Pod의 정보에 암묵적으로 병합됩니다. 이는 현재 CRI 구현이 Docker로부터 물려받은 설계 결정이었으며, 커뮤니티는 지금까지 이를 재고하지 않았다는 점에 유의하십시오.

무엇이 문제인가?

컨테이너 이미지 내 /etc/group에서 암묵적으로 병합된 그룹 정보는 보안 위험을 초래합니다. 이러한 암묵적인 GID는 Pod 매니페스트에 기록되어 있지 않기 때문에 정책 엔진에 의해 탐지되거나 검증될 수 없습니다. 이는 특히 볼륨 접근 시 예상치 못한 접근 제어 문제로 이어질 수 있습니다(자세한 내용은 kubernetes/kubernetes#112879 참조). 왜냐하면 Linux에서 파일 권한은 UID/GID에 의해 제어되기 때문입니다.

Pod의 세분화된 보조 그룹 제어: supplementalGroupsPolicy

이 문제를 해결하기 위해 Pod의 .spec.securityContextsupplementalGroupsPolicy 필드가 포함되었습니다.

이 필드를 통해 Kubernetes가 Pod 내 컨테이너 프로세스의 보조 그룹을 계산하는 방식을 제어할 수 있습니다. 사용 가능한 정책은 다음과 같습니다.

  • Merge: 컨테이너의 주 사용자에 대해 /etc/group에 정의된 그룹 멤버십이 병합됩니다. 지정되지 않으면 이 정책이 적용됩니다 (즉, 이전 버전과의 호환성을 위한 현재 동작).
  • Strict: fsGroup, supplementalGroups, 또는 runAsGroup에 지정된 그룹 ID만 컨테이너 프로세스에 보조 그룹으로 연결됩니다. 컨테이너의 주 사용자에 대해 /etc/group에 정의된 그룹 멤버십은 무시됩니다.

Strict 정책이 어떻게 작동하는지 설명하겠습니다. 다음 Pod 매니페스트는 supplementalGroupsPolicy: Strict를 지정합니다.

apiVersion: v1
kind: Pod
metadata:
  name: strict-supplementalgroups-policy-example
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    supplementalGroups: [4000]
    supplementalGroupsPolicy: Strict
  containers:
  - name: example-container
    image: registry.k8s.io/e2e-test-images/agnhost:2.45
    command: [ "sh", "-c", "sleep 1h" ]
    securityContext:
      allowPrivilegeEscalation: false

example-container 컨테이너에서 id 명령을 실행한 결과는 다음과 유사할 것입니다.

uid=1000 gid=3000 groups=3000,4000

Strict 정책이 그룹 50000groups에서 제외할 수 있음을 알 수 있습니다!

따라서, supplementalGroupsPolicy: Strict (일부 정책 메커니즘에 의해 강제됨)를 보장하면 Pod의 암묵적인 보조 그룹을 방지하는 데 도움이 됩니다.

Pod 상태에 연결된 프로세스 ID

이 기능은 또한 컨테이너의 첫 번째 컨테이너 프로세스에 연결된 프로세스 ID를 .status.containerStatuses[].user.linux 필드를 통해 노출합니다. 이는 암묵적인 그룹 ID가 연결되었는지 확인하는 데 도움이 됩니다.

...
status:
  containerStatuses:
  - name: ctr
    user:
      linux:
        gid: 3000
        supplementalGroups:
        - 3000
        - 4000
        uid: 1000
...

Strict 정책은 최신 컨테이너 런타임을 필요로 합니다

고수준 컨테이너 런타임(예: containerd, CRI-O)은 컨테이너에 연결될 보조 그룹 ID를 계산하는 데 핵심적인 역할을 합니다. 따라서 supplementalGroupsPolicy: Strict는 이 기능을 지원하는 CRI 런타임을 필요로 합니다. 이전 동작(supplementalGroupsPolicy: Merge)은 이 기능을 지원하지 않는 CRI 런타임에서도 작동할 수 있습니다. 이는 이 정책이 이전 버전과 완전히 호환되기 때문입니다.

이 기능을 지원하는 일부 CRI 런타임과 필요한 버전은 다음과 같습니다.

  • containerd: v2.0 이상
  • CRI-O: v1.31 이상

그리고 Node의 .status.features.supplementalGroupsPolicy 필드에서 기능 지원 여부를 확인할 수 있습니다. 이 필드는 KEP-5328: Node Declared Features (이전 Node Capabilities)에서 소개된 status.declaredFeatures와는 다르다는 점에 유의하십시오.

apiVersion: v1
kind: Node
...
status:
  features:
    supplementalGroupsPolicy: true

컨테이너 런타임이 이 기능을 보편적으로 지원함에 따라, 다양한 보안 정책이 Strict 동작을 더 안전한 것으로 강제하기 시작할 수 있습니다. Pod가 이러한 강제에 대비하고 모든 보조 그룹이 이미지 내에서가 아닌 Pod 스펙에 투명하게 선언되었는지 확인하는 것이 가장 좋은 방법입니다.

참여하기

이 개선 사항은 SIG Node 커뮤니티에 의해 주도되었습니다. 커뮤니티와 연결하고 위에 언급된 기능 및 그 이상에 대한 아이디어와 피드백을 공유하려면 저희와 함께해 주십시오. 여러분의 의견을 기다리겠습니다!

더 자세히 알아보려면?