Split View: Kubernetes RBAC 심층 가이드: Role·ClusterRole·OPA Gatekeeper로 구현하는 최소 권한 접근제어
Kubernetes RBAC 심층 가이드: Role·ClusterRole·OPA Gatekeeper로 구현하는 최소 권한 접근제어
- 들어가며
- RBAC 핵심 개념
- ServiceAccount와 토큰 관리
- RBAC 설계 패턴
- OPA Gatekeeper 아키텍처
- ConstraintTemplate과 Rego 정책 작성
- 실전 정책 사례
- RBAC 감사(Audit) 및 모니터링
- 비교표: RBAC vs OPA Gatekeeper vs PSA vs Kyverno
- 장애 사례 및 복구 절차
- 운영 체크리스트
- 마치며

들어가며
Kubernetes 클러스터를 프로덕션에서 운영하면서 가장 먼저 체계를 잡아야 하는 영역이 접근제어(Access Control) 이다. 클러스터 관리자, 개발자, CI/CD 파이프라인, 모니터링 에이전트 등 다양한 주체가 API Server에 요청을 보내며, 이 요청들을 누가(Subject) 어떤 리소스(Resource)에 어떤 동작(Verb)을 수행할 수 있는가로 세밀하게 제어해야 한다. Kubernetes가 제공하는 기본 인가 메커니즘인 RBAC(Role-Based Access Control)만으로도 상당한 수준의 접근제어가 가능하지만, "특정 이미지 레지스트리만 허용", "모든 Pod에 리소스 제한 필수 설정", "특정 라벨이 없는 네임스페이스 생성 금지"와 같은 정책(Policy) 기반 제어는 RBAC의 영역을 벗어난다.
이 간극을 채우는 것이 바로 OPA Gatekeeper이다. Open Policy Agent(OPA) 기반의 Kubernetes 어드미션 컨트롤러로, Rego 언어로 작성한 정책을 ConstraintTemplate과 Constraint CRD를 통해 클러스터에 적용한다. RBAC이 "누구에게 어떤 권한을 부여할 것인가"를 다룬다면, OPA Gatekeeper는 "허용된 요청이라도 정책을 준수하는가"를 검증하는 보완 계층이다.
이 글에서는 RBAC의 핵심 구성 요소부터 ServiceAccount 토큰 관리, 네임스페이스별 권한 격리 설계 패턴, OPA Gatekeeper 아키텍처와 Rego 정책 작성, 실전 정책 사례, 감사(Audit) 전략, 주요 정책 엔진 비교, 그리고 장애 사례와 복구 절차까지 종합적으로 다룬다.
RBAC 핵심 개념
Kubernetes RBAC은 네 가지 API 오브젝트로 구성된다. 이 네 가지의 관계를 정확히 이해하는 것이 접근제어 설계의 출발점이다.
Role과 ClusterRole
Role은 특정 네임스페이스 안에서 리소스에 대한 권한 규칙(rules)을 정의한다. ClusterRole은 네임스페이스에 국한되지 않으며 클러스터 전역 리소스(nodes, persistentvolumes 등)에 대한 권한이나 여러 네임스페이스에 걸쳐 재사용할 권한 세트를 정의할 때 사용한다.
# 네임스페이스 범위 Role: dev 네임스페이스에서 Pod 읽기 권한
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'watch', 'list']
- apiGroups: ['']
resources: ['pods/log']
verbs: ['get']
# 클러스터 범위 ClusterRole: 모든 네임스페이스에서 Deployment 관리
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deployment-manager
rules:
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch']
- apiGroups: ['apps']
resources: ['deployments/scale']
verbs: ['update', 'patch']
핵심 원칙은 와일드카드(*) 사용을 최대한 회피하는 것이다. resources: ["*"]나 verbs: ["*"]를 사용하면 현재는 물론 미래에 추가될 리소스에 대해서도 무제한 접근을 허용하게 되어 보안 위험이 급격히 증가한다.
RoleBinding과 ClusterRoleBinding
정의된 Role/ClusterRole을 실제 주체(Subject)에 연결하는 것이 RoleBinding과 ClusterRoleBinding이다.
# RoleBinding: dev 네임스페이스에서 frontend-team 그룹에 pod-reader Role 부여
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: frontend-pod-reader
namespace: dev
subjects:
- kind: Group
name: frontend-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
주의사항: system:masters 그룹에 사용자를 추가하는 것은 절대 피해야 한다. 이 그룹의 멤버는 모든 RBAC 검사를 우회하며, RoleBinding이나 ClusterRoleBinding을 삭제해도 권한을 회수할 수 없다.
Subject 유형
RBAC에서 권한을 부여받는 주체(Subject)는 세 가지 유형이 있다.
- User: 외부 인증 시스템(OIDC, 인증서 등)이 제공하는 사용자 아이덴티티
- Group: 사용자들의 논리적 그룹. 인증 시스템에서 그룹 정보를 전달
- ServiceAccount: Kubernetes가 직접 관리하는 Pod 내부 워크로드용 계정
ServiceAccount와 토큰 관리
Kubernetes 1.24부터 ServiceAccount에 자동으로 영구 토큰이 생성되지 않는다. TokenRequest API를 통해 시간 제한이 있는 토큰을 발급받는 것이 기본 동작이며, 이는 보안 측면에서 중요한 개선이다.
# ServiceAccount 생성
kubectl create serviceaccount ci-deployer -n staging
# 시간 제한 토큰 발급 (1시간 유효)
kubectl create token ci-deployer -n staging --duration=3600s
# ServiceAccount 권한 확인
kubectl auth can-i create deployments --as=system:serviceaccount:staging:ci-deployer -n staging
# 특정 ServiceAccount에 바인딩된 Role 확인
kubectl get rolebindings -n staging -o json | \
jq '.items[] | select(.subjects[]? | .name=="ci-deployer" and .kind=="ServiceAccount")'
CI/CD 파이프라인용 ServiceAccount를 설계할 때는 다음 원칙을 따른다.
- 파이프라인별 전용 ServiceAccount 생성: 하나의 ServiceAccount를 여러 파이프라인에서 공유하지 않는다
- 필요한 네임스페이스에만 RoleBinding 생성: ClusterRoleBinding 대신 네임스페이스별 RoleBinding을 사용한다
- 토큰 유효 기간 최소화: 파이프라인 실행 시간에 맞춰 토큰 유효 기간을 설정한다
- automountServiceAccountToken 비활성화: Pod에서 불필요하게 ServiceAccount 토큰이 마운트되지 않도록 설정한다
# automountServiceAccountToken 비활성화 예시
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service
namespace: production
automountServiceAccountToken: false
RBAC 설계 패턴
네임스페이스별 격리
멀티 테넌트 환경에서는 네임스페이스를 팀 또는 환경 단위로 분리하고, 각 네임스페이스에 독립적인 RBAC 정책을 적용한다.
# 팀별 네임스페이스 + RBAC 일괄 구성 예시
---
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha
labels:
team: alpha
environment: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-alpha
name: team-alpha-developer
rules:
- apiGroups: ['', 'apps', 'batch']
resources: ['pods', 'deployments', 'services', 'configmaps', 'jobs']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
- apiGroups: ['']
resources: ['secrets']
verbs: ['get', 'list'] # Secret 쓰기 권한은 제한
- apiGroups: ['']
resources: ['pods/exec']
verbs: ['create'] # 디버깅을 위한 exec 허용
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-developer-binding
namespace: team-alpha
subjects:
- kind: Group
name: team-alpha-devs
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: team-alpha-developer
apiGroup: rbac.authorization.k8s.io
권한 에스컬레이션 방지
RBAC 설계에서 가장 주의해야 할 것은 권한 에스컬레이션(Privilege Escalation) 경로를 차단하는 것이다. 다음 권한들은 특별히 주의가 필요하다.
pods/exec: Pod 내부에서 임의 명령을 실행할 수 있어, Pod의 ServiceAccount 토큰 탈취 가능secrets읽기: 다른 ServiceAccount의 토큰이나 데이터베이스 자격 증명 노출 위험createonrolebindings/clusterrolebindings: 스스로에게 상위 권한 부여 가능escalate/bindverb: Role/ClusterRole을 수정하거나 바인딩할 수 있는 메타 권한
OPA Gatekeeper 아키텍처
OPA Gatekeeper는 Kubernetes 어드미션 웹훅(Admission Webhook)으로 동작하며, API Server가 리소스를 생성/수정/삭제하기 전에 정책 검증을 수행한다.
구성 요소
- Gatekeeper Controller Manager: 어드미션 웹훅 요청을 처리하고 Rego 정책을 평가하는 핵심 컴포넌트
- Audit Controller: 기존에 배포된 리소스가 정책을 준수하는지 주기적으로 검사
- ConstraintTemplate CRD: Rego 정책 로직과 파라미터 스키마를 정의하는 템플릿
- Constraint CRD: ConstraintTemplate을 인스턴스화하여 구체적인 정책 적용 대상과 파라미터를 지정
설치
# Gatekeeper 설치 (Helm)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set audit.interval=60 \
--set constraintViolationsLimit=50 \
--set audit.fromCache=true
# 설치 확인
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper
동작 흐름
Gatekeeper의 정책 평가 흐름은 다음과 같다.
- 사용자 또는 컨트롤러가 API Server에 리소스 생성/수정 요청을 전송
- API Server가 인증(AuthN)과 인가(AuthZ, RBAC)를 수행
- 어드미션 단계에서 Gatekeeper 웹훅으로 요청 전달
- Gatekeeper가 해당 리소스에 매칭되는 Constraint를 찾아 Rego 정책 평가
- 위반 사항이 있으면 요청 거부(Deny)와 함께 위반 메시지 반환
- 위반 없으면 요청 승인(Allow)하여 etcd에 저장
ConstraintTemplate과 Rego 정책 작성
기본 구조
ConstraintTemplate은 두 부분으로 구성된다. CRD 스펙(파라미터 스키마)과 Rego 코드(정책 로직)이다.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
description: '필수 라벨 목록'
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("리소스에 필수 라벨이 누락되었습니다: %v", [missing])
}
Constraint 적용
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Namespace']
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
labels:
- 'team'
- 'cost-center'
실전 정책 사례
사례 1: 이미지 레지스트리 제한
프로덕션 클러스터에서 허용된 컨테이너 레지스트리에서만 이미지를 가져오도록 강제한다.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not image_from_allowed(container.image)
msg := sprintf("컨테이너 '%v'의 이미지 '%v'는 허용되지 않은 레지스트리입니다. 허용 레지스트리: %v",
[container.name, container.image, input.parameters.repos])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not image_from_allowed(container.image)
msg := sprintf("initContainer '%v'의 이미지 '%v'는 허용되지 않은 레지스트리입니다.",
[container.name, container.image])
}
image_from_allowed(image) {
repo := input.parameters.repos[_]
startswith(image, repo)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: prod-allowed-repos
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Pod']
namespaces:
- production
- staging
parameters:
repos:
- 'gcr.io/my-company/'
- 'us-docker.pkg.dev/my-company/'
- 'registry.internal.company.com/'
사례 2: 리소스 요청/제한 필수 설정
모든 컨테이너에 CPU와 메모리의 requests/limits 설정을 강제한다.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequireresourcelimits
spec:
crd:
spec:
names:
kind: K8sRequireResourceLimits
validation:
openAPIV3Schema:
type: object
properties:
requiredResources:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequireresourcelimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
resource := input.parameters.requiredResources[_]
not container.resources.limits[resource]
msg := sprintf("컨테이너 '%v'에 resources.limits.%v가 설정되지 않았습니다.", [container.name, resource])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
resource := input.parameters.requiredResources[_]
not container.resources.requests[resource]
msg := sprintf("컨테이너 '%v'에 resources.requests.%v가 설정되지 않았습니다.", [container.name, resource])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireResourceLimits
metadata:
name: require-cpu-memory-limits
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Pod']
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
requiredResources:
- 'cpu'
- 'memory'
사례 3: 특권 컨테이너 차단
특권 모드(privileged)로 실행되는 컨테이너를 차단한다.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowprivileged
spec:
crd:
spec:
names:
kind: K8sDisallowPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowprivileged
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("특권 컨테이너는 허용되지 않습니다: '%v'", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("특권 initContainer는 허용되지 않습니다: '%v'", [container.name])
}
RBAC 감사(Audit) 및 모니터링
kubectl 기반 감사
# 현재 사용자의 권한 확인
kubectl auth can-i --list
# 특정 ServiceAccount의 네임스페이스 권한 확인
kubectl auth can-i --list --as=system:serviceaccount:production:app-deployer -n production
# 특정 동작 가능 여부 확인
kubectl auth can-i delete pods --as=system:serviceaccount:staging:ci-runner -n staging
# 클러스터 전체에서 secrets 읽기 가능한 주체 탐색 (kubectl-who-can 플러그인)
kubectl who-can get secrets --all-namespaces
# 과도한 권한을 가진 ClusterRoleBinding 탐색
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name, .subjects'
Gatekeeper 감사(Audit) 활용
Gatekeeper의 Audit 기능은 기존에 배포된 리소스에 대해서도 정책 위반 여부를 검사한다. enforcementAction: dryrun으로 설정하면 차단하지 않고 위반 사항만 기록할 수 있다.
# 정책 위반 현황 조회
kubectl get k8sallowedrepos prod-allowed-repos -o yaml | \
grep -A 100 "status:" | head -50
# 전체 Constraint 위반 요약
kubectl get constraints -o json | \
jq '.items[] | {name: .metadata.name, kind: .kind, violations: (.status.totalViolations // 0)}'
모니터링 통합
Gatekeeper는 Prometheus 메트릭을 기본으로 노출한다.
gatekeeper_violations: 현재 감사에서 발견된 위반 수gatekeeper_request_duration_seconds: 어드미션 요청 처리 시간gatekeeper_request_count: 총 어드미션 요청 수 (허용/거부별)gatekeeper_constraint_template_status: ConstraintTemplate 상태
비교표: RBAC vs OPA Gatekeeper vs PSA vs Kyverno
Kubernetes 접근제어 및 정책 엔진을 종합적으로 비교한다.
| 항목 | RBAC | OPA Gatekeeper | Pod Security Admission (PSA) | Kyverno |
|---|---|---|---|---|
| 동작 계층 | 인가(Authorization) | 어드미션(Admission) | 어드미션(Admission) | 어드미션(Admission) |
| 정책 언어 | YAML 선언형 | Rego | 내장 프로필 (Privileged/Baseline/Restricted) | YAML 선언형 |
| 학습 곡선 | 낮음 | 높음 (Rego 학습 필요) | 매우 낮음 | 낮음 |
| 유연성 | 낮음 (권한 부여/거부만) | 매우 높음 | 낮음 (3개 프로필만) | 높음 |
| Mutation 지원 | 해당 없음 | 지원 (Assign/Modify) | 미지원 | 지원 (mutate) |
| 리소스 생성 | 해당 없음 | 미지원 | 미지원 | 지원 (generate) |
| 감사(Audit) | audit log 분석 필요 | 내장 Audit 기능 | audit/warn 모드 | PolicyReport CRD |
| CNCF 단계 | Kubernetes 내장 | Graduated | Kubernetes 내장 | Incubating |
| 리소스 소비 | 없음 (API Server 내장) | 높음 (다수 Pod) | 매우 낮음 | 중간 |
| 추천 용도 | 기본 접근제어 | 복잡한 정책 로직 | Pod 보안 기준 강제 | Kubernetes 네이티브 정책 |
권장 조합: RBAC(기본 인가) + PSA(Pod 보안 기준) + Gatekeeper 또는 Kyverno(커스텀 정책)를 함께 사용하는 것이 프로덕션 환경의 모범 사례이다. 단순한 정책에는 Kyverno, 복잡한 교차 리소스 검증에는 OPA Gatekeeper가 적합하다.
장애 사례 및 복구 절차
사례 1: Gatekeeper 웹훅 장애로 전체 배포 차단
증상: 모든 Pod 생성이 거부되며, 에러 메시지에 webhook "validation.gatekeeper.sh" denied the request 또는 연결 타임아웃이 표시된다.
원인: Gatekeeper Controller Pod가 비정상 종료되었거나, 리소스 부족으로 응답하지 못하는 상태이다.
복구 절차:
# 1. Gatekeeper Pod 상태 확인
kubectl get pods -n gatekeeper-system
# 2. 긴급 상황: 웹훅 일시 비활성화 (failurePolicy를 Ignore로 변경)
kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration -o yaml > webhook-backup.yaml
kubectl patch validatingwebhookconfigurations gatekeeper-validating-webhook-configuration \
--type='json' -p='[{"op": "replace", "path": "/webhooks/0/failurePolicy", "value": "Ignore"}]'
# 3. Gatekeeper Pod 재시작
kubectl rollout restart deployment gatekeeper-controller-manager -n gatekeeper-system
# 4. 정상화 확인 후 failurePolicy 복원
kubectl apply -f webhook-backup.yaml
예방 조치: Gatekeeper의 failurePolicy를 Fail에서 Ignore로 변경하면 가용성은 높아지지만 정책 우회가 가능해지므로, 프로덕션에서는 Fail을 유지하되 Gatekeeper Pod의 리소스와 replicas를 충분히 확보한다.
사례 2: 과도한 ClusterRoleBinding으로 권한 누수
증상: 모든 ServiceAccount가 클러스터 리소스를 읽을 수 있는 비정상적인 상태가 발견된다.
진단 및 복구:
# cluster-admin 바인딩된 주체 탐색
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | {name: .metadata.name, subjects: .subjects}'
# 불필요한 ClusterRoleBinding 제거
kubectl delete clusterrolebinding suspicious-admin-binding
# 전체 ClusterRoleBinding 감사
kubectl get clusterrolebindings -o json | \
jq '.items[] | {name: .metadata.name, role: .roleRef.name, subjects: [.subjects[]? | .kind + ":" + .name]}'
사례 3: ConstraintTemplate 구문 오류로 정책 미작동
증상: Constraint를 생성했지만 정책이 적용되지 않는다. kubectl get constrainttemplates 에서 STATUS가 비정상이다.
진단:
# ConstraintTemplate 상태 확인
kubectl get constrainttemplate k8srequiredlabels -o json | jq '.status'
# Rego 구문 오류 확인
kubectl describe constrainttemplate k8srequiredlabels | grep -A 10 "Status:"
# Gatekeeper 로그에서 오류 확인
kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=100
운영 체크리스트
프로덕션 Kubernetes 클러스터의 접근제어를 점검하기 위한 체크리스트이다.
RBAC 체크리스트
-
system:masters그룹에 불필요한 사용자가 등록되어 있지 않은가 -
cluster-adminClusterRole이 바인딩된 주체를 모두 파악하고 있는가 - 와일드카드(
*) 권한을 사용하는 Role/ClusterRole이 존재하는가 - ServiceAccount에
automountServiceAccountToken: false가 기본 설정되어 있는가 - 네임스페이스별로 적절한 Role/RoleBinding이 구성되어 있는가
-
pods/exec,secrets,rolebindings등 민감 권한이 최소한으로 부여되어 있는가 - 사용하지 않는 ServiceAccount와 RoleBinding이 정리되어 있는가
- RBAC 설정이 Git에 관리되고 있는가 (GitOps)
OPA Gatekeeper 체크리스트
- Gatekeeper Controller가 고가용성(replicas 2 이상)으로 배포되어 있는가
-
failurePolicy가 프로덕션 요구사항에 맞게 설정되어 있는가 - kube-system, gatekeeper-system 등 시스템 네임스페이스가 적절히 제외되어 있는가
- 새 정책을
dryrun모드로 먼저 테스트하는 프로세스가 있는가 - Audit 결과를 정기적으로 검토하고 있는가
- ConstraintTemplate의 Rego 코드가 단위 테스트를 통과하는가
- Gatekeeper 메트릭이 Prometheus/Grafana에 통합되어 있는가
- 웹훅 장애 시 긴급 복구 절차(Runbook)가 문서화되어 있는가
정기 감사 항목
- 분기별 RBAC 권한 리뷰: 과도한 권한 보유 주체 식별
- 월별 Gatekeeper Audit 리포트: 정책 위반 리소스 현황
- ServiceAccount 토큰 사용 패턴 분석: 미사용 토큰 정리
- 신규 CRD/API 도입 시 RBAC 및 Gatekeeper 정책 업데이트
마치며
Kubernetes 접근제어는 RBAC만으로 완성되지 않는다. RBAC은 "누구에게 어떤 권한을 부여할 것인가"에 대한 답을 제공하지만, "허용된 작업이 정책을 준수하는가"를 검증하려면 OPA Gatekeeper와 같은 어드미션 컨트롤러가 필수적이다. 두 메커니즘을 함께 사용하고, Pod Security Admission으로 기본 보안 기준을 강제하며, 정기적인 감사를 수행하는 것이 프로덕션 환경의 최소 권한 원칙을 실현하는 방법이다.
특히 정책을 코드로 관리(Policy as Code)하고, 변경 전 반드시 dryrun 모드로 영향을 평가하며, 장애 시 긴급 복구 절차를 미리 준비하는 운영 습관이 보안 사고를 예방하는 핵심이다. RBAC과 OPA Gatekeeper는 기술적 도구이지만, 이를 효과적으로 운영하려면 조직의 접근제어 거버넌스와 연계해야 한다는 점을 잊지 말아야 한다.
Kubernetes RBAC Deep Dive: Implementing Least-Privilege Access Control with Role, ClusterRole, and OPA Gatekeeper
- Introduction
- RBAC Core Concepts
- ServiceAccount and Token Management
- RBAC Design Patterns
- OPA Gatekeeper Architecture
- ConstraintTemplate and Rego Policy Authoring
- Real-World Policy Use Cases
- RBAC Auditing and Monitoring
- Comparison: RBAC vs OPA Gatekeeper vs PSA vs Kyverno
- Failure Cases and Recovery Procedures
- Operational Checklist
- Conclusion

Introduction
When operating Kubernetes clusters in production, access control is the first area that demands a well-structured framework. Cluster administrators, developers, CI/CD pipelines, monitoring agents, and many other entities send requests to the API Server, and these requests must be finely controlled to determine who (Subject) can perform which actions (Verb) on which resources (Resource). While Kubernetes' built-in authorization mechanism, RBAC (Role-Based Access Control), provides a substantial level of access control, policy-based controls such as "allow only specific image registries," "require resource limits on all Pods," or "prohibit namespace creation without specific labels" fall outside the scope of RBAC.
OPA Gatekeeper fills this gap. As a Kubernetes admission controller built on Open Policy Agent (OPA), it enforces policies written in the Rego language through ConstraintTemplate and Constraint CRDs applied to the cluster. While RBAC addresses "who should be granted which permissions," OPA Gatekeeper validates "whether an authorized request complies with policies" -- a complementary security layer.
This guide covers RBAC core components, ServiceAccount token management, namespace-level permission isolation design patterns, OPA Gatekeeper architecture and Rego policy authoring, real-world policy use cases, audit strategies, policy engine comparison, and failure case studies with recovery procedures.
RBAC Core Concepts
Kubernetes RBAC consists of four API objects. Understanding the relationships among these four is the starting point for access control design.
Role and ClusterRole
A Role defines permission rules for resources within a specific namespace. A ClusterRole is not scoped to any namespace and is used to define permissions for cluster-wide resources (nodes, persistentvolumes, etc.) or to create reusable permission sets across multiple namespaces.
# Namespace-scoped Role: Pod read access in the dev namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: dev
name: pod-reader
rules:
- apiGroups: ['']
resources: ['pods']
verbs: ['get', 'watch', 'list']
- apiGroups: ['']
resources: ['pods/log']
verbs: ['get']
# Cluster-scoped ClusterRole: Deployment management across all namespaces
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: deployment-manager
rules:
- apiGroups: ['apps']
resources: ['deployments']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch']
- apiGroups: ['apps']
resources: ['deployments/scale']
verbs: ['update', 'patch']
The core principle is to avoid wildcard (*) usage as much as possible. Using resources: ["*"] or verbs: ["*"] grants unlimited access not only to current resources but also to any future resources that may be added, dramatically increasing security risk.
RoleBinding and ClusterRoleBinding
RoleBinding and ClusterRoleBinding connect defined Roles/ClusterRoles to actual Subjects.
# RoleBinding: Grant pod-reader Role to frontend-team group in dev namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: frontend-pod-reader
namespace: dev
subjects:
- kind: Group
name: frontend-team
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
Critical warning: Never add users to the system:masters group. Members of this group bypass all RBAC checks, and their permissions cannot be revoked by removing RoleBindings or ClusterRoleBindings.
Subject Types
There are three types of Subjects that can receive permissions in RBAC:
- User: A user identity provided by an external authentication system (OIDC, certificates, etc.)
- Group: A logical grouping of users. Group information is conveyed by the authentication system
- ServiceAccount: An in-cluster account managed directly by Kubernetes, used for workloads running inside Pods
ServiceAccount and Token Management
Since Kubernetes 1.24, ServiceAccounts no longer automatically generate permanent tokens. The TokenRequest API issues time-limited tokens by default, which is a significant security improvement.
# Create a ServiceAccount
kubectl create serviceaccount ci-deployer -n staging
# Issue a time-limited token (valid for 1 hour)
kubectl create token ci-deployer -n staging --duration=3600s
# Check ServiceAccount permissions
kubectl auth can-i create deployments --as=system:serviceaccount:staging:ci-deployer -n staging
# Find Roles bound to a specific ServiceAccount
kubectl get rolebindings -n staging -o json | \
jq '.items[] | select(.subjects[]? | .name=="ci-deployer" and .kind=="ServiceAccount")'
When designing ServiceAccounts for CI/CD pipelines, follow these principles:
- Create dedicated ServiceAccounts per pipeline: Never share a single ServiceAccount across multiple pipelines
- Create RoleBindings only in required namespaces: Use namespace-scoped RoleBindings instead of ClusterRoleBindings
- Minimize token validity period: Set token expiration to match the pipeline execution duration
- Disable automountServiceAccountToken: Prevent unnecessary token mounting in Pods
# Disabling automountServiceAccountToken
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-service
namespace: production
automountServiceAccountToken: false
RBAC Design Patterns
Namespace-Level Isolation
In multi-tenant environments, namespaces are separated by team or environment, with independent RBAC policies applied to each namespace.
# Team namespace + RBAC batch configuration example
---
apiVersion: v1
kind: Namespace
metadata:
name: team-alpha
labels:
team: alpha
environment: production
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: team-alpha
name: team-alpha-developer
rules:
- apiGroups: ['', 'apps', 'batch']
resources: ['pods', 'deployments', 'services', 'configmaps', 'jobs']
verbs: ['get', 'list', 'watch', 'create', 'update', 'patch', 'delete']
- apiGroups: ['']
resources: ['secrets']
verbs: ['get', 'list'] # Restrict Secret write permissions
- apiGroups: ['']
resources: ['pods/exec']
verbs: ['create'] # Allow exec for debugging
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: team-alpha-developer-binding
namespace: team-alpha
subjects:
- kind: Group
name: team-alpha-devs
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: team-alpha-developer
apiGroup: rbac.authorization.k8s.io
Preventing Privilege Escalation
The most critical aspect of RBAC design is blocking privilege escalation paths. The following permissions require special attention:
pods/exec: Allows arbitrary command execution inside Pods, enabling ServiceAccount token theftsecretsread access: Risk of exposing other ServiceAccount tokens or database credentialscreateonrolebindings/clusterrolebindings: Allows self-granting elevated permissionsescalate/bindverbs: Meta-permissions that allow modifying or binding Roles/ClusterRoles
OPA Gatekeeper Architecture
OPA Gatekeeper operates as a Kubernetes admission webhook, performing policy validation before the API Server creates, modifies, or deletes resources.
Components
- Gatekeeper Controller Manager: The core component that processes admission webhook requests and evaluates Rego policies
- Audit Controller: Periodically inspects already-deployed resources for policy compliance
- ConstraintTemplate CRD: Defines the Rego policy logic and parameter schema as a template
- Constraint CRD: Instantiates a ConstraintTemplate, specifying concrete policy targets and parameters
Installation
# Install Gatekeeper (Helm)
helm repo add gatekeeper https://open-policy-agent.github.io/gatekeeper/charts
helm repo update
helm install gatekeeper gatekeeper/gatekeeper \
--namespace gatekeeper-system \
--create-namespace \
--set audit.interval=60 \
--set constraintViolationsLimit=50 \
--set audit.fromCache=true
# Verify installation
kubectl get pods -n gatekeeper-system
kubectl get crd | grep gatekeeper
Request Flow
The Gatekeeper policy evaluation flow works as follows:
- A user or controller sends a resource create/modify request to the API Server
- The API Server performs authentication (AuthN) and authorization (AuthZ/RBAC)
- During the admission phase, the request is forwarded to the Gatekeeper webhook
- Gatekeeper finds matching Constraints for the resource and evaluates Rego policies
- If violations are found, the request is denied with violation messages
- If no violations exist, the request is allowed and persisted to etcd
ConstraintTemplate and Rego Policy Authoring
Basic Structure
A ConstraintTemplate consists of two parts: the CRD spec (parameter schema) and the Rego code (policy logic).
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names:
kind: K8sRequiredLabels
validation:
openAPIV3Schema:
type: object
properties:
labels:
type: array
items:
type: string
description: 'List of required labels'
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("Resource is missing required labels: %v", [missing])
}
Applying a Constraint
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
name: require-team-label
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Namespace']
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
labels:
- 'team'
- 'cost-center'
Real-World Policy Use Cases
Use Case 1: Image Registry Restriction
Enforce that containers in production clusters can only pull images from approved registries.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
not image_from_allowed(container.image)
msg := sprintf("Container '%v' uses image '%v' from an unauthorized registry. Allowed registries: %v",
[container.name, container.image, input.parameters.repos])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
not image_from_allowed(container.image)
msg := sprintf("Init container '%v' uses image '%v' from an unauthorized registry.",
[container.name, container.image])
}
image_from_allowed(image) {
repo := input.parameters.repos[_]
startswith(image, repo)
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: prod-allowed-repos
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Pod']
namespaces:
- production
- staging
parameters:
repos:
- 'gcr.io/my-company/'
- 'us-docker.pkg.dev/my-company/'
- 'registry.internal.company.com/'
Use Case 2: Mandatory Resource Requests and Limits
Require all containers to have CPU and memory requests/limits configured.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequireresourcelimits
spec:
crd:
spec:
names:
kind: K8sRequireResourceLimits
validation:
openAPIV3Schema:
type: object
properties:
requiredResources:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequireresourcelimits
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
resource := input.parameters.requiredResources[_]
not container.resources.limits[resource]
msg := sprintf("Container '%v' is missing resources.limits.%v", [container.name, resource])
}
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
resource := input.parameters.requiredResources[_]
not container.resources.requests[resource]
msg := sprintf("Container '%v' is missing resources.requests.%v", [container.name, resource])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequireResourceLimits
metadata:
name: require-cpu-memory-limits
spec:
enforcementAction: deny
match:
kinds:
- apiGroups: ['']
kinds: ['Pod']
excludedNamespaces:
- kube-system
- gatekeeper-system
parameters:
requiredResources:
- 'cpu'
- 'memory'
Use Case 3: Block Privileged Containers
Prevent containers from running in privileged mode.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sdisallowprivileged
spec:
crd:
spec:
names:
kind: K8sDisallowPrivileged
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sdisallowprivileged
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged containers are not allowed: '%v'", [container.name])
}
violation[{"msg": msg}] {
container := input.review.object.spec.initContainers[_]
container.securityContext.privileged == true
msg := sprintf("Privileged init containers are not allowed: '%v'", [container.name])
}
RBAC Auditing and Monitoring
kubectl-Based Auditing
# Check current user permissions
kubectl auth can-i --list
# Check a specific ServiceAccount's namespace permissions
kubectl auth can-i --list --as=system:serviceaccount:production:app-deployer -n production
# Verify a specific action
kubectl auth can-i delete pods --as=system:serviceaccount:staging:ci-runner -n staging
# Find subjects that can read secrets cluster-wide (kubectl-who-can plugin)
kubectl who-can get secrets --all-namespaces
# Discover ClusterRoleBindings with excessive permissions
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | .metadata.name, .subjects'
Gatekeeper Audit Capabilities
Gatekeeper's Audit feature inspects already-deployed resources for policy violations. Setting enforcementAction: dryrun records violations without blocking requests.
# View policy violation status
kubectl get k8sallowedrepos prod-allowed-repos -o yaml | \
grep -A 100 "status:" | head -50
# Summary of all Constraint violations
kubectl get constraints -o json | \
jq '.items[] | {name: .metadata.name, kind: .kind, violations: (.status.totalViolations // 0)}'
Monitoring Integration
Gatekeeper exposes Prometheus metrics by default:
gatekeeper_violations: Number of violations found during the current auditgatekeeper_request_duration_seconds: Admission request processing timegatekeeper_request_count: Total admission requests (by allow/deny)gatekeeper_constraint_template_status: ConstraintTemplate status
Comparison: RBAC vs OPA Gatekeeper vs PSA vs Kyverno
A comprehensive comparison of Kubernetes access control and policy engines.
| Category | RBAC | OPA Gatekeeper | Pod Security Admission (PSA) | Kyverno |
|---|---|---|---|---|
| Layer | Authorization | Admission | Admission | Admission |
| Policy Language | Declarative YAML | Rego | Built-in profiles (Privileged/Baseline/Restricted) | Declarative YAML |
| Learning Curve | Low | High (requires Rego) | Very Low | Low |
| Flexibility | Low (grant/deny only) | Very High | Low (only 3 profiles) | High |
| Mutation Support | N/A | Supported (Assign/Modify) | Not supported | Supported (mutate) |
| Resource Generation | N/A | Not supported | Not supported | Supported (generate) |
| Audit | Requires audit log analysis | Built-in Audit feature | audit/warn modes | PolicyReport CRD |
| CNCF Stage | Built into Kubernetes | Graduated | Built into Kubernetes | Incubating |
| Resource Consumption | None (built into API Server) | High (multiple Pods) | Very Low | Medium |
| Recommended For | Base access control | Complex policy logic | Pod security baseline enforcement | Kubernetes-native policies |
Recommended combination: Using RBAC (base authorization) + PSA (Pod security baseline) + Gatekeeper or Kyverno (custom policies) together is the best practice for production environments. Kyverno is well-suited for simple policies, while OPA Gatekeeper excels at complex cross-resource validation.
Failure Cases and Recovery Procedures
Case 1: Gatekeeper Webhook Failure Blocking All Deployments
Symptom: All Pod creations are rejected with error messages showing webhook "validation.gatekeeper.sh" denied the request or connection timeouts.
Root Cause: Gatekeeper Controller Pods have crashed or are unresponsive due to resource exhaustion.
Recovery Procedure:
# 1. Check Gatekeeper Pod status
kubectl get pods -n gatekeeper-system
# 2. Emergency: Temporarily disable webhook (change failurePolicy to Ignore)
kubectl get validatingwebhookconfigurations gatekeeper-validating-webhook-configuration -o yaml > webhook-backup.yaml
kubectl patch validatingwebhookconfigurations gatekeeper-validating-webhook-configuration \
--type='json' -p='[{"op": "replace", "path": "/webhooks/0/failurePolicy", "value": "Ignore"}]'
# 3. Restart Gatekeeper Pods
kubectl rollout restart deployment gatekeeper-controller-manager -n gatekeeper-system
# 4. After confirming recovery, restore failurePolicy
kubectl apply -f webhook-backup.yaml
Prevention: Changing Gatekeeper's failurePolicy from Fail to Ignore improves availability but allows policy bypass. In production, maintain Fail while ensuring adequate resources and replicas for Gatekeeper Pods.
Case 2: Permission Leakage Through Excessive ClusterRoleBindings
Symptom: All ServiceAccounts can unexpectedly read cluster resources.
Diagnosis and Recovery:
# Find subjects bound to cluster-admin
kubectl get clusterrolebindings -o json | \
jq '.items[] | select(.roleRef.name=="cluster-admin") | {name: .metadata.name, subjects: .subjects}'
# Remove unnecessary ClusterRoleBindings
kubectl delete clusterrolebinding suspicious-admin-binding
# Audit all ClusterRoleBindings
kubectl get clusterrolebindings -o json | \
jq '.items[] | {name: .metadata.name, role: .roleRef.name, subjects: [.subjects[]? | .kind + ":" + .name]}'
Case 3: ConstraintTemplate Syntax Error Causing Policy Inaction
Symptom: A Constraint has been created but the policy is not being enforced. The STATUS column in kubectl get constrainttemplates shows an abnormal state.
Diagnosis:
# Check ConstraintTemplate status
kubectl get constrainttemplate k8srequiredlabels -o json | jq '.status'
# Check for Rego syntax errors
kubectl describe constrainttemplate k8srequiredlabels | grep -A 10 "Status:"
# Check Gatekeeper logs for errors
kubectl logs -n gatekeeper-system -l control-plane=controller-manager --tail=100
Operational Checklist
A checklist for auditing access control in production Kubernetes clusters.
RBAC Checklist
- Are there any unnecessary users in the
system:mastersgroup? - Have all subjects bound to the
cluster-adminClusterRole been identified? - Do any Roles/ClusterRoles use wildcard (
*) permissions? - Is
automountServiceAccountToken: falseset as the default for ServiceAccounts? - Are appropriate Roles/RoleBindings configured per namespace?
- Are sensitive permissions (
pods/exec,secrets,rolebindings) granted minimally? - Have unused ServiceAccounts and RoleBindings been cleaned up?
- Are RBAC configurations managed in Git (GitOps)?
OPA Gatekeeper Checklist
- Is the Gatekeeper Controller deployed with high availability (2+ replicas)?
- Is
failurePolicyconfigured appropriately for production requirements? - Are system namespaces (kube-system, gatekeeper-system) properly excluded?
- Is there a process to test new policies in
dryrunmode first? - Are Audit results reviewed regularly?
- Does the ConstraintTemplate Rego code pass unit tests?
- Are Gatekeeper metrics integrated with Prometheus/Grafana?
- Is there a documented emergency recovery runbook for webhook failures?
Periodic Audit Items
- Quarterly RBAC permission review: Identify subjects with excessive permissions
- Monthly Gatekeeper Audit report: Current policy violation resource status
- ServiceAccount token usage pattern analysis: Clean up unused tokens
- Update RBAC and Gatekeeper policies when new CRDs/APIs are introduced
Conclusion
Kubernetes access control is not complete with RBAC alone. RBAC answers "who should be granted which permissions," but validating "whether authorized actions comply with policies" requires an admission controller like OPA Gatekeeper. Using both mechanisms together, enforcing baseline security with Pod Security Admission, and performing regular audits is how you realize the least-privilege principle in production environments.
The operational habits that prevent security incidents are managing policies as code (Policy as Code), always evaluating impact with dryrun mode before changes, and preparing emergency recovery procedures for failures in advance. RBAC and OPA Gatekeeper are technical tools, but effective operation requires integration with your organization's access control governance framework.