Post

Part 18/26: SecurityContext와 PSA

Part 18/26: SecurityContext와 PSA

Kubernetes에서 Pod와 컨테이너의 보안 설정은 SecurityContextPod Security Admission(PSA)을 통해 제어한다. 이 기능들은 컨테이너가 호스트 시스템에 미치는 영향을 제한하고, 최소 권한 원칙을 적용하는 데 핵심적이다.

SecurityContext

개념

SecurityContext는 Pod와 컨테이너의 권한 및 접근 제어 설정을 정의한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
apiVersion: v1
kind: Pod
metadata:
  name: security-pod
spec:
  securityContext:        # Pod 수준 (모든 컨테이너에 적용)
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
  containers:
  - name: app
    image: nginx
    securityContext:      # 컨테이너 수준 (이 컨테이너만)
      runAsNonRoot: true
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL

적용 우선순위: 컨테이너 수준 > Pod 수준

주요 설정 옵션

runAsUser / runAsGroup

컨테이너 프로세스를 실행할 UID/GID를 지정한다.

1
2
3
securityContext:
  runAsUser: 1000    # UID 1000으로 실행
  runAsGroup: 3000   # GID 3000으로 실행
1
2
3
# Pod 내부에서 확인
kubectl exec security-pod -- id
# uid=1000 gid=3000 groups=3000,2000

runAsNonRoot

root(UID 0)로 실행되는 것을 방지한다.

1
2
securityContext:
  runAsNonRoot: true  # root로 실행 시 컨테이너 시작 실패

이미지가 root로 실행되도록 설정된 경우 (예: nginx 기본 이미지):

  • runAsNonRoot: true 설정 시 컨테이너가 시작되지 않음
  • runAsUser로 non-root UID를 명시해야 함

fsGroup

볼륨에 접근할 때 사용할 supplementary group을 설정한다.

1
2
securityContext:
  fsGroup: 2000  # 마운트된 볼륨의 그룹 소유권

마운트된 볼륨의 파일들이 이 GID 소유로 변경된다.

readOnlyRootFilesystem

컨테이너의 루트 파일시스템을 읽기 전용으로 설정한다.

1
2
securityContext:
  readOnlyRootFilesystem: true

주의: 로그 파일, 임시 파일을 쓰려면 emptyDir 볼륨을 마운트해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spec:
  containers:
  - name: app
    image: myapp
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - name: tmp
      mountPath: /tmp
    - name: logs
      mountPath: /var/log
  volumes:
  - name: tmp
    emptyDir: {}
  - name: logs
    emptyDir: {}

allowPrivilegeEscalation

프로세스가 부모보다 더 많은 권한을 얻는 것을 방지한다.

1
2
securityContext:
  allowPrivilegeEscalation: false  # setuid 비트 등 무효화

privileged

컨테이너에 모든 호스트 권한을 부여한다. 매우 위험.

1
2
securityContext:
  privileged: true  # 호스트의 모든 디바이스 접근 가능

사용 사례: 네트워크 플러그인, 스토리지 드라이버 등 시스템 컴포넌트에만 필요.

Linux Capabilities

root 권한을 세분화하여 필요한 것만 부여한다.

1
2
3
4
5
6
securityContext:
  capabilities:
    drop:
    - ALL              # 모든 capability 제거
    add:
    - NET_BIND_SERVICE # 1024 미만 포트 바인딩만 허용

주요 Capabilities:

Capability설명
NET_BIND_SERVICE1024 미만 포트 바인딩
NET_RAWraw 소켓 사용
SYS_ADMIN시스템 관리 권한 (매우 넓음)
SYS_PTRACE프로세스 추적
SYS_TIME시스템 시간 변경
CHOWN파일 소유권 변경
DAC_OVERRIDE파일 권한 무시

최소 권한 원칙: drop: ALL 후 필요한 것만 add.

seccompProfile

시스템 콜을 제한한다.

1
2
3
4
5
6
securityContext:
  seccompProfile:
    type: RuntimeDefault  # 컨테이너 런타임 기본 프로파일
    # 또는
    # type: Localhost
    # localhostProfile: profiles/custom.json

type 옵션:

  • Unconfined: 제한 없음 (기본값)
  • RuntimeDefault: 런타임(containerd, CRI-O)의 기본 프로파일
  • Localhost: 노드의 커스텀 프로파일

완전한 보안 설정 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      runAsNonRoot: true
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

Pod Security Admission (PSA)

개념

Pod Security Admission은 Kubernetes 1.25+에서 기본으로 활성화된 Admission Controller이다. PodSecurityPolicy(PSP)를 대체한다.

PSA는 Pod Security Standards(PSS)를 기준으로 Pod의 보안 설정을 검증한다.

Pod Security Standards (PSS)

세 가지 수준(Level)을 정의한다:

Level설명사용 사례
Privileged제한 없음시스템 컴포넌트
Baseline알려진 권한 상승 방지대부분의 워크로드
Restricted강화된 보안민감한 워크로드

PSA 모드

Mode동작
enforce위반 시 Pod 생성 거부
audit위반 시 감사 로그에 기록
warn위반 시 경고 메시지 출력

Namespace에 PSA 적용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
apiVersion: v1
kind: Namespace
metadata:
  name: production
  labels:
    # enforce 모드로 restricted 수준 적용
    pod-security.kubernetes.io/enforce: restricted
    pod-security.kubernetes.io/enforce-version: latest
    # audit 모드도 함께 적용
    pod-security.kubernetes.io/audit: restricted
    pod-security.kubernetes.io/audit-version: latest
    # warn 모드도 함께 적용
    pod-security.kubernetes.io/warn: restricted
    pod-security.kubernetes.io/warn-version: latest
1
2
3
4
# 기존 namespace에 라벨 추가
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=restricted \
  pod-security.kubernetes.io/warn=restricted

PSS 각 수준의 제한 사항

Baseline이 차단하는 것:

  • hostNetwork, hostPID, hostIPC
  • privileged 컨테이너
  • 특정 위험한 capabilities (NET_RAW 등은 허용)
  • hostPath 볼륨 (일부)
  • /proc mount propagation

Restricted가 추가로 제한하는 것:

  • root(UID 0)로 실행
  • privilege escalation
  • 모든 capabilities (drop: ALL 필요)
  • 모든 hostPath 볼륨
  • seccomp 프로파일 필수 (RuntimeDefault 이상)

Restricted 수준 호환 Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
  name: restricted-compliant
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL

PSA 예외 처리

특정 리소스나 사용자를 예외 처리할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# AdmissionConfiguration으로 설정
apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: PodSecurity
  configuration:
    apiVersion: pod-security.admission.config.k8s.io/v1
    kind: PodSecurityConfiguration
    defaults:
      enforce: baseline
      enforce-version: latest
    exemptions:
      usernames:
      - system:serviceaccount:kube-system:*
      namespaces:
      - kube-system
      runtimeClasses:
      - special-runtime

실전 보안 설정 패턴

최소 권한 Pod

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apiVersion: v1
kind: Pod
metadata:
  name: minimal-privileges
spec:
  automountServiceAccountToken: false  # SA 토큰 마운트 안 함
  securityContext:
    runAsUser: 65534    # nobody
    runAsGroup: 65534
    fsGroup: 65534
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: distroless/static:latest
    securityContext:
      runAsNonRoot: true
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
    resources:
      limits:
        cpu: "100m"
        memory: "128Mi"

네트워크 애플리케이션

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: v1
kind: Pod
metadata:
  name: network-app
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 1000
  containers:
  - name: web
    image: nginx:unprivileged  # non-root nginx
    securityContext:
      allowPrivilegeEscalation: false
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE  # 80 포트 사용 시

시스템 컴포넌트 (예: CNI)

1
2
3
4
5
6
7
8
9
10
11
12
# Privileged가 필요한 경우 (최소화)
apiVersion: v1
kind: Pod
metadata:
  name: cni-installer
spec:
  hostNetwork: true
  containers:
  - name: cni
    image: calico/cni
    securityContext:
      privileged: true  # 필요한 경우에만

트러블슈팅

Pod 생성 실패 (PSA)

1
2
3
4
5
6
7
8
9
10
11
# 에러 예시
Error from server (Forbidden): pods "test" is forbidden: violates PodSecurity "restricted:latest":
allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true

# 해결: Pod 보안 설정 수정
securityContext:
  runAsNonRoot: true
  allowPrivilegeEscalation: false
  capabilities:
    drop:
    - ALL

컨테이너가 root로 실행되는 경우

1
2
3
4
5
6
7
8
9
10
11
12
# 이미지 기본 사용자 확인
docker inspect nginx:latest | grep -i user
# "User": ""  → root로 실행됨

# 해결 1: runAsUser 지정
securityContext:
  runAsUser: 1000

# 해결 2: non-root 이미지 사용
image: nginx:unprivileged
# 또는
image: bitnami/nginx  # non-root by default

readOnlyRootFilesystem으로 인한 실패

1
2
3
4
5
6
7
8
9
10
11
12
# 에러: Read-only file system
# 해결: 쓰기가 필요한 경로에 emptyDir 마운트
volumeMounts:
- name: tmp
  mountPath: /tmp
- name: cache
  mountPath: /var/cache/nginx
volumes:
- name: tmp
  emptyDir: {}
- name: cache
  emptyDir: {}

기술 면접 대비

자주 묻는 질문

Q: Pod SecurityContext와 Container SecurityContext의 차이는?

A: Pod SecurityContext는 모든 컨테이너에 공통으로 적용되고, Container SecurityContext는 특정 컨테이너에만 적용된다. 같은 설정이 둘 다 있으면 컨테이너 수준 설정이 우선한다. fsGroup 같은 일부 설정은 Pod 수준에서만 가능하고, capabilities 같은 설정은 컨테이너 수준에서만 가능하다.

Q: allowPrivilegeEscalation: false의 효과는?

A: 컨테이너 내 프로세스가 부모 프로세스보다 더 많은 권한을 얻는 것을 방지한다. setuid/setgid 비트가 있는 실행 파일이나 no_new_privs 플래그를 설정한다. 이를 통해 컨테이너 탈출이나 권한 상승 공격을 방어할 수 있다.

Q: Pod Security Standards의 Baseline과 Restricted 차이는?

A: Baseline은 알려진 권한 상승을 막는 최소한의 정책이다. privileged 컨테이너, hostNetwork, hostPID 등을 차단하지만 root 실행은 허용한다. Restricted는 추가로 root 실행 차단, 모든 capabilities drop, seccomp 프로파일 필수 등 더 엄격한 정책을 적용한다. 대부분의 워크로드는 Baseline으로 시작하고, 민감한 환경은 Restricted를 적용한다.

Q: readOnlyRootFilesystem의 보안상 이점은?

A: 공격자가 컨테이너에 침투해도 파일시스템에 악성 파일을 쓸 수 없다. 웹쉘 설치, 악성 스크립트 저장 등을 방지한다. 컨테이너의 불변성(immutability)을 보장하여 보안 베이스라인을 유지할 수 있다.

Q: Linux Capabilities를 drop: ALL 후 add하는 이유는?

A: 최소 권한 원칙을 적용하기 위해서다. 기본적으로 컨테이너는 많은 capabilities를 가지고 있어 불필요한 권한이 포함된다. 모두 제거한 후 꼭 필요한 것만 추가하면 공격 표면을 최소화할 수 있다.

CKA 시험 대비 필수 명령어

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Pod 보안 컨텍스트 확인
kubectl get pod <pod> -o jsonpath='{.spec.securityContext}'
kubectl get pod <pod> -o jsonpath='{.spec.containers[0].securityContext}'

# Namespace PSA 라벨 확인
kubectl get namespace -L pod-security.kubernetes.io/enforce

# Namespace에 PSA 적용
kubectl label namespace <ns> pod-security.kubernetes.io/enforce=restricted
kubectl label namespace <ns> pod-security.kubernetes.io/warn=baseline

# Pod 내부에서 현재 사용자 확인
kubectl exec <pod> -- id
kubectl exec <pod> -- whoami

CKA 빈출 시나리오

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# 시나리오 1: non-root로 실행하는 Pod
apiVersion: v1
kind: Pod
metadata:
  name: nonroot-pod
spec:
  securityContext:
    runAsUser: 1000
    runAsGroup: 3000
  containers:
  - name: app
    image: busybox
    command: ["sleep", "3600"]
    securityContext:
      runAsNonRoot: true

# 시나리오 2: 읽기 전용 파일시스템 + 쓰기 볼륨
apiVersion: v1
kind: Pod
metadata:
  name: readonly-pod
spec:
  containers:
  - name: app
    image: nginx
    securityContext:
      readOnlyRootFilesystem: true
    volumeMounts:
    - name: cache
      mountPath: /var/cache/nginx
    - name: run
      mountPath: /var/run
  volumes:
  - name: cache
    emptyDir: {}
  - name: run
    emptyDir: {}

# 시나리오 3: 최소 capabilities
apiVersion: v1
kind: Pod
metadata:
  name: minimal-cap-pod
spec:
  containers:
  - name: app
    image: nginx
    securityContext:
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE

다음 단계

This post is licensed under CC BY 4.0 by the author.