LabHub
배우기 러닝패스 코스

CKS — Kubernetes Security Specialist

Admission Policy Written in CEL

LabHub 에서 이어서 보기

한국어 원문으로 표시합니다.

목표

외부 웹훅 서버 없이 API 서버 안에서 CEL 로 동작하는 ValidatingAdmissionPolicy 를 직접 만들고, 바인딩으로 적용 범위를 좁힌 뒤, 전통적인 ValidatingWebhookConfiguration 오브젝트도 함께 작성해 두 방식의 차이를 손으로 확인합니다.

왜 중요한가

RBAC 은 "Pod 를 만들 수 있는가"까지만 답합니다. 그 Pod 가 privileged 인지, 허용된 레지스트리의 이미지인지는 RBAC 으로 표현할 수 없습니다. 이 간극을 메우는 것이 어드미션 정책입니다.

전통적인 방법은 ValidatingWebhookConfiguration 으로 외부 서버를 등록하는 것인데, 여기에는 가용성 함정이 있습니다. failurePolicy: Fail 로 등록된 웹훅의 서버가 죽으면 대상 리소스의 생성이 전부 거부되어 배포가 통째로 멈춥니다. 정책을 지키려다 클러스터를 세우는 것입니다. ValidatingAdmissionPolicy 는 API 서버 안에서 CEL 표현식을 평가하므로 네트워크 홉과 외부 프로세스가 사라지고, 이 실패 모드 자체가 줄어듭니다.

정책과 바인딩이 분리된 것도 의도된 설계입니다. 정책은 "무엇이 위반인가"만 정의하고, 바인딩이 "어디에, 얼마나 세게 적용할 것인가"를 정합니다. 같은 정책을 스테이징에서는 Warn 으로, 프로덕션에서는 Deny 로 켤 수 있습니다.

단계

  1. 네임스페이스 cks-admission 을 만들고 라벨 cks-policy=enforce 를 붙인다.
  2. ValidatingAdmissionPolicy cks-no-latest-tag 를 만든다. spec.matchConstraints.resourceRules[0] 은 apiGroups apps, apiVersions v1, operations CREATEUPDATE, resources deployments 다. (API 서버는 validationsauditAnnotations 도 없는 정책을 거부한다. 이 단계에서는 expression: "true" 같은 자리표시자 검증식을 하나 넣어 두고, 3 단계에서 진짜 표현식으로 바꾼다.)
  3. 같은 정책에 spec.validations[0] 을 넣는다. expression 은 Deployment 의 모든 컨테이너 이미지가 :latest 로 끝나지 않는지 검사하는 CEL 이고(object.spec.template.spec.containersall 이 들어가야 한다), message 는 비어 있으면 안 된다.
  4. 같은 정책의 spec.failurePolicyFail 로 설정한다.
  5. ValidatingAdmissionPolicyBinding cks-no-latest-tag-binding 을 만든다. policyNamecks-no-latest-tag, validationActionsDeny, matchResources.namespaceSelector.matchLabelscks-policy: enforce 다.
  6. ValidatingAdmissionPolicy cks-no-privileged 를 만든다. matchConstraints 는 코어 그룹 (apiGroups 는 빈 문자열), apiVersions v1, operations CREATE·UPDATE, resources pods 이고, validations[0].expression 에는 privileged 라는 단어와 object.spec.containers 가 들어가야 하며 message 도 있어야 한다.
  7. ValidatingWebhookConfiguration cks-image-verify 를 만든다. 웹훅 하나(nameverify.images.cks.local)를 두고 failurePolicyIgnore, sideEffectsNone, admissionReviewVersionsv1, clientConfig.service 는 네임스페이스 cks-admission 의 서비스 image-verifier, rules 는 코어 그룹 v1pods 에 대한 CREATE 다.
  8. cks-admission 에 Deployment checkout 을 만든다. replicas 2, 셀렉터와 파드 라벨은 app=checkout, 파드 템플릿에 라벨 app.kubernetes.io/name=checkout 도 넣는다. 컨테이너 이름은 app, 이미지는 @sha256: 로 고정하고 :latest 를 쓰지 않으며, 컨테이너 securityContext 에 privileged: falseallowPrivilegeEscalation: false 를 명시한다.

참고

정책을 적용할 네임스페이스

네임스페이스 cks-admission 을 만들고 라벨 cks-policy=enforce 를 붙인다.

바인딩의 namespaceSelector 가 이 라벨을 보고 대상을 고릅니다. 라벨 이름과 값을 정확히 맞추세요.

ValidatingAdmissionPolicy 골격

ValidatingAdmissionPolicy cks-no-latest-tag 를 만든다. spec.matchConstraints.resourceRules[0] 은 apiGroups apps, apiVersions v1, operations CREATEUPDATE, resources deployments 다. (API 서버는 validationsauditAnnotations 도 없는 정책을 거부한다. 이 단계에서는 expression: "true" 같은 자리표시자 검증식을 하나 넣어 두고, 3 단계에서 진짜 표현식으로 바꾼다.)

matchConstraints.resourceRules 는 어드미션 웹훅의 rules 와 같은 모양입니다. apiGroups·apiVersions·operations·resources 네 가지를 다 채워야 합니다. 그리고 API 서버는 validationsauditAnnotations 도 없는 정책은 생성 자체를 거부하므로, 이 단계에서는 자리표시자 검증식을 하나 넣어 두고 다음 단계에서 실제 표현식으로 바꾸세요.

CEL 표현식으로 검증 규칙 쓰기

같은 정책에 spec.validations[0] 을 넣는다. expression 은 Deployment 의 모든 컨테이너 이미지가 :latest 로 끝나지 않는지 검사하는 CEL 이고(object.spec.template.spec.containersall 이 들어가야 한다), message 는 비어 있으면 안 된다.

object 는 검사 대상 리소스입니다. Deployment 라면 컨테이너는 object.spec.template.spec.containers 에 있고, CEL 의 all() 매크로로 전부 검사할 수 있습니다.

failurePolicy 정하기

같은 정책의 spec.failurePolicyFail 로 설정한다.

표현식 평가가 실패했을 때 어떻게 할지의 정책입니다. 보안 정책이라면 열어 두는 쪽이 아니라 막는 쪽입니다.

바인딩으로 정책 켜기

ValidatingAdmissionPolicyBinding cks-no-latest-tag-binding 을 만든다. policyNamecks-no-latest-tag, validationActionsDeny, matchResources.namespaceSelector.matchLabelscks-policy: enforce 다.

정책은 바인딩이 없으면 아무 일도 하지 않습니다. validationActions 에 무엇을 넣느냐가 거부인지 경고인지를 가릅니다.

privileged 금지 정책 추가

ValidatingAdmissionPolicy cks-no-privileged 를 만든다. matchConstraints 는 코어 그룹 (apiGroups 는 빈 문자열), apiVersions v1, operations CREATE·UPDATE, resources pods 이고, validations[0].expression 에는 privileged 라는 단어와 object.spec.containers 가 들어가야 하며 message 도 있어야 한다.

파드 대상이므로 컨테이너 경로가 Deployment 와 다릅니다. securityContext 가 없는 컨테이너도 있으니 CEL 에서 존재 여부를 먼저 확인하세요.

ValidatingWebhookConfiguration 작성

ValidatingWebhookConfiguration cks-image-verify 를 만든다. 웹훅 하나(nameverify.images.cks.local)를 두고 failurePolicyIgnore, sideEffectsNone, admissionReviewVersionsv1, clientConfig.service 는 네임스페이스 cks-admission 의 서비스 image-verifier, rules 는 코어 그룹 v1pods 에 대한 CREATE 다.

sideEffectsadmissionReviewVersions 는 필수 필드라 빠지면 생성이 거부됩니다. 그리고 웹훅 서버가 없는 상태이므로 실패 정책을 잘못 고르면 클러스터가 멈춥니다.

종합: 정책을 통과하는 배포

cks-admission 에 Deployment checkout 을 만든다. replicas 2, 셀렉터와 파드 라벨은 app=checkout, 파드 템플릿에 라벨 app.kubernetes.io/name=checkout 도 넣는다. 컨테이너 이름은 app, 이미지는 @sha256: 로 고정하고 :latest 를 쓰지 않으며, 컨테이너 securityContext 에 privileged: falseallowPrivilegeEscalation: false 를 명시한다.

앞에서 만든 두 정책을 모두 만족해야 합니다. 태그가 아니라 다이제스트를 쓰고, 컨테이너 securityContext 에서 권한을 내려 두세요.