Pod Security — 컨테이너 실행 권한을 제한하는 방법
컨테이너가 root 권한으로 실행되고, 호스트 네트워크에 접근할 수 있다면, 컨테이너 격리의 의미가 있을까요?
컨테이너는 기본적으로 호스트와 격리되지만, 설정에 따라 격리가 무너질 수 있습니다. privileged 모드로 실행되거나, root 사용자로 동작하거나, 호스트의 네임스페이스에 접근하면 컨테이너 탈출(container escape)의 위험이 커집니다. Pod Security는 이런 위험한 설정을 제한하는 방어 체계입니다.
securityContext — 컨테이너 보안 설정
securityContext는 Pod이나 컨테이너 수준에서 보안 관련 설정을 제어합니다.
컨테이너 수준
spec:
containers:
- name: app
image: my-app:1.0
securityContext:
runAsNonRoot: true # root 실행 거부
runAsUser: 1000 # UID 1000으로 실행
runAsGroup: 1000 # GID 1000으로 실행
readOnlyRootFilesystem: true # 루트 파일시스템 읽기 전용
allowPrivilegeEscalation: false # 권한 상승 차단
capabilities:
drop:
- ALL # 모든 리눅스 capability 제거
add:
- NET_BIND_SERVICE # 필요한 것만 추가
Pod 수준
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000 # 볼륨의 파일 그룹 소유권
seccompProfile:
type: RuntimeDefault # seccomp 프로파일 적용
주요 설정 항목
| 설정 | 효과 | 권장 |
|---|---|---|
runAsNonRoot: true | root 실행 차단 | 필수 |
readOnlyRootFilesystem: true | 쓰기 차단 | 권장 |
allowPrivilegeEscalation: false | setuid 등 차단 | 필수 |
capabilities.drop: [ALL] | 모든 리눅스 기능 제거 | 권장 |
privileged: false | 특권 모드 차단 | 필수 |
# 보안 강화된 Pod 예제
apiVersion: v1
kind: Pod
metadata:
name: secure-app
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-app:1.0
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp # 쓰기 필요한 경로만 emptyDir로
- name: cache
mountPath: /app/cache
volumes:
- name: tmp
emptyDir: {}
- name: cache
emptyDir: {}
readOnlyRootFilesystem을 설정하면 애플리케이션이 파일을 쓸 수 없으므로, 쓰기가 필요한 디렉토리는 emptyDir로 마운트합니다.
Pod Security Admission (PSA)
PSA는 Kubernetes 1.25에서 정식(GA) 기능이 된 내장 보안 정책입니다. 네임스페이스에 레이블을 추가하여 세 가지 프로파일을 적용합니다.
세 가지 프로파일
| 프로파일 | 제한 수준 | 허용 범위 |
|---|---|---|
| Privileged | 제한 없음 | 모든 설정 허용 |
| Baseline | 최소 제한 | 알려진 위험 설정만 차단 (privileged, hostNetwork 등) |
| Restricted | 최대 제한 | 비root, 읽기 전용 FS, capability 제거 등 요구 |
PSA 적용 방법
# 네임스페이스에 PSA 레이블 추가
kubectl label namespace production \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/audit=restricted \
pod-security.kubernetes.io/warn=restricted
세 가지 동작 모드
| 모드 | 동작 |
|---|---|
enforce | 위반 시 Pod 생성 거부 |
audit | 위반 시 감사 로그 기록 (생성은 허용) |
warn | 위반 시 사용자에게 경고 표시 (생성은 허용) |
점진적 도입 시 warn → audit → enforce 순서로 적용하는 것이 안전합니다.
# 네임스페이스에 직접 설정
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/warn: restricted
Baseline에서 차단되는 항목
privileged: truehostNetwork: truehostPID: true,hostIPC: true- 위험한 capabilities (SYS_ADMIN 등)
/proc마운트 변경
Restricted에서 추가로 요구하는 항목
runAsNonRoot: trueallowPrivilegeEscalation: falseseccompProfile설정- capabilities
drop: [ALL] runAsUser(UID 0 차단)
OPA Gatekeeper — 커스텀 정책
PSA로 커버되지 않는 세밀한 정책은 OPA(Open Policy Agent) Gatekeeper로 구현합니다.
# Gatekeeper 설치
kubectl apply -f https://raw.githubusercontent.com/open-policy-agent/gatekeeper/v3.15.0/deploy/gatekeeper.yaml
정책 예제: 허용된 이미지 레지스트리만 사용
# ConstraintTemplate — 정책 템플릿 정의
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 startswith(container.image, input.parameters.repos[_])
msg := sprintf("이미지 '%v'는 허용된 레지스트리가 아닙니다", [container.image])
}
---
# Constraint — 정책 적용
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: allowed-repos
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["production"]
parameters:
repos:
- "myregistry.io/"
- "docker.io/library/"
다른 유용한 Gatekeeper 정책
- 특정 레이블 필수 (모든 리소스에 team 레이블 요구)
- 리소스 제한 필수 (requests/limits 없는 Pod 차단)
- 최신 태그 차단 (
latest태그 사용 금지) - 외부 로드밸런서 제한
Kyverno — YAML 기반 정책
Gatekeeper의 대안으로, Rego 대신 YAML로 정책을 작성할 수 있는 Kyverno도 있습니다.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-labels
spec:
validationFailureAction: Enforce
rules:
- name: check-team-label
match:
any:
- resources:
kinds: ["Deployment"]
validate:
message: "team 레이블이 필요합니다"
pattern:
metadata:
labels:
team: "?*"
보안 강화 체크리스트
# 프로덕션 워크로드 보안 체크리스트
spec:
serviceAccountName: dedicated-sa # 전용 ServiceAccount
automountServiceAccountToken: false # 불필요한 API 토큰 마운트 차단
securityContext:
runAsNonRoot: true
runAsUser: 1000
fsGroup: 2000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: myregistry.io/app:v1.2.3 # latest 태그 사용 금지
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
requests:
cpu: 200m
memory: 256Mi
limits:
memory: 512Mi
정리
Pod Security는 securityContext로 개별 컨테이너의 권한을 제한하고, PSA로 네임스페이스 수준의 정책을 적용하며, OPA Gatekeeper/Kyverno로 커스텀 정책을 구현하는 다층 방어 체계입니다. runAsNonRoot, readOnlyRootFilesystem, capabilities drop ALL은 프로덕션 워크로드의 기본 설정으로 생각하는 것이 좋습니다.