[kuber-study] Chapter13. Securing cluster nodes and the network

jeonghyun yu·2022년 2월 10일
0

kuber-study

목록 보기
6/7
post-thumbnail

호스트 노드의 namespace

각 pod는 고유한 네트워크 네임스페이스를 사용하기 때문에 각각의 ip와 port를 갖고, 자체 pid 네임스페이스를 가지고 고유한 IPC 네임스페이스도 사용하므로 동일한 파드의 프로세스간 통신 매커니즘(IPC, Inter-Process Communication)으로 서로 통신할 수 있다.

네트워크 네임스페이스 사용

  • host-network.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-host-network
  spec:
    hostNetwork: true       # 노드의 네트워크 인터페이스 사용
    containers:
    - name: main
      image: alpine
      command: ["/bin/sleep", "999999"]

  • 호스트 노드의 네트워크 어댑터가 나오는 것으로 확인
$ kubectl create -f host-network.yaml

$ kubectl exec pod-with-host-network ifconfig
docker0     Link encap:Ethernet HWaddr 02:42:14:08:23:47
            inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
            ...
eth0        Link encap:Ethernet HWaddr 08:00:27:F8:FA:4E
            inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0
            ...
lo          Link encap:Local Loopback
            inet addr:127.0.0.1 Mask:255.0.0.0
            ...
veth1178d4f Link encap:Ethernet HWaddr 1E:03:8D:D6:E1:2C
            inet6 addr: fe80::1c03:8dff:fed6:e12c/64 Scope:Link
            UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
...

호스트의 네트워크 네임스페이스를 사용하지 않고 host port에 바인딩 하기

  • kubia-hostport.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-hostport
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    ports:
    - containerPort: 8080  # pod의 ip의 port
      hostPort: 9000       # node의 port
      protocol: TCP

노드의 PID, IPC namespace 사용

  • host-pid-and-ipc.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-host-pid-and-ipc
spec:
  hostPID: true 
  hostIPC: true 
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
  • hostPID : host node에서 실행 중인 모든 프로세스 확인 가능
$ kubectl create -f host-pid-and-ipc.yaml

$ kubectl exec pod-with-host-pid-and-ipc ps aux
PID USER TIME COMMAND
  1 root 0:01 /usr/lib/systemd/systemd --switched-root --system ...
  2 root 0:00 [kthreadd]
  3 root 0:00 [ksoftirqd/0]
  5 root 0:00 [kworker/0:0H]
  6 root 0:00 [kworker/u2:0]
  7 root 0:00 [migration/0]
  8 root 0:00 [rcu_bh]
  9 root 0:00 [rcu_sched]
 10 root 0:00 [watchdog/0]
...
  • hostIPC : pod의 컨테이너에 있는 프로세스는 IPC(Inter-Process Communication)를 통해 노드에서 실행되는 다른 모든 프로세스들과 통신


security context

  • SECURITY CONTEXT는 무엇을 할 수 있나?

    • 컨테이너에서 프로세스를 실행할 사용자(user ID) 지정
    • 컨테이너가 root로 실행되지 않도록 설정
    • 컨테이너를 privileged mode로 실행 - 노드 커널에 대한 전체 액세스 권한
    • 세분화된 권한을 구성
    • SELinux(Security Enhanced Linux) 옵션을 설정
    • 프로세스가 컨테이너의 파일 시스템에 쓰는 것(write)을 방지
  • SECURITY CONTEXT 사용x

$ kubectl run pod-with-defaults --image alpine --restart Never -- /bin/sleep 999999

$ kubectl exec pod-with-defaults id
uid=0(root) gid=0(root) groups=0(root), 1(bin), 2(daemon), 3(sys), 4(adm), 6(disk), 10(wheel), 11(floppy), 20(dialout), 26(tape), 27(video)

➕ Dockerfile의 USER로도 설정 가능

user ID를 지정한 컨테이너

  • user-guest.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-as-user-guest
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 405  # userID를 405로 지정
  • user ID 출력
$ kubectl create -f user-guest.yaml

$ kubectl exec pod-as-user-guest id
uid=405(guest) gid=100(users)

컨테이너를 root로 실행되지 않게 하기

  • non-root.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-run-as-non-root
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext: 
      runAsNonRoot: true   # root가 아닌 사용자로 컨테이너 실행

privileged mode로 pod 실행

특권 모드의 컨테이너는 모든 호스트 노드의 디바이스를 자유롭게 보고 사용할 수 있다.

  • privileged.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-privileged
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      privileged: true  # privileged mode로 컨테이너 실행

컨테이너에 커널 기능 추가

  • 컨테이너는 시스템 시간 변경 불가
$ kubectl exec -it pod-with-defaults -- date +%T -s "12:00:00"
date: can't set date: Operation not permitted
  • settime-capability.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-add-settime-capability
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext: 
      capabilities: 
        add:          # capability 추가
        - SYS_TIME
  • 시스템 시간 변경
$ kubectl exec -it pod-add-settime-capability -- date +%T -s "12:00:00"
12:00:00

$ kubectl exec -it pod-add-settime-capability -- date
Sun May 7 12:00:03 UTC 2017

➕ Linux man 페이지에서 Linux 커널 목록 확인

컨테이너에 커널 기능 제거

  • chown-capability.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-drop-settime-capability
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext: 
      capabilities: 
        drop:          # capability 제거
        - CHOWN

프로세스가 컨테이너의 파일 시스템에 쓰는 것(write)을 방지

  • readonly-filesystem.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-readonly-filesystem
  spec:
    containers:
    - name: main
      image: alpine
      command: ["/bin/sleep", "999999"]
      securityContext: 
        readOnlyRootFilesystem: true   # write 방지
      volumeMounts: 
      - name: my-volume 
        mountPath: /volume             # 볼륨에는 write 가능
        readOnly: false 
    volumes:
    - name: my-volume
      emptyDir:

➕ pod.spec에서 securityContext속성을 설정하면 pod 수준에서 설정 가능

다른 사용자가 실행하는 컨테이너 사이에서 볼륨 공유

  • shared-volume-fsgroup.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-with-shared-volume-fsgroup
spec:
  securityContext: 
    fsGroup: 555                     # 볼륨 소유 그룹
    supplementalGroups: [666, 777]   # 추가 그룹
  containers:
  - name: first
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext: 
      runAsUser: 1111                # 1번 유저
    volumeMounts: 
    - name: shared-volume 
      mountPath: /volume
      readOnly: false
  - name: second
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext: 
      runAsUser: 2222                # 2번 유저
    volumeMounts: 
    - name: shared-volume 
      mountPath: /volume
      readOnly: false
  volumes:                           # 같은 볼륨을 공유
  - name: shared-volume 
    emptyDir:
  • test
$ kubectl create -f shared-volume-fsgroup.yaml

# 실행 및 id 확인
$ kubectl exec -it pod-with-shared-volume-fsgroup -c first sh
/ $ id
uid=1111 gid=0(root) groups=555,666,777

# 볼륨 소유 그룹 확인
/ $ ls -l / | grep volume
drwxrwsrwx   2 root     555  ...

/ $ echo foo > /volume/foo
/ $ ls -l /volume
total 4
-rw-r--r--   1 1111     555  ...

# 파일시스템에서 파일 소유자는?
/ $ echo foo > /tmp/foo
/ $ ls -l /tmp
total 4
-rw-r--r--   1 1111     root  ...


PodSecurityPolicy

pod에서 사용하거나 못할 보안 관련 기능을 정의하는 클러스터 수준 리소스. API 서버에서 실행되는 PodSecurityPolicy admission control plugin이 PodSecurityPolicy로 pod 정의를 확인.

  • Minikube에서는 PodSecurityPolicy plugin이 비활성화 되어있는 것이 기본

  • PodSecurityPolicy 기능

    • pod가 호스트의 IPC, PID 또는 네트워크 네임스페이스를 사용할 수 있는지 여부
    • pod가 바인딩할 수 있는 호스트 포트
    • 컨테이너를 실행할 수 있는 user ID
    • 권한 있는 컨테이너가 있는 pod를 생성할 수 있는지 여부
    • 기본적으로 추가되고 항상 삭제되는 허용되는 커널 기능
    • 컨테이너에서 사용할 수 있는 SELinux 레이블
    • 컨테이너가 쓰기 가능한 루트 파일 시스템을 사용할 수 있는지 여부
    • 컨테이너를 실행할 수 있는 파일 시스템 그룹
    • pod에서 사용할 수 있는 볼륨 타입
  • 사용

apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: default
spec:
  hostIPC: false               # host의 IPC, PID, network namespace 사용 불가
  hostPID: false 
  hostNetwork: false 
  hostPorts:                   # 바인드 가능한 host port
  - min: 10000
    max: 11000 
  - min: 13000 
    max: 14000 
  privileged: false            # privilieged mode에서 실행 불가
  readOnlyRootFilesystem: true # read-only root filesystem
  runAsUser:                   # 모든 user와 group으로 실행 가능
    rule: RunAsAny 
  fsGroup: 
    rule: RunAsAny 
  supplementalGroups: 
    rule: RunAsAny 
  seLinux:                     # 모든 SELinux 그룹도 사용 가능
    rule: RunAsAny 
  volumes:                     # 모든 볼륨 타입 사용 가능
  - '*' 
# PodSecurityPolicy 생성
kubectl create -f pod-security-policy.yaml

# PodSecurityPolicy 조회
kubectl get psp
  • psp : PdoSecurityPolicy의 약어

runAsUser, fsGroup, and supplementalGroups 정책

  • MUSTRUNAS
runAsUser:
  rule: MustRunAs
  ranges:         # 하나만 허용
  - min: 2 
    max: 2 
fsGroup:
  rule: MustRunAs
  ranges:         # 범위로 허용
  - min: 2 
    max: 10 
  - min: 20 
    max: 30 
supplementalGroups:
  rule: MustRunAs
  ranges:
  - min: 2 
    max: 10 
  - min: 20 
    max: 30
  • 범위를 벗어난 user ID를 가진 컨테이너 이미지 배포
FROM node:7
ADD app.js /app.js
USER 5 
ENTRYPOINT ["node", "app.js"]
# 생성 시 API서버가 거부하지 않음
$ kubectl run run-as-5 --image luksa/kubia-run-as-user-5 --restart Never
pod "run-as-5" created

# ID 확인 (PodSecurityPolicy에 정의된 대로 설정)
$ kubectl exec run-as-5 -- id
uid=2(bin) gid=2(bin) groups=2(bin)
  • MustRunAsNonRoot
    RunAsUser 필드에서 사용하면 root로 실행되는 컨테이너의 배포가 거부된다.

allowedCapabilities, defaultAddCapabilities, requiredDropCapabilities

apiVersion: extensions/v1beta1 
kind: PodSecurityPolicy
spec:
  allowedCapabilities: 
  - SYS_TIME 
  defaultAddCapabilities: 
  - CHOWN 
  requiredDropCapabilities: 
  - SYS_ADMIN 
  - SYS_MODULE 
  ...
  • allowedCapabilities
    securityContext.capabilities 필드에 추가할 수 있는 기능 정의. PodSecurityPolicy admission control plugin이 활성화된 경우에는 적혀있지 않은 기능은 추가할 수 없다.

  • defaultAddCapabilities
    배포되는 모든 pod의 컨테이너에 추가할 기능 정의

  • requiredDropCapabilities
    모든 컨테이너에서 기능 삭제. 기능을 포함한 pod를 생성하면 거부된다.

pod가 사용할 수 있는 볼륨 타입 제한

kind: PodSecurityPolicy
spec:
  volumes:
  - emptyDir
  - configMap
  - secret
  - downwardAPI
  - persistentVolumeClaim

RBAC 메커니즘

PodSecurityPolicy는 클러스터 수준의 리소스라 특정 네임스페이스에만 적용시킬 수 없다. 각각의 user 및 group에 다른 PodSecurityPolicies를 할당하기 위한 방법.

  • privileged 컨테이너를 배포할 수 있는 PodSecurityPolicy 생성
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: privileged     # 정책 이름
spec:
  privileged: true     # privileged container 허용
  runAsUser:
    rule: RunAsAny
  fsGroup:
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  volumes:
  - '*'
# psp 생성
kubectl create -f psp-privileged.yaml

# psp 확인
$ kubectl get psp
NAME        PRIV  CAPS SELINUX   RUNASUSER  FSGROUP  ... 
default     false []   RunAsAny  RunAsAny   RunAsAny ...
privileged  true  []   RunAsAny  RunAsAny   RunAsAny ...
  • RBAC을 사용해서 다른 user에게 다른 podSecurityPolicy 부여
# 기본 클러스터 생성
$ kubectl create clusterrole psp-default --verb=use --resource=podsecuritypolicies --resource-name=default

# 특권모드 클러스터 생성
$ kubectl create clusterrole psp-privileged --verb=use --resource=podsecuritypolicies --resource-name=privileged

# 인증된 사용자에게 default ClusterRole 바인딩
$ kubectl create clusterrolebinding psp-all-users --clusterrole=psp-default --group=system:authenticated

# bob 사용자에게 privileged ClusterRole 바인딩
$ kubectl create clusterrolebinding psp-bob --clusterrole=psp-privileged --user=bob

# 유저 생성
$ kubectl config set-credentials alice --username=alice --password=password
$ kubectl config set-credentials bob --username=bob --password=password


NetworkPolicy

  • 셀렉터와 일치하는 pod의 network 분리
  • ingress/gress 규칙으로 구성

namespace에서의 네트워크 분리

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
spec:
  podSelector:        # 빈 파드 셀렉터는 네임스페이스에 있는 모든 pod에 매칭된다. 

해당 네임스페이스의 pod들에 다른 네임스페이스에서 연결 불가

namespace의 일부 pod만 서버 pod에 연결 허용

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: postgres-netpolicy
spec:
  podSelector:          # 액세스 될 pod
    matchLabels: 
      app: database 
  ingress: 
  - from:               # 여기서 오는 연결만 허용
    - podSelector: 
      matchLabels: 
        app: webserver 
    ports: 
    - port: 5432 

클라이언트 pod가 서비스를 통해서 접근할 때도 적용된다.

namespaces 간의 네트워크 분리

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: shoppingcart-netpolicy
spec:
  podSelector:           # 마이크로 서비스를 위한 label
    matchLabels: 
      app: shopping-cart 
  ingress:
  - from:
    - namespaceSelector: # 해당 테넌트의 pod에서만 접근 가능
      matchLabels: 
        tenant: manning 
    ports:
    - port: 80

CIDR 표기법

ingress:
- from:
  - ipBlock: 
    cidr: 192.168.1.0/24  # 해당 IP 블록에서만 허용

outbound 트래픽 제한

spec:
  podSelector: 
    matchLabels: 
      app: webserver 
  egress:              # outbound 트래픽 제한
  - to: 
    - podSelector: 
      matchLabels: 
        app: database

0개의 댓글