Kubernetes Access Control은 API 서버에 대한 접근을 제어하는 핵심 보안 메커니즘입니다.

| 단계 | 질문 | 실패 시 | 담당 |
|---|---|---|---|
| Authentication | "당신은 누구입니까?" | 401 Unauthorized | 신원 확인 |
| Authorization | "무엇을 할 수 있습니까?" | 403 Forbidden | RBAC 평가 |
| Admission Control | "정책을 준수합니까?" | 거부 메시지 | Webhook/정책 |
2022년 보안 연구에 따르면, 약 100만 개의 Kubernetes 인스턴스가 잘못된 설정으로 인터넷에 노출되어 있었습니다. 이는 Access Control 설정의 중요성을 보여주는 대표적인 사례입니다.

| 구분 | 인증 (Authentication) | 인가 (Authorization) |
|---|---|---|
| 질문 | "당신은 누구입니까?" | "무엇을 할 수 있습니까?" |
| 목적 | 주체(Subject) 식별 | 허용된 작업 결정 |
| 실패 시 | 401 Unauthorized | 403 Forbidden |
| 예시 | OIDC 토큰 검증, 인증서 확인 | Pod 생성 권한, Secret 읽기 권한 |
실무 예시: 개발자 Alice가 kubectl로 Pod를 생성하려 할 때
- 인증: Alice의 OIDC 토큰이 유효한지 확인
- 인가: Alice가 해당 namespace에서 Pod를 생성할 권한이 있는지 확인
모든 Kubernetes API 요청은 반드시 3단계를 순차적으로 통과해야 합니다. 각 단계는 순차적으로 실행되며, 하나라도 실패하면 요청이 거부됩니다.

Authentication (인증)
401 Unauthorized 반환Authorization (인가)
403 Forbidden 반환3. Admission Control (승인 제어)
예시: kubectl create pod를 실행하면
- 인증: kubeconfig의 토큰/인증서로 사용자 신원 확인
- 인가: 해당 namespace에서 pods 리소스에 대한 create 권한 확인
- 승인: Pod 스펙이 조직 정책(리소스 제한, 이미지 레지스트리 등)을 준수하는지 확인
Kubernetes는 다양한 플러그인 방식의 인증 전략을 제공합니다.
여러 전략을 동시에 사용할 수 있으며, 하나의 전략이 성공하면 다음 단계로 진행됩니다.

| 전략 | 설명 | 적합한 환경 |
|---|---|---|
| Bearer Tokens (OIDC) | OAuth2 기반 OIDC 토큰 사용 | 기업 환경, SSO 연동 |
| Client Certificates (X.509) | TLS 인증서 기반 인증 | 서비스 간 통신, CI/CD |
| Authenticating Proxy | 프록시를 통한 인증 위임 | 레거시 시스템 연동 |
| Webhook Token | 외부 서비스로 토큰 검증 위임 | 커스텀 인증 로직 |
운영 팁: Static Token Files는 보안 취약점이 많아 프로덕션에서 사용을 권장하지 않습니다.

kubeconfig 설정 예시
users:
- name: alice
user:
auth-provider:
name: oidc
config:
idp-issuer-url: <https://keycloak.example.com/realms/k8s>
client-id: kubernetes
refresh-token: <refresh-token>
id-token: <id-token>

| 유형 | 관리 방식 | 인증 방법 | 사용 사례 |
|---|---|---|---|
| User | 외부 IdP (Keycloak, Okta) | OIDC, X.509 | kubectl, 관리자 접근 |
| ServiceAccount | Kubernetes API | 자동 발급 토큰 | Pod, Operator, CI/CD |
인증이 완료되면 사용자 정보는 다음과 같은 형식으로 표현됩니다
alice,4bc01e30-406b-4514,"system:authenticated,developers","scopes:openid"
| 필드 | 값 | 설명 |
|---|---|---|
| Username | alice | 사용자 이름 |
| UID | 4bc01e30-406b-4514 | 고유 사용자 ID |
| Groups | system:authenticated,developers | 소속 그룹 목록 |
| Extra | scopes:openid | 추가 정보 (인증 방식 등) |
system: 접두사가 붙은 사용자/그룹은 Kubernetes 내부용이므로, 직접 생성하지 않습니다.
ServiceAccount는 Pod의 워크로드 신원(workload identity)을 나타내는 Kubernetes 리소스입니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: my-app-sa
namespace: production
automountServiceAccountToken: false # 보안 권장: 필요 없으면 비활성화
imagePullSecrets: # 프라이빗 레지스트리 인증
- name: my-registry-secret
secrets: # 마운트 가능한 Secret 제한
- name: my-app-secret
ServiceAccount 사용자 이름 형식
system:serviceaccount:<namespace>:<name>
예시:
system:serviceaccount:production:my-app-sa
imagePullSecrets
mountable secrets (secrets 필드)
kubernetes.io/enforce-mountable-secrets: "true" 어노테이션과 함께 사용apiVersion: v1
kind: ServiceAccount
metadata:
name: restricted-sa
namespace: production
annotations:
kubernetes.io/enforce-mountable-secrets: "true" # 제한 활성화
secrets:
- name: allowed-secret-1
- name: allowed-secret-2
# 이 SA를 사용하는 Pod는 allowed-secret-1, allowed-secret-2만 마운트 가능
Pod가 생성되면 연결된 ServiceAccount의 토큰이 자동으로 마운트됩니다.
apiVersion: v1
kind: Pod
metadata:
name: my-app
spec:
serviceAccountName: my-app-sa
containers:
- name: app
image: my-app:latest
# 토큰은 아래 경로에 자동 마운트됨
# /var/run/secrets/kubernetes.io/serviceaccount/token
Kubernetes 1.24+ 변경사항
Projected Volume 토큰이 기본값이 되었습니다.
토큰이 Pod 수명과 연동되어 자동 회전되며, 만료 시간 설정이 가능합니다.
시스템 사용자
| Username | 용도 |
|---|---|
system:anonymous | 익명 요청 |
system:apiserver | API 서버 자체 |
system:kube-controller-manager | 컨트롤러 매니저 |
system:kube-scheduler | 스케줄러 |
시스템 그룹
| Group | 용도 |
|---|---|
system:unauthenticated | 인증되지 않은 모든 요청 |
system:authenticated | 인증된 사용자 |
system:masters | 클러스터 무제한 접근 권한 |
system:serviceaccounts | 모든 ServiceAccount |
system:serviceaccounts:<ns> | 특정 네임스페이스의 ServiceAccount |
RBAC는 "누가(Subject) 무엇을(Resource) 어떻게(Verb) 할 수 있는가"를 정의합니다.

핵심 관계:
Role은 특정 네임스페이스 내에서 허용되는 작업을 정의합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-reader
namespace: production # Role은 항상 네임스페이스에 종속됨
rules:
- apiGroups: [""] # 코어 API 그룹 (빈 문자열)
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get"]
| 필드 | 설명 | 예시 |
|---|---|---|
| apiGroups | API 그룹 | "" (코어), apps, networking.k8s.io |
| resources | 리소스 종류 | pods, deployments, secrets |
| verbs | 허용 작업 | get, list, create, delete |
| resourceNames | 특정 리소스 이름 (선택) | ["my-config"] |
Verb → HTTP 메서드 매핑:
| Verbs | HTTP | 설명 |
|---|---|---|
get | GET | 단일 리소스 조회 |
list | GET | 리소스 목록 조회 |
watch | GET (streaming) | 변경 이벤트 감시 |
create | POST | 리소스 생성 |
update | PUT | 리소스 전체 수정 |
patch | PATCH | 리소스 부분 수정 |
delete | DELETE | 단일 리소스 삭제 |
deletecollection | DELETE | 다수 리소스 삭제 |
RoleBinding은 Subject와 Role을 연결합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: read-pods-binding
namespace: production
subjects:
- kind: User
name: alice
apiGroup: rbac.authorization.k8s.io
- kind: ServiceAccount
name: ci-bot
namespace: ci-cd # 다른 네임스페이스의 SA도 가능
- kind: Group
name: developers
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: pod-reader
apiGroup: rbac.authorization.k8s.io
ClusterRole은 클러스터 전체에 적용되는 Role입니다.

사용 시나리오
| 시나리오 | Role 유형 | Binding 유형 |
|---|---|---|
| 특정 NS 리소스 접근 | Role | RoleBinding |
| 여러 NS에서 동일 규칙 재사용 | ClusterRole | RoleBinding (각 NS) |
| 클러스터 전역 리소스 (CRD, Node) | ClusterRole | ClusterRoleBinding |
| 모든 NS의 특정 리소스 접근 | ClusterRole | ClusterRoleBinding |
| ClusterRole | 권한 | 사용 시 주의 |
|---|---|---|
view | 대부분 읽기 (Secret 제외) | 안전, 개발자용 |
edit | 대부분 읽기/수정 | Role/RoleBinding 수정 불가 |
admin | 네임스페이스 내 완전 제어 | Role/RoleBinding 수정 가능 |
cluster-admin | 클러스터 전체 완전 제어 | ⚠️ 절대 Pod에 부여 금지 |
여러 ClusterRole의 권한을 동적으로 결합할 수 있습니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: custom-view
labels:
rbac.authorization.k8s.io/aggregate-to-view: "true" # view에 자동 포함
rules:
- apiGroups: ["mycompany.io"]
resources: ["myresources"]
verbs: ["get", "list", "watch"]
CRD 권한 추가 시 aggregate-to-view 레이블을 사용하면
view Role을 가진 모든 사용자에게 해당 권한이 자동 부여됩니다.
특정 이름의 리소스에만 접근을 허용하려면 resourceNames 필드를 사용합니다.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: configmap-updater
namespace: production
rules:
# 특정 ConfigMap만 수정 가능
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config", "feature-flags"] # 이 두 개만 허용
verbs: ["get", "update", "patch"]
# 모든 ConfigMap 읽기는 허용
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
[주의사항]
resourceNames는 create verb와 함께 사용할 수 없습니다.
생성 시점에는 리소스 이름이 아직 존재하지 않기 때문입니다.
Pod의 로그 조회, exec 실행 등은 Subresource로 별도 권한이 필요합니다.

Subresource 권한 예시
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: pod-debugger
namespace: production
rules:
# Pod 조회
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list"]
# 로그 조회 (kubectl logs)
- apiGroups: [""]
resources: ["pods/log"]
verbs: ["get"]
# exec 실행 (kubectl exec) - 주의: 매우 강력한 권한
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"]
# port-forward (kubectl port-forward)
- apiGroups: [""]
resources: ["pods/portforward"]
verbs: ["create"]
주요 Subresource 목록:
| Subresource | 용도 | 필요 Verb |
|---|---|---|
pods/log | 컨테이너 로그 조회 | get |
pods/exec | 컨테이너 내 명령 실행 | create |
pods/portforward | 포트 포워딩 | create |
pods/status | Pod 상태 수정 | update, patch |
deployments/scale | Deployment 스케일 조정 | update, patch |
services/proxy | 서비스 프록시 | get, create |
pods/exec과 pods/portforward는 컨테이너 내부에 직접 접근할 수 있어 매우 강력합니다.
프로덕션 환경에서는 제한적으로 부여해야 합니다.
Kubernetes는 자신이 가지지 않은 권한을 다른 사용자에게 부여하는 것을 방지합니다.

Escalation 제어 Verb
| Verb | 설명 | 사용 사례 |
|---|---|---|
escalate | Role/ClusterRole 수정 시 권한 상승 허용 | Role 관리자 |
bind | 자신이 가지지 않은 Role도 Binding 가능 | RBAC 관리자 |
예시: RBAC 관리자 Role
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: rbac-manager
rules:
# Role과 ClusterRole 관리
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "clusterroles"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# RoleBinding과 ClusterRoleBinding 관리
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "clusterrolebindings"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
# 권한 상승 허용 (매우 주의!)
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["roles", "clusterroles"]
verbs: ["escalate"]
- apiGroups: ["rbac.authorization.k8s.io"]
resources: ["rolebindings", "clusterrolebindings"]
verbs: ["bind"]
escalate와 bind 권한은 실질적으로 cluster-admin과 동등한 권한을 부여할 수 있으므로,
극히 제한된 관리자에게만 부여해야 합니다.
인증과 인가를 통과한 요청에 대해 추가 검증 및 수정을 수행합니다.

| 기능 | 설명 | 예시 |
|---|---|---|
| 기본값 설정 | 리소스에 기본값 자동 적용 | StorageClass 기본 설정 |
| 검증 | 리소스 스펙 유효성 검사 | 리소스 제한 검증 |
| 정책 적용 | 조직 정책 강제 | 특정 레지스트리만 허용 |
Admission Webhook은 API 서버가 외부 HTTP 서비스를 호출하여 요청을 검증하거나 수정하는 방식입니다.

Webhook 호출 순서
Mutating Webhook이 여러 개인 경우, 순서에 따라 결과가 달라질 수 있습니다.
reinvocationPolicy: IfNeeded를 설정하면 다른 Webhook의 수정 후 재호출됩니다.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: pod-policy-validator
webhooks:
- name: validate.pods.example.com
# Webhook 서비스 위치
clientConfig:
service:
name: pod-validator-service
namespace: validation-system
path: /validate
port: 443
caBundle: <base64-encoded-ca-cert> # Webhook 서버 인증서 검증용
# 대상 리소스 지정
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
scope: Namespaced # Namespaced 또는 Cluster
# 네임스페이스 필터링
namespaceSelector:
matchExpressions:
- key: environment
operator: In
values: ["production", "staging"]
# 실패 시 동작 (Fail: 거부, Ignore: 허용)
failurePolicy: Fail
# 사이드이펙트 없음 선언 (Dry-run 요청도 처리)
sideEffects: None
# 타임아웃 (기본 10초, 최대 30초)
timeoutSeconds: 5
# 요청에 포함할 정보
admissionReviewVersions: ["v1", "v1beta1"]
# 동일 객체의 여러 변경 처리 방식
matchPolicy: Equivalent
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
name: sidecar-injector
webhooks:
- name: inject.sidecar.example.com
clientConfig:
service:
name: sidecar-injector
namespace: istio-system
path: /inject
port: 443
caBundle: <base64-encoded-ca-cert>
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE"]
resources: ["pods"]
# 특정 레이블이 있는 네임스페이스만 대상
namespaceSelector:
matchLabels:
sidecar-injection: enabled
# 특정 레이블이 있는 Pod는 제외
objectSelector:
matchExpressions:
- key: sidecar.istio.io/inject
operator: NotIn
values: ["false"]
failurePolicy: Ignore # 주입 실패해도 Pod 생성은 허용
sideEffects: None
admissionReviewVersions: ["v1"]
# Mutating Webhook은 재호출 정책 설정 가능
reinvocationPolicy: IfNeeded
| 항목 | 권장 | 이유 |
|---|---|---|
| failurePolicy | Fail (중요 정책) | 보안 정책은 우회되면 안 됨 |
| timeoutSeconds | 5초 이하 | API 응답 지연 방지 |
| sideEffects | None | Dry-run 지원, 캐싱 가능 |
| 고가용성 | 최소 2개 Pod | Webhook 장애 시 전체 API 차단 방지 |
| namespaceSelector | kube-system 제외 | 시스템 컴포넌트 영향 방지 |
kube-system 제외 예시
namespaceSelector:
matchExpressions:
- key: kubernetes.io/metadata.name
operator: NotIn
values: ["kube-system", "kube-public"]
운영 주의: Webhook 서비스가 다운되면 failurePolicy: Fail인 경우
모든 관련 API 요청이 실패합니다. 반드시 고가용성을 확보하세요.
| 버전 | 변경사항 | 영향 |
|---|---|---|
| 1.24 | ServiceAccount Token Secret 자동 생성 제거 | Projected Volume 필수 |
| 1.27 | ValidatingAdmissionPolicy Beta | CEL 기반 정책 |
| 1.30 | ValidatingAdmissionPolicy GA | 프로덕션 사용 가능 |
| 1.31 | Bound ServiceAccount Token 개선 | audience 분리 강화 |
| 1.32 | AuthorizeWithSelectors (Beta) | 필드 셀렉터 기반 인가 |
OPA/Gatekeeper 없이 Kubernetes 네이티브로 정책을 정의할 수 있습니다.
# Privileged 컨테이너 차단 정책
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: deny-privileged
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "!object.spec.containers.exists(c, c.securityContext.privileged == true)"
message: "Privileged containers are not allowed"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: deny-privileged-binding
spec:
policyName: deny-privileged
validationActions: ["Deny"]
matchResources:
namespaceSelector:
matchLabels:
environment: production
CEL vs OPA Gatekeeper
| 항목 | CEL (ValidatingAdmissionPolicy) | OPA Gatekeeper |
|---|---|---|
| 설치 | 내장 (1.30+) | 별도 설치 |
| 언어 | CEL | Rego |
| 학습 곡선 | 낮음 | 높음 |
| 성능 | 매우 빠름 | Webhook 오버헤드 |
| 복잡한 정책 | 제한적 | 강력함 |
외부 시스템 연동 시 토큰 재사용을 방지하기 위한 audience 분리 전략입니다.
apiVersion: v1
kind: Pod
metadata:
name: vault-client
spec:
serviceAccountName: vault-sa
containers:
- name: app
image: my-app
volumeMounts:
- name: vault-token
mountPath: /var/run/secrets/vault
volumes:
- name: vault-token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 600
audience: "<https://vault.example.com>" # Vault 전용 토큰
보안 이점: K8s API용 토큰이 탈취되어도 Vault는 audience가 다르므로 접근 차단됩니다.
Kubernetes v1.25+에서 GA된 Pod 보안 표준 강제 메커니즘입니다.
PodSecurityPolicy(PSP)의 대체 기능으로, 네임스페이스 레벨에서 Pod 보안 정책을 적용합니다.
flowchart TD
subgraph Levels["보안 레벨"]
Privileged["privileged<br/>(제한 없음)"]
Baseline["baseline<br/>(기본 보안)"]
Restricted["restricted<br/>(강화된 보안)"]
end
subgraph Modes["적용 모드"]
Enforce["enforce<br/>(차단)"]
Audit["audit<br/>(감사 로그)"]
Warn["warn<br/>(경고)"]
end
Privileged --> Enforce
Baseline --> Audit
Restricted --> Warn
보안 레벨 비교
| 레벨 | 설명 | 제한 사항 |
|---|---|---|
privileged | 제한 없음, 레거시 호환 | 없음 |
baseline | 알려진 권한 상승 방지 | hostNetwork, hostPID, privileged 컨테이너 차단 |
restricted | 강화된 보안 정책 | runAsNonRoot 필수, 특정 capabilities만 허용 |
네임스페이스에 PSA 적용
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
# 정책 위반 시 Pod 생성 차단
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.30
# 정책 위반 시 감사 로그 기록
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.30
# 정책 위반 시 사용자에게 경고
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.30
restricted 레벨을 통과하는 Pod 예시
apiVersion: v1
kind: Pod
metadata:
name: secure-pod
namespace: production
spec:
securityContext:
runAsNonRoot: true
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-app:latest
securityContext:
allowPrivilegeEscalation: false
capabilities:
drop: ["ALL"]
readOnlyRootFilesystem: true
runAsNonRoot: true
이미지 레지스트리 제한
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: restrict-image-registry
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: |
object.spec.containers.all(c,
c.image.startsWith('registry.company.com/') ||
c.image.startsWith('gcr.io/google-containers/')
)
message: "이미지는 허용된 레지스트리에서만 가져올 수 있습니다"
리소스 제한 필수화
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-resource-limits
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: |
object.spec.containers.all(c,
has(c.resources) &&
has(c.resources.limits) &&
has(c.resources.limits.memory) &&
has(c.resources.limits.cpu)
)
message: "모든 컨테이너에 CPU/메모리 limits를 설정해야 합니다"
레이블 필수화
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-labels
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: |
has(object.metadata.labels) &&
has(object.metadata.labels.app) &&
has(object.metadata.labels.owner)
message: "Deployment에는 'app'과 'owner' 레이블이 필수입니다"
유용한 CEL 표현식 패턴
| 패턴 | CEL 표현식 | 용도 |
|---|---|---|
| 모든 항목 검증 | list.all(item, condition) | 모든 컨테이너 검증 |
| 하나라도 만족 | list.exists(item, condition) | 특정 조건 존재 확인 |
| 필드 존재 확인 | has(object.field) | 선택 필드 검증 |
| 문자열 시작 | string.startsWith('prefix') | 이미지 레지스트리 검증 |
| 문자열 포함 | string.contains('substr') | 이름 규칙 검증 |
| 정규식 매칭 | string.matches('regex') | 복잡한 패턴 검증 |
기존 RBAC는 리소스 종류만 제어했지만, AuthorizeWithSelectors는 필드/레이블 셀렉터 기반으로 더 세밀한 인가가 가능합니다.
flowchart LR
subgraph Traditional["기존 RBAC"]
T1["pods 리소스"] --> T2["모든 Pod 접근"]
end
subgraph New["AuthorizeWithSelectors"]
N1["pods 리소스"] --> N2["environment=dev인<br/>Pod만 접근"]
end
사용 예시 - 특정 레이블의 Pod만 접근 허용
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dev-pod-viewer
rules:
- apiGroups: [""]
resources: ["pods"]
verbs: ["get", "list", "watch"]
# v1.32+ Beta 기능
fieldSelector:
- key: metadata.namespace
operator: In
values: ["dev", "staging"]
labelSelector:
- key: environment
operator: In
values: ["development"]
주의: 이 기능은 v1.32에서 Beta입니다. --authorization-config 설정에서 활성화해야 합니다.
활성화 방법:
# kube-apiserver 시작 옵션
--feature-gates=AuthorizeWithSelectors=true
# /etc/kubernetes/authorization-config.yaml
apiVersion: apiserver.config.k8s.io/v1
kind: AuthorizationConfiguration
authorizers:
- type: RBAC
name: rbac
rbac:
# 셀렉터 기반 인가 활성화
allowFieldSelectors: true
allowLabelSelectors: true
- type: Node
name: node
# kube-apiserver 시작 옵션
--authorization-config=/etc/kubernetes/authorization-config.yaml
--authorization-config를 사용하면 기존--authorization-mode플래그는 무시됩니다.
모든 authorizer를 설정 파일에 명시해야 합니다.
버전별 상태
| 버전 | 상태 | Feature Gate | authorization-config |
|---|---|---|---|
| v1.31 | Alpha | 수동 활성화 필요 | 필수 |
| v1.32 | Beta | 기본 활성화 | 필수 |
| v1.33+ | GA 예정 | 기본 활성화 | 필수 |

v1.24 마이그레이션 - ServiceAccount Token
❌ 이전 방식 (v1.24 이전) - 더 이상 자동 생성 안 됨
apiVersion: v1
kind: Secret
metadata:
name: my-sa-token
annotations:
kubernetes.io/service-account.name: my-sa
type: kubernetes.io/service-account-token
✅ 권장 방식 (v1.24+) - Projected Volume 사용
apiVersion: v1
kind: Pod
spec:
containers:
- name: app
volumeMounts:
- name: token
mountPath: /var/run/secrets/tokens
volumes:
- name: token
projected:
sources:
- serviceAccountToken:
path: token
expirationSeconds: 3600
v1.25 마이그레이션 - PSP → PSA
| PSP 설정 | PSA 레벨 | 조치 |
|---|---|---|
privileged: true 허용 | privileged | 네임스페이스 레이블 설정 |
hostNetwork, hostPID 차단 | baseline | 네임스페이스 레이블 설정 |
runAsNonRoot, capabilities 제한 | restricted | Pod 스펙 수정 필요 |
마이그레이션 체크리스트
# 1. 현재 PSP 사용 현황 확인
kubectl get psp
# 2. 각 네임스페이스의 Pod 보안 수준 확인
kubectl label --dry-run=server --overwrite ns my-ns \\
pod-security.kubernetes.io/enforce=restricted
# 3. PSA 경고 모드로 먼저 테스트
kubectl label ns my-ns \\
pod-security.kubernetes.io/warn=restricted \\
pod-security.kubernetes.io/warn-version=latest
# 4. 문제 없으면 enforce 모드 적용
kubectl label ns my-ns \\
pod-security.kubernetes.io/enforce=restricted \\
pod-security.kubernetes.io/enforce-version=v1.30
| 항목 | 권장 | 비권장 |
|---|---|---|
| 권한 범위 | 최소 권한 원칙 | 와일드카드(*) 남용 |
| ServiceAccount | 전용 SA 생성 | default SA 사용 |
| 토큰 마운트 | automountServiceAccountToken: false | 무조건 true |
| Binding 유형 | RoleBinding 우선 | 불필요한 ClusterRoleBinding |
| cluster-admin | 관리자 전용 | ⚠️ 절대 Pod에 부여 금지 |
# cluster-admin 권한을 가진 ServiceAccount 확인
kubectl get clusterrolebindings -o json | \\
jq -r '.items[] | select(.roleRef.name == "cluster-admin") | .subjects[]? | "\\(.namespace)/\\(.name)"'
# 특정 SA의 모든 권한 나열
kubectl auth can-i --list \\
--as system:serviceaccount:production:my-sa \\
-n production
# 특정 작업 가능 여부 확인
kubectl auth can-i create pods \\
--as system:serviceaccount:production:my-sa \\
-n production

# rakkess - 권한 매트릭스 조회
kubectl krew install access-matrix
kubectl access-matrix --as system:serviceaccount:default:my-sa
# rbac-lookup - RBAC 관계 조회
kubectl krew install rbac-lookup
kubectl rbac-lookup alice

선택 기준 상세:
| 시나리오 | Role 유형 | Binding 유형 | 예시 |
|---|---|---|---|
| 단일 NS에서만 작업 | Role | RoleBinding | 개발자의 dev NS 접근 |
| 동일 규칙을 여러 NS에 적용 | ClusterRole | RoleBinding (각 NS) | 모니터링 에이전트 |
| 모든 NS의 리소스 접근 | ClusterRole | ClusterRoleBinding | 클러스터 관리자 |
| 클러스터 범위 리소스 접근 | ClusterRole | ClusterRoleBinding | Node 관리, CRD 조회 |
권장: 가능한 한 Role + RoleBinding을 우선 사용하세요. 권한 범위가 작을수록 보안 위험이 줄어듭니다.
# 위험한 예시 - 절대 사용 금지
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: dangerous-role
rules:
- apiGroups: ["*"] # 모든 API 그룹
resources: ["*"] # 모든 리소스
verbs: ["*"] # 모든 작업
왜 위험한가?

구체적인 위험 사례:
| 와일드카드 사용 | 의도하지 않은 접근 | 위험도 |
|---|---|---|
resources: ["*"] | 새로 설치된 CRD 리소스 자동 접근 | 높음 |
verbs: ["*"] | delete, deletecollection 포함 | 높음 |
apiGroups: ["*"] | 모든 서드파티 API 접근 | 높음 |
resourceNames: [] (빈 배열 아님) | 해당 리소스 전체 접근 | 중간 |
올바른 권한 설계:
# 권장 - 명시적으로 필요한 권한만 나열
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-developer
namespace: development
rules:
# Pod 관리
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch", "create", "delete"]
# Deployment 관리
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
# ConfigMap 읽기
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list"]
# Secret은 의도적으로 제외
최소 권한 원칙: "필요한 권한만, 필요한 범위에서, 필요한 기간 동안만" 부여해야 한다.
다음 권한 조합은 보안상 특히 주의가 필요합니다
| 권한 조합 | 위험성 | 대안 |
|---|---|---|
secrets + get/list | 모든 Secret 접근 가능 | resourceNames로 특정 Secret만 허용 |
pods/exec + create | 컨테이너 내부 명령 실행 | 디버깅 용도로만 제한적 부여 |
* + * + * | cluster-admin과 동등 | 명시적 권한 나열 |
escalate / bind | 권한 상승 가능 | RBAC 관리자만 부여 |
impersonate | 다른 사용자로 가장 | 감사/긴급 대응용으로만 |
# 와일드카드 권한을 가진 ClusterRole 찾기
kubectl get clusterroles -o json | \\
jq -r '.items[] | select(.rules[]? | .resources[]? == "*" or .verbs[]? == "*") | .metadata.name'

완전한 보안을 위해서는 다음이 함께 필요합니다
| 영역 | 도구/정책 | 역할 |
|---|---|---|
| 네트워크 | NetworkPolicy | Pod 간 통신 제어 |
| 컨테이너 | Pod Security Standards | 보안 컨텍스트 강제 |
| 정책 | ValidatingAdmissionPolicy | 리소스 정책 검증 |
| 런타임 | Falco, Sysdig | 런타임 위협 탐지 |
| 시크릿 | Vault, Sealed Secrets | 외부 Secret 관리 |