K8s Security - 04. admission-control
인증·인가 통과한 뒤에도 한 번 더 검문한다 — 어드미션 컨트롤
한 팀이 "모든 Pod는 반드시 자원 제한(limits)을 가져야 한다"는 정책을 원했다. RBAC는 "누가 만들 수 있나"만 다루지 "만들어지는 객체가 어떤 모양*이어야 하나"는 다루지 못한다. 그래서 그 팀은 Pod를 만들고 *나서 수동으로 검사했다 — 늦었다. 어드미션 컨트롤(Admission Control)이 이 빈 칸을 채운다 — 인가 통과 후, 객체가 etcd에 저장되기 전에 한 번 더 검문한다.
이 글이 푸는 것은: 어드미션 컨트롤이 인증·인가 뒤에 무엇을 검사하는가, 그리고 ValidatingAdmissionPolicy(CEL 기반, 1.30 GA)가 정책 코드를 어떻게 바꿨는가다.
요청의 네 단계 — 어드미션은 세 번째
flowchart LR
REQ["요청"] --> A1["1. 인증<br/>"]
A1 --> A2["2. 인가/RBAC<br/>"]
A2 --> ADM["3. 어드미션 컨트롤"]
ADM --> ETCD["4. etcd 저장"]
ADM -. "Mutating: 객체 수정" .-> ADM
ADM -. "Validating: 거부/허용" .-> ADM
어드미션 컨트롤은 인가 통과한 요청이 etcd에 들어가기 전에 끼어든다. 두 종류:
- Mutating(변형): 객체를 고칠 수 있다. 예: 모든 Pod에 기본 라벨/annotation 자동 추가, image tag를 항상 digest로.
- Validating(검증): 객체를 검사만 하고 허용/거부. 고칠 순 없다. 예: limits 없는 Pod는 거부.
순서가 중요: Mutating이 먼저 돌아 객체를 고치고, 그 다음 Validating이 고쳐진 객체를 검사.
어드미션 컨트롤러의 두 종류 — 내장 vs 동적
- 내장(built-in) 어드미션 컨트롤러: apiserver에 컴파일된 것. 예:
NamespaceLifecycle(없는 namespace로 만들기 금지),ServiceAccount(SA 자동 마운트),DefaultStorageClass.--enable-admission-plugins로 켬. - 동적(dynamic) 어드미션 컨트롤러: ValidatingWebhookConfiguration/MutatingWebhookConfiguration — 외부 서버(webhook)에 HTTP로 질의. OPA Gatekeeper, Kyverno 등이 이 패턴.
동적 webhook이 확장성의 핵심 — 클러스터 운영자가 정책을 코드로 추가.
ValidatingAdmissionPolicy — 정책을 webhook 없이 (1.30 GA)
전통적 어드미션 정책은 외부 webhook 서버가 필요했다(OPA Gatekeeper). 운영 부담(별도 서버 가용성). *ValidatingAdmissionPolicy**(CEL — Common Expression Language 기반)는 이를 바꾼다 — 정책을 *Kubernetes 객체로 직접 쓰고, apiserver가 내장 평가.
# Kubernetes 1.36
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata: {name: require-limits}
spec:
matchConstraints:
resourceRules: [{apiGroups: [""], apiVersions: ["v1"], operations: ["CREATE"], resources: ["pods"]}]
validations:
- expression: "object.spec.containers.all(c, has(c.resources) && has(c.resources.limits))"
message: "모든 컨테이너는 resources.limits가 필요합니다"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata: {name: require-limits-binding}
spec:
policyName: require-limits
validationActions: [Deny] # 위반 시 거부
이점:
- 별도 서버 없음 — apiserver가 CEL로 평가. webhook 가용성/지연 문제 없음.
- YAML로 정책 — 코드 배포가 Kubernetes 객체 배포.
- 1.30 GA — 프로덕션 안전.
CEL은 Rego(OPA)와 다른 가벼운 표현 언어. ValidatingAdmissionPolicy에 채택. OPA Gatekeeper와 비교해 "외부 의존 없이"라는 장점이 있으나, 표현력/생태계는 OPA가 더 넓음. 선택은 팀 숙련도/요구.
MutatingAdmissionPolicy (1.36) — 변형도 정책으로
ValidatingAdmissionPolicy가 검증(거부/허용)만 한다면, MutatingAdmissionPolicy는 변형도 CEL 기반 정책으로 가져온다 — 1.36에서 Stable. "모든 Pod에 기본 annotation 추가", "image 태그를 digest로 변환" 같은 Mutating webhook 작업을 webhook 서버 없이 apiserver 내장에서 처리한다.
왜 검증과 변형이 분리되나? 검증은 객체를 안 고치고 거부만 하지만, 변형은 객체를 수정한다 — 부작용이 있는 작업이다. 이 둘을 같은 정책 객체에 섞으면 "거부했는데 이미 고쳤다" 같은 문제가 생길 수 있다. 그래서 Kubernetes는 검증(VAP)과 변형(MutatingAdmissionPolicy)을 별도 객체로 분리하고, 체인 순서(Mutating 먼저 → Validating 나중)로 부작용을 제어한다.
정확한 1.36 MutatingAdmissionPolicy 상태와 지원 범위는 CHANGELOG 1.36와 KEP에서 실측.
직접 확인하기
# Kubernetes 1.36 — 켜진 어드미션 플러그인
kubectl get pod -n kube-system <apiserver-pod> -o yaml | grep -A30 enable-admission-plugins
kind/kubeadm 클러스터의 apiserver 정적 파드에서 확인.
# ValidatingAdmissionPolicy로 "limits 필수" 적용 후
kubectl apply -f require-limits-policy.yaml
kubectl run bad --image=nginx:1.27 --restart=Never # limits 없음
# → 거부: "모든 컨테이너는 resources.limits가 필요합니다"
확인할 것: limits 없는 Pod 생성이 정책에 거부됨.
어드미션 체인의 정확한 순서 — Mutating이 먼저인 이유
인가(RBAC) 통과 후, 어드미션은 두 단계로 동작하며 순서가 보장된다:
flowchart LR
REQ["요청 (인가 통과)"] --> M["1. Mutating Webhook (변형)<br/>객체를 고침"]
M --> O["객체 직렬화 (고쳐진 최종 형태)"]
O --> V["2. Validating Webhook / VAP (검증)<br/>고쳐진 객체를 검사"]
V --> ETCD["3. etcd 저장"]
왜 Mutating이 먼저인가? 고쳐진 객체를 검증해야 하기 때문. Mutating이 기본 라벨을 추가하고, 그 다음 Validating이 "라벨이 있는가?"를 검증. 순서가 반대면 — Validating이 원본을 보고 통과시킨 뒤 Mutating이 바꿔서, 검증되지 않은 형태가 저장될 수 있다.
이 순서가 실무에서 중요한 순간: Mutating webhook이 image 태그를 digest로 바꾸고(공급망), Validating이 "신뢰된 레지스트리인가?"를 검증. Mutating→Validating 순서여야 바뀐 digest가 검증 대상이 된다.
내장 어드미션 컨트롤러도 이 순서를 따른다.
ServiceAccount(Mutating)가 SA를 자동 마운트한 뒤,ValidatingAdmissionPolicy가 검증. 전체 체인의 순서가 정책의 정확성을 결정.
webhook의 failurePolicy — 정책 서버가 죽었을 때
동적 webhook(외부 서버)에 의존하면, 그 서버가 죽었을 때 어떻게 해야 하나가 관건. failurePolicy 설정:
- Fail(기본): webhook 서버가 응답 안 하면 요청 거부. 보안 강하지만 — 서버 장애 시 클러스터 전체가 객체 생성 불가.
- Ignore: 서버 응답 없으면 통과. 가용성 우선이지만 — 정책이 우회될 수 있음(보안 약화).
webhooks:
- failurePolicy: Fail # 또는 Ignore
이 trade-off가 실무에서 날카롭다. 보안 정책 서버가 SPOF가 되면, Fail 모드에서 그 서버 장애 = 클러스터 마비. 그래서 webhook 서버의 고가용성(다중 replica)이 필수. ValidatingAdmissionPolicy(CEL, apiserver 내장)가 이 문제를 근본적으로 피하는 이유 — 외부 서버 의존이 없으니 failurePolicy 딜레마 자체가 안 생긴다.
CEL vs Rego(OPA) — 표현력과 단순성의 trade-off
ValidatingAdmissionPolicy의 CEL과 OPA Gatekeeper의 Rego는 다른 언어·다른 철학:
| CEL (VAP) | Rego (OPA) | |
|---|---|---|
| 위치 | apiserver 내장 | 외부 webhook 서버 |
| 언어 | 단순 표현식 (C-like) | 논리 프로그래밍 (Datalog 계열) |
| 학습 곡선 | 완만 | 가파름 |
| 표현력 | 제한적(복잡한 논리는 한계) | 강력(데이터 쿼리, 복잡 규칙) |
| 가용성 | apiserver 일부(SPOF 없음) | 별도 서버(SPOF/스케일 고려) |
| 생태계 | Kubernetes 내장 | OPA 생태계(정책 공유/bundle) |
선택: 단순 정책(필수 라벨, 자원 제한, 이미지 레지스트리)은 CEL로 충분 + 외부 의존 없음 이점. 복잡한 정책(조직 전체 정책, 데이터 기반 의사결정, 기존 OPA 투자)은 Rego. 실무에선 둘을 섞어 쓰기도 — 단순은 CEL, 복잡은 OPA.
실무 정책 패턴 — 현장에서 자주 쓰는 어드미션 규칙
어드미션 정책이 강제하는 흔한 규칙들을 구체적으로:
- 자원 제한 필수: limits 없는 Pod 거부. 리소스 부족/노드 과밀 방지(11장 QoS 연계).
- 이미지 레지스트리 화이트리스트:
registry.company.com/*만 허용. 외부 이미지 차단(공급망). - privileged Pod 금부:
privileged: true차단. 05장 Pod Security Admission과 겹치나, 정책으로 더 세밀하게. - 필수 라벨:
team/cost-center라벨 없는 객체 거부. 비용 추적/소유권. - namespace 할당량: 한 namespace의 Pod/Service 총량 제한. ResourceQuota와 보완.
- Ingress 호스트 충돌 방지: 같은 호스트를 두 Ingress가 주장하지 않게.
이런 정책들이 코드(YAML) 로 관리되면 — 감사 가능, 버전 관리, 자동 적용. "운영자가 수동으로 검사"에서 "정책이 자동 강제"로의 전환이 어드미션 컨트롤의 가치.
webhook timeoutSeconds — 정책 서버가 느릴 때
어드미션 webhook이 외부 서버에 HTTP 요청을 보내는데, 그 서버가 느리면 어떻게 되나. timeoutSeconds가 그 한계:
webhooks:
- timeoutSeconds: 3 # 기본 10초, 최대 30초
3초 안에 응답 없으면 → failurePolicy 적용(Fail이면 거부, Ignore면 통과). 이 timeout이 전체 클러스터 객체 생성 속도에 영향 — webhook이 30초 timeout이면, 객체 생성이 최대 30초 지연될 수 있다. 짧은 timeout + 빠른 webhook 서버가 사용자 경험에 중요.
dry-run — 어드미션 정책을 거칠지 미리 보기
kubectl apply --dry-run=server가 어드미션 체인을 거치되 etcd에 저장 안 함. "이 매니페스트가 정책을 통과할까?"를 실제로 검증 without side effect. 정책 도입 시 유용 — 새 정책이 기존 워크로드를 거부할지 dry-run으로 확인.
어드미션과 audit의 연결 — 거부를 추적
어드미션이 거부한 요청이 audit log에 기록된다. "왜 이 Pod가 안 만들어지나"를 audit에서 찾을 수 있음 — 어드미션 webhook/VAP의 거부 사유가 audit 이벤트에. 이것이 "정책이 거부했는데 이유를 모르겠다"를 audit log로 추적하는 경로.
흔히 묻는 것, 흔히 틀리는 것
| 오해 | 정정 |
|---|---|
| "인가(RBAC) 통과하면 끝이다" | 어드미션 컨트롤이 한 번 더 (형태 검사) |
| "Validating은 객체를 고친다" | 검사만. 고치는 건 Mutating |
| "어드미션 정책은 항상 webhook 서버가 필요하다" | ValidatingAdmissionPolicy(1.30 GA)는 CEL로 apiserver 내장 |
| "Mutating과 Validating 순서는 무관하다" | Mutating 먼저 → 그 후 Validating |
| "CEL과 OPA는 같다" | 다른 언어/생태계. CEL은 가벼움, OPA는 표현력/생태계 |
| "어드미션이 거부하면 403이다" | 보통 403 아닌 별도 거부 응답. 디버깅 시 이벤트 확인 |
요약 — 이 글의 결론
- 어드미션 컨트롤이 인가(RBAC) 후 etcd 저장 전에 형태를 검문 — "권한은 있지만 이 모양은 안 된다"를 강제.
- Mutating(변형) → Validating(검증) 순서. Mutating이 고치고 Validating이 검사.
- 내장(apiserver 컴파일) + 동적(webhook). 동적 webhook(OPA Gatekeeper/Kyverno)이 확장성.
- ValidatingAdmissionPolicy(CEL, 1.30 GA)가 webhook 없이 정책을 apiserver 내장 평가. 별도 서버/지연 부담 없음.
- MutatingAdmissionPolicy(1.36)가 변형도 CEL 정책으로 확장.
- CEL vs OPA: CEL은 가벼움/apiserver 내장, OPA는 표현력/생태계. 팀 요구로 선택.
생각해 볼 문제
- RBAC은 "누가"만 다룬다. "만들어지는 객체가 어떤 모양이어야"를 강제하려면 어느 메커니즘인가?
- Mutating webhook이 객체를 고쳤다. 그 후 Validating이 거부했다. 결과는? (롤백)
- OPA Gatekeeper(webhook)를 쓰다 ValidatingAdmissionPolicy로 옮기면 얻는 것/잃는 것?
- "신뢰된 레지스트리 이미지만" 정책을 ValidatingAdmissionPolicy CEL로 짜 보라(의사코드).
- 어드미션 webhook 서버가 죽었다. 클러스터는 어떻게 되나? (failurePolicy)
- Mutating webhook이 라벨을 자동 추가했다. 사용자가 모르는 변형의 위험은?
참고
- Kubernetes 공식 문서 - Admission Controllers - 접근 2026-07-12
- Kubernetes 공식 문서 - Dynamic Admission Control (webhook) - 접근 2026-07-12
- Kubernetes 공식 문서 - ValidatingAdmissionPolicy (CEL) - 접근 2026-07-12 (1.30 GA)
- CEL (Common Expression Language) - 접근 2026-07-12
- Kubernetes 1.36 CHANGELOG - MutatingAdmissionPolicy - 접근 2026-07-12
- OPA Gatekeeper / Kyverno - 접근 2026-07-12 (비교)
'Tech Artifacts > K8s Security' 카테고리의 다른 글
| K8s Security - 06. secrets management (0) | 2026.07.16 |
|---|---|
| K8s Security - 05. pod-security-admission (0) | 2026.07.16 |
| K8s Security - 03. fine-grained kubelet API authorization (0) | 2026.07.16 |
| K8s Security - 02. authentication (0) | 2026.07.16 |
| K8s Security - 01. rbac (0) | 2026.07.16 |