無知

갈 길이 먼 공부 일기

기술 공부/쿠버네티스

쿠버네티스 (13) | 클러스터 노드와 네트워크 보안

moozii 2022. 3. 23. 20:13
앞으로의 스터디 내용은 <Kubernetes in Action>을 기반으로 진행합니다.
자세한 내용은, 해당 책을 확인해주세요! 
http://www.yes24.com/Product/Goods/89607047 

 

 

핵심 내용

  • 파드가 노드의 리소스에 액세스하는 방법
  • 파드에서 사용자가 원하는 작업을 마음대로 수행하지 못하도록 클러스터를 구성하는 방법
  • 파드 통신 시 네트워크를 보호하는 방법

 

https://github.com/lrakai/kubernetes-security

 

 

파드에서 호스트 노드의 네임스페이스 사용

  • 파드의 네임스페이스
    • 파드의 컨테이너는 개별 리눅스 네임스페이스에서 실행되어 다른 노드, 다른 컨테이너와 프로세스가 격리된다.
    • 고유한 네트워크 네임스페이스를 사용하므로 고유 IP와 포트 공간을 얻는다.
    • 고유한 PID 네임스페이스를 사용해, 동일 파드 내 프로세스 간 통신 메커니즘, IPC로 통신한다
  • 파드에서 노드의 네트워크 네임스페이스 쓰임새
    • 시스템 파드 등 특정 파드는 기본 네임스페이스에서 작동해야 노드 리소스 및 장치 조작이 가능하다. 이 경우 파드는 본 파드의 네트워크 인터페이스가 아닌, 노드의 네트워크 인터페이스를 사용한다.(파드 스펙에서 hostNetwork 속성을 true로 설정) 즉, 파드 자체 IP를 사용하지 않고, 포트 바인딩은 노드의 포트에 바인딩됨을 의미한다. 
$ kubectl create -f pod-with-host-network.yaml
pod/pod-with-host-network created

$ cat pod-with-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 exec pod-with-host-network -- ifconfig
docker0   Link encap:Ethernet  HWaddr 02:42:09:36:DD:75
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:855 errors:0 dropped:0 overruns:0 frame:0
          TX packets:969 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:111751 (109.1 KiB)  TX bytes:402427 (392.9 KiB)

eth0      Link encap:Ethernet  HWaddr 08:00:27:02:95:B6
          inet addr:10.0.2.15  Bcast:10.0.2.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:4112 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1519 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:3335868 (3.1 MiB)  TX bytes:264900 (258.6 KiB)

eth1      Link encap:Ethernet  HWaddr 08:00:27:08:13:50
          inet addr:192.168.59.100  Bcast:192.168.59.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:487 errors:0 dropped:0 overruns:0 frame:0
          TX packets:238 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:65208 (63.6 KiB)  TX bytes:203610 (198.8 KiB)

lo        Link encap:Local Loopback
          inet addr:127.0.0.1  Mask:255.0.0.0
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:34356 errors:0 dropped:0 overruns:0 frame:0
          TX packets:34356 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:13481770 (12.8 MiB)  TX bytes:13481770 (12.8 MiB)

veth020c2fb Link encap:Ethernet  HWaddr B2:2D:8A:8F:70:37
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:105 errors:0 dropped:0 overruns:0 frame:0
          TX packets:113 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:28007 (27.3 KiB)  TX bytes:17433 (17.0 KiB)

veth3d95152 Link encap:Ethernet  HWaddr 7E:D1:7F:B8:7A:09
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:176 errors:0 dropped:0 overruns:0 frame:0
          TX packets:189 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:19012 (18.5 KiB)  TX bytes:43823 (42.7 KiB)

veth47f699d Link encap:Ethernet  HWaddr 82:14:11:15:B8:5D
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:336 (336.0 B)

veth8860eb9 Link encap:Ethernet  HWaddr C2:5E:0F:88:10:14
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:84 errors:0 dropped:0 overruns:0 frame:0
          TX packets:109 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:9674 (9.4 KiB)  TX bytes:12112 (11.8 KiB)

vetha153460 Link encap:Ethernet  HWaddr DE:46:53:87:17:5A
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:0 (0.0 B)  TX bytes:336 (336.0 B)

vethf3b33e1 Link encap:Ethernet  HWaddr 6E:62:7F:3E:9C:8E
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:470 errors:0 dropped:0 overruns:0 frame:0
          TX packets:560 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:64358 (62.8 KiB)  TX bytes:322509 (314.9 KiB)

*노드의 네트워크인터페이스를 사용하는 파드를 생성하고, 해당 파드의 네트워크 인터페이스를 확인하는 명령어를 통해, 모든 호스트의 네트워크 어댑터가 다 표시됨을 확인해서, 실제로 호스트 네트워크 네임스페이스 사용을 확인해볼 수 있다. 

 

**쿠버네티스 컨트롤 플레인 구성 요소가 파드로 배포되면, 해당 파드는 호스트네트워크 옵션을 사용해서, 파드 안에서 실행되지 않는 것처럼 작동 가능하다. 

 

 

  • 호스트 네트워크 네임스페이스를 사용하지 않고 호스트 포트에 바인딩
    • 파드가 호스트 네트워크 옵션으로 노드 기본 네임스페이스에 포트 바인딩도 가능하지만, 여전히 고유한 네트워크 네임스페이스도 가진다. 즉, 컨테이너 포트 정의 필드 (containers.ports 필드) 내 hostPort 속성을 사용해 파드 고유 네트워크 네임스페이스를 가질 수 있다. 
    • 호스트 포트 사용 파드와 노드포트 서비스 노출 파드의 차이
      • 파드가 호스트 포트를 사용하는 경우 : 노드 포트 연결은 해당 노드에서 실행 중인 파드로 직접 전달. 노드포트는 해당 파드를 실행하는 노드에만 바인딩.   >> 프로세스와 호스트 포트(노드)의 1:1 대응 방식의 바인딩이 이뤄지므로 파드 인스턴스 1개만 노드에 스케줄링된다. (스케줄러가 여러 파드를 한 노드에 스케줄링하지 않음. 노드 당 1개의 레플리카만 가능.)
      • 파드가 노드 포트 서비스로 노출된 경우 : 노드 포트의 연결이 임의 파드로 전달 (타 노드 내 파드도 가능). 모든 노드의 포트를 바인딩. 
    • 호스트 포트 기능은 기본적으로 데몬셋을 사용해 모든 노드에 배포되는 시스템 서비스를 노출한다. 
$ cat kubia-hostport.yaml
apiVersion: v1
kind: Pod
metadata:
  name: kubia-hostport
spec:
  containers:
  - image: luksa/kubia
    name: kubia
    ports:
    - containerPort: 8080
      hostPort: 9000
      protocol: TCP

*컨테이너 파드 IP의 포트 8080으로도, 노드포트 9000으로도 연결 가능.

 

  • 노드의 PID와 IPC 네임스페이스 사용
    • hostNetwork와 유사한 파드 스펙 속성
      • hostPID=true : 파드의 컨테이너가 노드의 PID 네임스페이스를 사용. 실행 중인 프로세스가 노드 내 다른 프로세스 확인.
      • hostIPC=true : 파드의 컨테이너가 노드의 IPC 네임스페이스를 사용. 실행 중인 프로세스가 IPC로 다른 프로세스와 통신.
$ cat pod-with-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"]
$ kubectl exec pod-with-host-pid-and-ipc -- ps aux
PID   USER     TIME  COMMAND
    1 root      0:06 {systemd} /sbin/init noembed norestore
    2 root      0:00 [kthreadd]
    3 root      0:00 [rcu_gp]
    4 root      0:00 [rcu_par_gp]
    6 root      0:00 [kworker/0:0H-kb]
    8 root      0:00 [mm_percpu_wq]
    9 root      0:00 [ksoftirqd/0]
   10 root      0:01 [rcu_sched]
   11 root      0:00 [rcu_bh]
   12 root      0:00 [migration/0]
   13 root      0:00 [cpuhp/0]
   15 root      0:00 [cpuhp/1]
   16 root      0:00 [migration/1]
   17 root      0:00 [ksoftirqd/1]
   19 root      0:00 [kworker/1:0H-kb]
   20 root      0:00 [kdevtmpfs]
   21 root      0:00 [netns]
   25 root      0:00 [kauditd]
  470 root      0:00 [oom_reaper]
  471 root      0:00 [writeback]
  473 root      0:00 [kcompactd0]
  474 root      0:00 [khugepaged]
  475 root      0:00 [crypto]
  477 root      0:00 [kblockd]
  537 root      0:00 [ata_sff]
  557 root      0:00 [md]
  559 root      0:00 [edac-poller]
  661 root      0:00 [rpciod]
  662 root      0:00 [kworker/u5:0-xp]
  663 root      0:00 [xprtiod]
  666 root      0:00 [cfg80211]
  695 root      0:00 [kswapd0]
  806 root      0:00 [nfsiod]
  818 root      0:00 [cifsiod]
  819 root      0:00 [cifsoplockd]
  836 root      0:00 [xfsalloc]
  837 root      0:00 [xfs_mru_cache]
  889 root      0:00 [acpi_thermal_pm]
 1042 root      0:00 [scsi_eh_0]
 1044 root      0:00 [scsi_tmf_0]
 1048 root      0:00 [scsi_eh_1]
 1049 root      0:00 [scsi_tmf_1]
 1050 root      0:00 [scsi_eh_2]
 1053 root      0:00 [scsi_tmf_2]
 1054 root      0:00 [scsi_eh_3]
 1057 root      0:00 [scsi_tmf_3]
 1058 root      0:00 [scsi_eh_4]
 1059 root      0:00 [scsi_tmf_4]
 1060 root      0:00 [scsi_eh_5]
 1061 root      0:00 [scsi_tmf_5]
 1062 root      0:00 [scsi_eh_6]
 1065 root      0:00 [scsi_tmf_6]
 1070 root      0:00 [scsi_eh_7]
 1073 root      0:00 [scsi_tmf_7]
 1075 root      0:00 [scsi_eh_8]
 1076 root      0:00 [scsi_tmf_8]
 1079 root      0:00 [scsi_eh_9]
 1080 root      0:00 [scsi_tmf_9]
 1084 root      0:00 [scsi_eh_10]
 1085 root      0:00 [scsi_tmf_10]
 1087 root      0:00 [scsi_eh_11]
 1088 root      0:00 [scsi_tmf_11]
 1090 root      0:00 [scsi_eh_12]
 1093 root      0:00 [scsi_tmf_12]
 1096 root      0:00 [scsi_eh_13]
 1098 root      0:00 [scsi_tmf_13]
 1100 root      0:00 [scsi_eh_14]
 1102 root      0:00 [scsi_tmf_14]
 1105 root      0:00 [scsi_eh_15]
 1106 root      0:00 [scsi_tmf_15]
 1108 root      0:00 [scsi_eh_16]
 1109 root      0:00 [scsi_tmf_16]
 1112 root      0:00 [scsi_eh_17]
 1113 root      0:00 [scsi_tmf_17]
 1116 root      0:00 [scsi_eh_18]
 1117 root      0:00 [scsi_tmf_18]
 1120 root      0:00 [scsi_eh_19]
 1122 root      0:00 [scsi_tmf_19]
 1124 root      0:00 [scsi_eh_20]
 1126 root      0:00 [scsi_tmf_20]
 1128 root      0:00 [scsi_eh_21]
 1130 root      0:00 [scsi_tmf_21]
 1132 root      0:00 [scsi_eh_22]
 1134 root      0:00 [scsi_tmf_22]
 1135 root      0:00 [scsi_eh_23]
 1136 root      0:00 [scsi_tmf_23]
 1140 root      0:00 [scsi_eh_24]
 1142 root      0:00 [scsi_tmf_24]
 1144 root      0:00 [scsi_eh_25]
 1146 root      0:00 [scsi_tmf_25]
 1148 root      0:00 [scsi_eh_26]
 1150 root      0:00 [scsi_tmf_26]
 1152 root      0:00 [scsi_eh_27]
 1153 root      0:00 [scsi_tmf_27]
 1156 root      0:00 [scsi_eh_28]
 1157 root      0:00 [scsi_tmf_28]
 1159 root      0:00 [scsi_eh_29]
 1162 root      0:00 [scsi_tmf_29]
 1190 root      0:00 [kworker/u4:28-f]
 1262 root      0:00 [dm_bufio_cache]
 1302 root      0:00 [ipv6_addrconf]
 1308 root      0:00 [ceph-msgr]
 1342 root      0:00 [kworker/1:2-eve]
 1357 root      0:00 [kworker/1:1H-kb]
 1361 root      0:00 [kworker/0:1H-kb]
 1392 root      0:00 /usr/lib/systemd/systemd-journald
 1666 root      0:00 /usr/lib/systemd/systemd-udevd
 2069 1003      0:00 /usr/lib/systemd/systemd-networkd
 2089 root      0:00 [iprt-VBoxWQueue]
 2127 root      0:00 /usr/sbin/acpid --foreground --netlink
 2128 1001      0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation --syslog-only
 2130 root      0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
 2135 root      0:00 /usr/lib/systemd/systemd-logind
 2153 root      0:00 /usr/sbin/VBoxService -f --disable-automount --timesync-set-start --timesync-set-threshold 5000
 2168 1004      0:00 /usr/lib/systemd/systemd-resolved
 2171 root      0:00 [jbd2/sda1-8]
 2172 root      0:00 [ext4-rsv-conver]
 2247 root      0:00 /usr/sbin/rpc.statd
 2249 root      0:00 /usr/sbin/rpcbind
 2251 root      0:00 /usr/sbin/rpc.mountd
 2258 root      0:00 [kworker/u5:2]
 2259 root      0:00 [lockd]
 2262 root      0:00 [nfsd]
 2263 root      0:00 [nfsd]
 2264 root      0:00 [nfsd]
 2265 root      0:00 [nfsd]
 2266 root      0:00 [nfsd]
 2267 root      0:00 [nfsd]
 2268 root      0:00 [nfsd]
 2269 root      0:00 [nfsd]
 2346 root      0:00 sshd: /usr/sbin/sshd -D -e [listener] 0 of 10-100 startups
 2469 root      0:32 /usr/bin/dockerd -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock --default-ulimit=nofile=1048576:1048576 --tlsverify --tlscacert /etc/docker/ca.pem --tlscert /etc/docker/server.pem --tlskey /etc/docker/server-key.pem --label provider=virtualbox --insecure-registry 10.96.0.0/12
 2475 root      0:03 containerd --config /var/run/docker/containerd/containerd.toml --log-level info
 3868 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 4b574cb01aa2a75a87706d81617a01e27c21e538bbc2c3cd910c1de907c95c94 -address /var/run/docker/containerd/containerd.sock
 3880 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 2e1cded3fa06aa56722bdb4ff9025c46236dca5bdcaeedb0690a813cdfc37f4a -address /var/run/docker/containerd/containerd.sock
 3895 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id cf42f4d166c167ed7f0e57ddc7cb805ae4bb442b639c7483912c907bb42e38a1 -address /var/run/docker/containerd/containerd.sock
 3917 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id b70f80e43a5f4778e648d0c7cb09262b7f496b2818507dde88d82f35cbacdf04 -address /var/run/docker/containerd/containerd.sock
 3951 65535     0:00 /pause
 3963 65535     0:00 /pause
 3978 65535     0:00 /pause
 3996 65535     0:00 /pause
 4038 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id a542b87f460b79e10115bb08e26f5de2b6085c28e44eef6fe78268690927effc -address /var/run/docker/containerd/containerd.sock
 4067 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id f9ecbaf31e9aea17dd8acd22be7b7cdb74dd1d102a5475a62ab51414bed778e1 -address /var/run/docker/containerd/containerd.sock
 4070 root      2:15 kube-apiserver --advertise-address=192.168.59.100 --allow-privileged=true --authorization-mode=Node,RBAC --client-ca-file=/var/lib/minikube/certs/ca.crt --enable-admission-plugins=NamespaceLifecycle,LimitRanger,ServiceAccount,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota --enable-bootstrap-token-auth=true --etcd-cafile=/var/lib/minikube/certs/etcd/ca.crt --etcd-certfile=/var/lib/minikube/certs/apiserver-etcd-client.crt --etcd-keyfile=/var/lib/minikube/certs/apiserver-etcd-client.key --etcd-servers=https://127.0.0.1:2379 --kubelet-client-certificate=/var/lib/minikube/certs/apiserver-kubelet-client.crt --kubelet-client-key=/var/lib/minikube/certs/apiserver-kubelet-client.key --kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname --proxy-client-cert-file=/var/lib/minikube/certs/front-proxy-client.crt --proxy-client-key-file=/var/lib/minikube/certs/front-proxy-client.key --requestheader-allowed-names=front-proxy-client --requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt --requestheader-extra-headers-prefix=X-Remote-Extra- --requestheader-group-headers=X-Remote-Group --requestheader-username-headers=X-Remote-User --secure-port=8443 --service-account-issuer=https://kubernetes.default.svc.cluster.local --service-account-key-file=/var/lib/minikube/certs/sa.pub --service-account-signing-key-file=/var/lib/minikube/certs/sa.key --service-cluster-ip-range=10.96.0.0/12 --tls-cert-file=/var/lib/minikube/certs/apiserver.crt --tls-private-key-file=/var/lib/minikube/certs/apiserver.key
 4108 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id eb74435109fcc460a2b5ef0a91309b582c9477d0878fa3e4384d5c6d9cc9fa7c -address /var/run/docker/containerd/containerd.sock
 4112 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 2709ec5e1cda28d5658449be42cad76a296743b9174d09c69a77153d17c78e60 -address /var/run/docker/containerd/containerd.sock
 4127 root      0:48 etcd --advertise-client-urls=https://192.168.59.100:2379 --cert-file=/var/lib/minikube/certs/etcd/server.crt --client-cert-auth=true --data-dir=/var/lib/minikube/etcd --initial-advertise-peer-urls=https://192.168.59.100:2380 --initial-cluster=minikube=https://192.168.59.100:2380 --key-file=/var/lib/minikube/certs/etcd/server.key --listen-client-urls=https://127.0.0.1:2379,https://192.168.59.100:2379 --listen-metrics-urls=http://127.0.0.1:2381 --listen-peer-urls=https://192.168.59.100:2380 --name=minikube --peer-cert-file=/var/lib/minikube/certs/etcd/peer.crt --peer-client-cert-auth=true --peer-key-file=/var/lib/minikube/certs/etcd/peer.key --peer-trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt --proxy-refresh-interval=70000 --snapshot-count=10000 --trusted-ca-file=/var/lib/minikube/certs/etcd/ca.crt
 4162 root      0:59 kube-controller-manager --allocate-node-cidrs=true --authentication-kubeconfig=/etc/kubernetes/controller-manager.conf --authorization-kubeconfig=/etc/kubernetes/controller-manager.conf --bind-address=127.0.0.1 --client-ca-file=/var/lib/minikube/certs/ca.crt --cluster-cidr=10.244.0.0/16 --cluster-name=mk --cluster-signing-cert-file=/var/lib/minikube/certs/ca.crt --cluster-signing-key-file=/var/lib/minikube/certs/ca.key --controllers=*,bootstrapsigner,tokencleaner --kubeconfig=/etc/kubernetes/controller-manager.conf --leader-elect=false --requestheader-client-ca-file=/var/lib/minikube/certs/front-proxy-ca.crt --root-ca-file=/var/lib/minikube/certs/ca.crt --service-account-private-key-file=/var/lib/minikube/certs/sa.key --service-cluster-ip-range=10.96.0.0/12 --use-service-account-credentials=true
 4169 root      0:09 kube-scheduler --authentication-kubeconfig=/etc/kubernetes/scheduler.conf --authorization-kubeconfig=/etc/kubernetes/scheduler.conf --bind-address=127.0.0.1 --kubeconfig=/etc/kubernetes/scheduler.conf --leader-elect=false
 4371 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 126fc4ae70bb741b1b8e158293c63838252db8faeadf780ee1d4f5d61f6041b7 -address /var/run/docker/containerd/containerd.sock
 4393 65535     0:00 /pause
 4481 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 8401a09e9b3cef49c5c53909a99b6e817c781d4a0c7b407afe5226fbcfc94ab7 -address /var/run/docker/containerd/containerd.sock
 4568 65535     0:00 /pause
 4576 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 81f682f69469538ca6d5690c0195bc111c3a3ed7e6279ca6e79dc08e2b65cfaf -address /var/run/docker/containerd/containerd.sock
 4610 root      0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 443 -container-ip 172.17.0.4 -container-port 443
 4616 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 3cc97c799501ad0abddc566e44cab8747d8d32529ab59b77bd14a86e0f5ddfe2 -address /var/run/docker/containerd/containerd.sock
 4663 65535     0:00 /pause
 4670 root      0:00 /usr/bin/docker-proxy -proto tcp -host-ip 0.0.0.0 -host-port 80 -container-ip 172.17.0.4 -container-port 80
 4703 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 0b1442bc804cce70edfd699abf702c7d51fdb44fa061fe0764535ca78f7819a8 -address /var/run/docker/containerd/containerd.sock
 4726 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id f10cc4cbcc7873c10fcacb8799ac44551902795959ea72013d36c14d5b59e8c6 -address /var/run/docker/containerd/containerd.sock
 4735 65535     0:00 /pause
 4791 65535     0:00 /pause
 4820 65535     0:00 /pause
 4884 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id c658e301656fe3e40cd72cb254fa41cd7976bca81f8f46b69a3050cd417d3f0f -address /var/run/docker/containerd/containerd.sock
 4899 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 6453b858c13068a584f900d9b699a02af99d9242d3cedf4df74fffc7592388d8 -address /var/run/docker/containerd/containerd.sock
 4938 65535     0:00 /pause
 4943 65535     0:00 /pause
 4963 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 5de0d73706c172a301eed53c537f573b72907bdfaee131acdce2b7c47b8d152d -address /var/run/docker/containerd/containerd.sock
 4969 root      1:15 /var/lib/minikube/binaries/v1.23.1/kubelet --bootstrap-kubeconfig=/etc/kubernetes/bootstrap-kubelet.conf --config=/var/lib/kubelet/config.yaml --container-runtime=docker --hostname-override=minikube --housekeeping-interval=5m --kubeconfig=/etc/kubernetes/kubelet.conf --node-ip=192.168.59.100
 5031 root      0:04 /coredns -conf /etc/coredns/Corefile
 5402 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 21b313713ce7dde1d40458b479952b7c1ef5906e890f855ff2edc98607d2634e -address /var/run/docker/containerd/containerd.sock
 5424 1001      0:00 /metrics-sidecar
 5444 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 588d49c0c698426cf423397efe8d43416aecc6e78c80194d852e948b2644fe4a -address /var/run/docker/containerd/containerd.sock
 5468 root      0:00 /usr/local/bin/kube-proxy --config=/var/lib/kube-proxy/config.conf --hostname-override=minikube
 5500 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id f199ae4f873907389794abbc87ec714a2b8eda6ed6b817c9e2682bd0bc5b65a5 -address /var/run/docker/containerd/containerd.sock
 5519 101       0:00 /usr/bin/dumb-init -- /nginx-ingress-controller --election-id=ingress-controller-leader --controller-class=k8s.io/ingress-nginx --watch-ingress-without-class=true --publish-status-address=localhost --configmap=ingress-nginx/ingress-nginx-controller --report-node-internal-ip-address --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --validating-webhook=:8443 --validating-webhook-certificate=/usr/local/certificates/cert --validating-webhook-key=/usr/local/certificates/key
 5545 101       0:03 /nginx-ingress-controller --election-id=ingress-controller-leader --controller-class=k8s.io/ingress-nginx --watch-ingress-without-class=true --publish-status-address=localhost --configmap=ingress-nginx/ingress-nginx-controller --report-node-internal-ip-address --tcp-services-configmap=ingress-nginx/tcp-services --udp-services-configmap=ingress-nginx/udp-services --validating-webhook=:8443 --validating-webhook-certificate=/usr/local/certificates/cert --validating-webhook-key=/usr/local/certificates/key
 5629 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 912a7f66ff6e2acd679f8ed17f76ffc0535bdbf0072c76247270aa31ab167a9f -address /var/run/docker/containerd/containerd.sock
 5650 root      0:00 /bin/sh -c /kubectl-proxy.sh
 5670 root      0:00 {kubectl-proxy.s} /bin/sh /kubectl-proxy.sh
 5672 root      0:00 /kubectl proxy --server=https://10.96.0.1:443 --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt --token=eyJhbGciOiJSUzI1NiIsImtpZCI6Il9nMFVJUjlpMWJBYk9fOGt1dG5PYmZLUEFMaGRnUEo0eFZ0VTBpYlZrbW8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjc5MTAyOTc1LCJpYXQiOjE2NDc1NjY5NzUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJiYXIiLCJwb2QiOnsibmFtZSI6InRlc3QiLCJ1aWQiOiIzNjMzNjJkNS04YTI2LTRhODUtODRjMy1jZDJlYjM0NTkyODgifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiI3MDZkZTM0Yy1jOTY2LTRlNTYtYTUzMy0zMWEyYjAxOGMzMGIifSwid2FybmFmdGVyIjoxNjQ3NTcwNTgyfSwibmJmIjoxNjQ3NTY2OTc1LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6YmFyOmRlZmF1bHQifQ.s9Bp2qRHW0i821PXashcUOlckkE73tYDxxr0DiPvG0RwxaFrpr9hEqKe5ja4WHH0C6lKYUyf1XL0KmzA0C_xrJr2lIfwy6_vX13ocay4Il2vE_UHhD-eIphz2YMtT07uN2Nf2B7XywNpMG5z-p9y0WwYljXiETA7N-W4aXKJUnz8U_0LQfQrK7LGiWBoD0Z195aPmtcVUBnD4qh3tQZz2BwQnV_HW0egFnPr-krfS6fCTUGoaLYf173Sq_ugPWdA2rWvKBuGlshuCJhrpB43hPtreJkmIDhi0_ODIIQOCBHSDvB5mHs5f-iF0drCrj0yw4VjtqktUjoIKQRaSO129A --accept-paths=^.*
 5679 101       0:00 nginx: master process /usr/local/nginx/sbin/nginx -c /etc/nginx/nginx.conf
 5686 101       0:00 nginx: worker process
 5687 101       0:00 nginx: worker process
 5688 101       0:00 nginx: cache manager process
 5808 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id dd8085bba2c3120dbdb206d1a54cb323c64cec960d2036f79bf62c7d486ae16c -address /var/run/docker/containerd/containerd.sock
 5828 root      0:00 /bin/sh -c /kubectl-proxy.sh
 5844 root      0:00 {kubectl-proxy.s} /bin/sh /kubectl-proxy.sh
 5846 root      0:00 /kubectl proxy --server=https://10.96.0.1:443 --certificate-authority=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt --token=eyJhbGciOiJSUzI1NiIsImtpZCI6Il9nMFVJUjlpMWJBYk9fOGt1dG5PYmZLUEFMaGRnUEo0eFZ0VTBpYlZrbW8ifQ.eyJhdWQiOlsiaHR0cHM6Ly9rdWJlcm5ldGVzLmRlZmF1bHQuc3ZjLmNsdXN0ZXIubG9jYWwiXSwiZXhwIjoxNjc5MTAyOTc1LCJpYXQiOjE2NDc1NjY5NzUsImlzcyI6Imh0dHBzOi8va3ViZXJuZXRlcy5kZWZhdWx0LnN2Yy5jbHVzdGVyLmxvY2FsIiwia3ViZXJuZXRlcy5pbyI6eyJuYW1lc3BhY2UiOiJmb28iLCJwb2QiOnsibmFtZSI6InRlc3QiLCJ1aWQiOiIyMjUzMjgxNC0yNGIxLTRmZmQtYjUxZC1kZmQ1ZDFhYmI4NTUifSwic2VydmljZWFjY291bnQiOnsibmFtZSI6ImRlZmF1bHQiLCJ1aWQiOiIzZjY0NWFmMi1mZTFkLTQ3NDYtOTVlMy02MWE4NzQwMzJlY2UifSwid2FybmFmdGVyIjoxNjQ3NTcwNTgyfSwibmJmIjoxNjQ3NTY2OTc1LCJzdWIiOiJzeXN0ZW06c2VydmljZWFjY291bnQ6Zm9vOmRlZmF1bHQifQ.HfWyTHSTA8Nu9oKS-byShnVZSlpWn9rSJLYc2az1TojkE4uDEH48M2uTlJP9XyHCExij1VAX3eC279dQIt8tVZrt48xAkiq3W53SBDNX6sA9nB3DkxVWtXau9pMzB-m3MCKxu7n6U16MLvq8QXuqpNl0kDImXs1ILoxXtB5g9bUjhLcliKHHgeCiFV4ZWz5baz8w9Q1yptta2jMPy8Gk7idNW20OlvS7dPgald81D_Sl5M7YhEpFidrHv6PH5TvJc_6J1pnpnYEONjYGwztcvVDgHfiGoQJdHLFh-f_o5O9P3I8dd-LBCSmd9-MMUbHU8Fb2ZAXIfLvIEidwHAT_sw --accept-paths=^.*
 6144 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id fc81694ccd208cf583538873b10d0214027acfb4d1ab7049382041c9f646c9ec -address /var/run/docker/containerd/containerd.sock
 6166 root      0:04 /storage-provisioner
 6245 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id beb6f4f7d5aa2d99575ca95bb5619778d481dcfb1e3146c6d39f4ac9078ddea5 -address /var/run/docker/containerd/containerd.sock
 6281 1001      0:01 /dashboard --insecure-bind-address=0.0.0.0 --bind-address=0.0.0.0 --namespace=kubernetes-dashboard --enable-skip-login --disable-settings-authorizer
 6618 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id f855735322c8fbb68c79b3f8306f5c70325eb931de9a2430fd5855758e77e490 -address /var/run/docker/containerd/containerd.sock
 6640 65535     0:00 /pause
 6681 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 43215c43e9dc6bd60acf7e7be82b9bc76512891a93aee603df9f198cef076230 -address /var/run/docker/containerd/containerd.sock
 6701 root      0:00 /bin/sleep 999999
 7160 root      0:00 [kworker/0:0-mm_]
 8257 root      0:00 [kworker/1:3-cgr]
10651 root      0:00 [kworker/0:1-eve]
11273 root      0:00 [kworker/u4:0-fl]
11347 root      0:00 [kworker/0:2-eve]
11945 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id 0f4640e21a86e1bb884c78425fb1d80496af6122f15d1adcb23455765159c371 -address /var/run/docker/containerd/containerd.sock
11968 65535     0:00 /pause
12022 root      0:00 /usr/bin/containerd-shim-runc-v2 -namespace moby -id e8853d3caed945005ac56e91eb460e9f6026a41402852ee7f7144c1ee9a6313d -address /var/run/docker/containerd/containerd.sock
12042 root      0:00 /bin/sleep 999999
12090 root      0:00 ps aux

**해당 파드는 상기 옵션을 켜두어, 컨테이너 프로세스를 조회하면, 컨테이너에서 실행 중인 프로세스 외로도 호스트 노드에서 실행 중인 모든 프로세스가 조회됨. 

 

 

컨테이너의 보안 컨텍스트 구성

securityContext 속성은, 파드 컨텍스트 아래의 개별 컨테이너 스펙에서 직접 지정 가능한 스펙이다. 

다른 보안 관련 기능들을 파드와 파드의 컨테이너에 구성하는 데에 사용하는 속성이다.

 

https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

 

Configure a Security Context for a Pod or Container

A security context defines privilege and access control settings for a Pod or Container. Security context settings include, but are not limited to: Discretionary Access Control: Permission to access an object, like a file, is based on user ID (UID) and gro

kubernetes.io

 

  • 보안 컨텍스트 설정 가능 사항
    • 컨테이너의 프로세스 실행 사용자(user ID) 지정
    • 컨테이너가 루트(root user. 전권 보유)로 실행 되는 것을 방지
    • 컨테이너가 특권 모드(privileged mode)에서 실행돼 노드 커널 접근 권한 전체를 가짐
    • 특권 모드에서 컨테이너를 실행해 컨테이너에 가능한 모든 권한 부여하지 않고, 기능 추가 및 삭제 통해 세분화된 권한 구성
    • 컨테이너 권한 확인의 강화를 위한 SELinux, Security Enhanced Linux 옵션 설정
    • 프로세스가 컨테이너의 파일 시스템에 작성하는 일을 방지
  • 보안 컨텍스트 실습 관련 노트 Part 1
    • 기본 보안 컨텍스트
      • 사용 방법 : 아무것도 지정하지 않은 채 파드를 실행.
      • 사용 결과 : kubectl exec pod_name id 실행 시 컨테이너가 사용자 ID 0, 그룹 ID 0인 루트 사용자로 실행됨 확인
        • 컨테이너 이미지에 지정한 사용자로 컨테이너는 실행되며, 도커파일에서 USER 지시문으로 지정한다. 만약 생략되어 있을 경우 루트 사용자로 실행된다.
    • 컨테이너를 컨테이너 이미지 설정과 다른 특정 사용자로 실행
      • 사용 방법 : 파드의 securityContext.runAsUser 속성을 설정. (ex. runAsUser: 405) (405는 게스트 사용자)
      • 사용 결과 : 컨테이너가 해당 id 사용자로 실행 (예시 : 게스트 405)
    • 컨테이너가 루트 사용자로 실행되는 것 방지하기
      • 예상 가능한 공격 상황 :
        • 도커 파일에서 USER 지시문으로 빌드된 이미지로 파드를 배포해 데몬 사용자로 실행
        • 공격자가 이미지 레지스트리에 접근해 동일 태그에 다른 이미지(루트 사용자 실행하는 이미지)를 푸시
        • 쿠버네티스가 새로운 파드 인스턴스를 스케줄링
        • kubelet이 공격자의 이미지를 다운로드 해 공격자의 코드를 실행
      • 프로세스를 루트로 실행하는 것이 안좋은 이유 : 
        • 예시 : 호스트 디렉터리가 컨테이너에 마운트될 경우 컨테이너 내 실행 중인 프로세스가 루트로 실행 중이라면, 마운트된 디렉토리에 모든 액세스 권한을 갖게 됨
      • 방지 방법 및 결과 : 
        • 방법 : securityContext.runAsNotRoot: true로 파드 스펙을 설정
        • 결과 : 누군가 컨테이너 이미지를 무단 변경해도 실행하지 않음
          "Error: container has runAsNonRoot and image will run as root"
$ cat pod-run-as-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

$ kubectl create -f pod-run-as-non-root.yaml
pod/pod-run-as-non-root created

$ kubectl get po pod-run-as-non-root
NAME                  READY   STATUS                       RESTARTS   AGE
pod-run-as-non-root   0/1     CreateContainerConfigError   0          12s

$ kubectl describe po pod-run-as-non-root
(중략)
Events:
  Type     Reason     Age               From               Message
  ----     ------     ----              ----               -------
  Normal   Scheduled  37s               default-scheduler  Successfully assigned default/pod-run-as-non-root to minikube
  Normal   Pulled     34s               kubelet            Successfully pulled image "alpine" in 2.522602857s
  Normal   Pulled     31s               kubelet            Successfully pulled image "alpine" in 2.468851488s
  Normal   Pulled     17s               kubelet            Successfully pulled image "alpine" in 2.503152373s
  Normal   Pulling    4s (x4 over 36s)  kubelet            Pulling image "alpine"
  Warning  Failed     1s (x4 over 34s)  kubelet            Error: container has runAsNonRoot and image will run as root (pod: "pod-run-as-non-root_default(5f3d5183-e460-42b5-b51d-8a3498f04623)", container: main)
  Normal   Pulled     1s                kubelet            Successfully pulled image "alpine" in 2.438172749s

**참고 : https://stackoverflow.com/questions/49720308/kubernetes-podsecuritypolicy-set-to-runasnonroot-container-has-runasnonroot-and

 

  • 보안 컨텍스트 실습 관련 노트 Part 2
    • 특권 모드로 파드 실행
      • 목적 : 일반 컨테이너에서 접근 불가한 시스템 장치 및 커널 기능을 사용하는 것과 같이 노드 커널의 모든 액세스 권한을 얻기 위해 활용 (예시 : kube-proxy 파드)
      • 방법 : securityContext.privileged: true
      • 결과 : 시스템의 모든 장치에 관한 장치 파일이 든 /dev라는 특별 파일 디렉터리를 조회하면 전체 장치 목록이 조회됨. 모든 호스트 노드의 장치를 자유롭게 보고 사용할 수 있음.
    • 컨테이너에 개별 커널 기능 추가
      • 목적 : 보안 상 컨테이너 권한을 미세 조정하고 공격자의 잠재적인 침입 영향 제한을 목적으로 실제 필요한 커널 기능만 액세스하도록 일부만 권한을 부여하기 위해
      • 방법 : securityContext.capabilities.add: - <capability_name> 
        (capability_name : 리눅스 커널 기능은 일반적으로 CAP_ 접두어로 시작하지만, 파드 스펙에서 지정하는 경우 접두어를 생략한다. 리눅스 man 페이지에서 리눅스 커널 기능 목록을 확인할 수 있다. )
        • 예를 들어 하드웨어 시계 시간인 시스템 시간을 변경하기 위해 그 권한을 컨테이너 기능 목록에 추가
          securityContext:
              capabilities:
                  add:
                  - SYS_TIME
        • 확인 방법: kubectl exec -it <pod_name> -- date +%T -s "12:00:00" 
    • 컨테이너에서 커널 기능 제거
      • 목적 : 컨테이너에서 사용할 수 있는 기능을 제거해 수행을 막기 위해
      • 방법 : securityContext.capabilities.drop 속성 아래에 기능을 추가하면 해당 기능 사용 불가
    • 프로세스가 컨테이너 파일시스템에 쓰는 것 방지
      • 예상 위험 상황 :
        • 공격자가 파일 시스템에 쓸 수 있도록 취약점이 있는 PHP 애플리케이션 실행
        • PHP 파일은 빌드 시 컨테이너 이미지에 추가되고, 컨테이너 파일 시스템에서 제공
        • 취약점을 통해 공격자가 파일을 수정해 악성코드 삽입 가능
      • 목적 : 보안 상 컨테이너에서 실행 중인 프로세스가 컨테이너의 파일 시스템에 쓰지 못하게 하고 마운트된 볼륨에만 쓰도록. 컨테이너 파일 시스템에 일반적으로 애플리케이션의 실행 코드가 저장되어 있기 때문. 프로덕션 환경에서 보안을 강화하기 위해서 자주 사용.
      • 방법 : securityContext.readOnlyRootFilesystem: true
    • 파드 수준의 보안 컨텍스트 옵션 설정
      • 앞선 예제들에서는 개별 컨테이너의 보안 컨텍스트를 설정했지만, 일부의 경우 pod.spec.securityContext 속성으로 파드 수준에서 설정할 수 있다. 이는 모든 파드의 컨테이너에 대한 기본값으로 사용된다. (컨테이너 수준에서 재정의 가능)
    • 컨테이너가 다른 사용자로 실행될 때 볼륨 공유
      • 과거 용례 : 파드의 컨테이너 간 데이터를 공유하는 기존 방법 = 두 컨테이너 모두 루트로 실행돼 볼륨 전체 액세스 권한 부여
      • 목적 : 두개의 컨테이너를 두명의 다른 사용자로 실행하는 경우, 두 컨테이너가 볼륨으로 파일을 공유할 때에 서로의 파일을 읽거나 쓸 수 있도록
      • 방법 : 모든 파드에 supplementalGroups 속성을 지정해, 실행 중인 사용자 ID와 무관히 파일 공유
        • pod.spec.securityContext.fsgroup: 555 )
          프로세스가 볼륨에 파일을 생성할 때 사용됨. 즉, 마운트된 볼륨을 그룹 ID 555가 소유
        • pod.spec.securityContext.supplementalGroups: [666, 777] )
          사용자와 관련된 추가 그룹 ID 목록을 지정
          즉, 앞서 정한 fsgroup과 함께 사용자와 연관된 그룹으로 555,666,777을 지정. 
$ cat pod-drop-chown-capability.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-drop-chown-capability
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      capabilities:
        drop:
        - CHOWN
        
$ kubectl create -f pod-drop-chown-capability.yaml
pod/pod-drop-chown-capability created

$ kubectl exec pod-drop-chown-capability -- chown guest /tmp
chown: /tmp: Operation not permitted
command terminated with exit code 1
$ cat pod-with-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
    volumeMounts:
    - name: shared-volume
      mountPath: /volume
      readOnly: false
  - name: second
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      runAsUser: 2222
    volumeMounts:
    - name: shared-volume
      mountPath: /volume
      readOnly: false
  volumes:
  - name: shared-volume
    emptyDir:
    
$ 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           4096 Mar 18 05:48 volume

/ $ echo foo > /volume/foo
/ $ ls -l /volume
total 4
-rw-r--r--    1 1111     555              4 Mar 18 06:09 foo

/ $ echo foo > /tmp/foo
/ $ ls -l /tmp
total 4
-rw-r--r--    1 1111     root             4 Mar 18 06:10 foo

/ $ exit

 

 

파드의 보안 관련 기능 사용 제한

클러스터 관리자는 파드 보안 정책 리소스, PodSecurityPolicy 리소스를 생성해, 보안 관련 기능의 사용을 제한할 수 있다.

 

  • PodSecurityPolicy 리소스 소개
    • 특징
      • 클러스터 수준 리소스.
      • 사용자가 파드에서 사용할 수 있거나 사용할 수 없는 보안 기능을 정의
      • 해당 리소스에 구성된 정책을 유지하는 작업은 API 서버의 어드미션 컨트롤 플러그인으로 수행
        • 사용자가 파드 서버 리소스를 API 서버에 게시
        • PodSecurityPolicy 어드미션 컨트롤 플러그인이 PodSecurityPolicy로 파드 정의 유효성을 검사
        • 파드가 클러스터의 정책을 준수 >> 승인 후 etcd에 저장. / 준수하지 않을 시 즉시 거부.
        • 플러그인이 정책 구성 기본값 따라 파드 리소스 수정 가능
      • 실습을 위해 어드미션 컨트롤을 활성화해야 하므로 다음과 같은 명령어를 입력해야 한다.
        minikube start --extra-config=apiserver.enable-admission-plugins=PodSecurityPolicy --addons=pod-security-policy
        자세한 사항은 Minikube 튜토리얼을 참고하자. https://minikube.sigs.k8s.io/docs/tutorials/using_psp/
    • PodSecurityPolicy, PSP 리소스 정의 범위
      • 파드가 호스트의 IPC, PID 또는 네트워크 네임스페이스 사용 가능 여부
      • 파드가 바인딩할 수 있는 호스트 포트
      • 컨테이너 실행 가능한 사용자 ID
      • 특권 모드 컨테이너가 파드를 생성할 수 있는지 여부
      • 커널 기능 허용, 추가, 삭제 목록
      • 컨테이너가 사용 가능한 SELinux 레이블
      • 컨테이너가 쓰기 가능한 루트 파일시스템 사용 가능한지 여부
      • 컨테이너가 실행 가능한 파일 시스템 그룹
      • 파드가 사용 가능한 볼륨 유형

 

$ cat pod-security-policy.yaml
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: default
spec:
  hostIPC: false
  hostPID: false
  hostNetwork: false
  hostPorts:				// 컨테이너는 호스트 포트 아래 범위에서만 바인딩 가능
  - min: 10000
    max: 11000
  - min: 13000
    max: 14000
  privileged: false 			// 컨테이너는 특권 모드에서 실행 불가
  readOnlyRootFilesystem: true		// 컨테이너는 읽기 전용 루트 파일 시스템으로 강제 실행
  runAsUser:				// 컨테이너는 모든 사용자로 실행 가능
    rule: RunAsAny
  fsGroup:				// 컨테이너는 모든 그룹으로 실행 가능
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  seLinux:				// 컨테이너는 원하는 SELinux 그룹 사용 가능
    rule: RunAsAny
  volumes:				// 컨테이너는 모든 볼륨 유형을 파드에 사용 가능
  - '*'
$ cat pod-security-policy.yaml
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: default
spec:
  hostIPC: false
  hostPID: false
  hostNetwork: false
  hostPorts:
  - min: 10000
    max: 11000
  - min: 13000
    max: 14000
  privileged: false
  readOnlyRootFilesystem: true
  runAsUser:
    rule: RunAsAny
  fsGroup:
apiVersion: policy/v1beta1
    rule: RunAsAny
  supplementalGroups:
    rule: RunAsAny
  seLinux:
    rule: RunAsAny
  volumes:
  - '*'

$ kubectl create -f pod-security-policy.yaml
error: unable to recognize "pod-security-policy.yaml": no matches for kind "PodSecurityPolicy" in version "extensions/v1beta1"

$ vim pod-security-policy.yaml
$ cat pod-security-policy.yaml
apiVersion: policy/v1beta1
(후략)

$ kubectl create -f pod-security-policy.yaml
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
podsecuritypolicy.policy/default created

$ kubectl create -f pod-privileged.yaml
pod/pod-privileged created

$ kubectl get po
NAME             READY   STATUS    RESTARTS   AGE
pod-privileged   1/1     Running   0          12s

$ cat pod-privileged.yaml
apiVersion: v1
kind: Pod
metadata:
  name: pod-privileged
spec:
  containers:
  - name: main
    image: alpine
    command: ["/bin/sleep", "999999"]
    securityContext:
      privileged: true
      
$ kubectl describe psp default
Warning: policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
Name:  default

Settings:
  Allow Privileged:                       false
  Allow Privilege Escalation:             true
  Default Add Capabilities:               <none>
  Required Drop Capabilities:             <none>
  Allowed Capabilities:                   <none>
  Allowed Volume Types:                   *
  Allow Host Network:                     false
  Allow Host Ports:                       10000-11000,13000-14000
  Allow Host PID:                         false
  Allow Host IPC:                         false
  Read Only Root Filesystem:              true
  SELinux Context Strategy: RunAsAny
    User:                                 <none>
    Role:                                 <none>
    Type:                                 <none>
    Level:                                <none>
  Run As User Strategy: RunAsAny
    Ranges:                               <none>
  FSGroup Strategy: RunAsAny
    Ranges:                               <none>
  Supplemental Groups Strategy: RunAsAny
    Ranges:                               <none>

** 상기 실습을 통해 확인할 수 있듯이 아래 문제들이 발생했다. 

  1. PodSecurityPolicy 스펙 파일이 잘못된 api version을 기반으로 함을 확인했다. 
    이는 쿠버네티스 공식 문서의 버전으로 변경해주면서 정정하여 오류를 수정했다.
    (문서 : https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.23/#podsecuritypolicy-v1beta1-policy )
  2. 그렇게 생성한 PSP가 무용지물이었다. 본래는 컨테이너의 특권 모드 실행을 막게 설정하기 때문에 특권모드 실행 컨테이너를 가진 파드의 생성 자체가 막혀야 하는데, 해당 파드 생성이 막히지 않음을 확인했다. 그 원인은 파악하지 못했다.
  3. PSP 개념을 배웠더니 1.25부터는 사용되지 않을 삭제 예정의 리소스 종류임을 확인했다. 공식 문서 설명은 다음과 같다. 
    Caution: PodSecurityPolicy is deprecated as of Kubernetes v1.21, and will be removed in v1.25. We recommend migrating to Pod Security Admission, or a 3rd party admission plugin. For a migration guide, see Migrate from PodSecurityPolicy to the Built-In PodSecurity Admission Controller. For more information on the deprecation, see PodSecurityPolicy Deprecation: Past, Present, and Future.
    출처 : https://kubernetes.io/docs/concepts/policy/pod-security-policy/
Pod Security Admission
FEATURE STATE: Kubernetes v1.23 [beta]

The Kubernetes Pod Security Standards define different isolation levels for Pods. These standards let you define how you want to restrict the behavior of pods in a clear, consistent fashion. As a Beta feature, Kubernetes offers a built-in Pod Security admission controller, the successor to PodSecurityPolicies. Pod security restrictions are applied at the namespace level when pods are created.
https://kubernetes.io/docs/concepts/security/pod-security-admission/ 

Pod Security Standards
The Pod Security Standards define three different policies to broadly cover the security spectrum. These policies are cumulative and range from highly-permissive to highly-restrictive. This guide outlines the requirements of each policy.

1. Privileged : Unrestricted policy, providing the widest possible level of permissions. This policy allows for known privilege escalations.
2. Baseline : Minimally restrictive policy which prevents known privilege escalations. Allows the default (minimally specified) Pod configuration.
3. Restricted : Heavily restricted policy, following current Pod hardening best practices.
https://kubernetes.io/docs/concepts/security/pod-security-standards/

 

  • PSP 내 runAsUser, fsGroup, supplementalGroups 정책
    • RunAsAny 규칙 : 컨테이너가 실행할 수 있는 사용자 / 그룹의 제한 없음
    • MustRunAs 규칙 : 사용자 혹은 그룹 ID 목록을 제한해 지정
    • MustRunAsNonRoot 규칙 : runAsUser 필드에 활용 가능한 추가 규칙. 루트로 실행되는 컨테이너 배포 불가. 
$ cat psp-must-run-as.yaml
apiVersion: extensions/v1beta1
kind: PodSecurityPolicy
metadata:
  name: default
spec:
  hostIPC: false
  hostPID: false
  hostNetwork: false
  hostPorts:
  - min: 10000
    max: 11000
  - min: 13000
    max: 14000
  privileged: false
  readOnlyRootFilesystem: true
  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
  seLinux:
    rule: RunAsAny
  volumes:
  - '*'

 

  • PSP 리소스로 컨테이너 리눅스 커널 기능 추가/삭제해 세분화 권한 제공하기
    • allowedCapabilities : 컨테이너에 해당 기능을 사용하는 것을 허용.
      파드 작성자가 컨테이너 스펙의 securityContext.capabilities 필드에 추가할 수 있는 기능을 지정
    • defaultAddCapabilities : 컨테이너에 해당 기능을 자동으로 추가.
      배포되는 모든 파드 컨테이너에 자동으로 추가됨. 각 컨테이너 사양에 명시적으로 삭제해야 제거 가능.
    • requiredDropCapabilites : 컨테이너에 해당 기능을 삭제하도록 요구.
      모든 컨테이너에서 자동으로 삭제. securityContext.capabilities.drop 필드 추가와 같다. 
  • PSP 리소스로 파드 사용 가능 볼륨 유형 제한
    • 사용자가 파드에 추가할 수 있는 볼륨 유형을 정의
    • 최소한 emptyDir, configMap, downwardAPI, persistentVolumeClaim 사용을 허용해야 함.
  • 각 사용자와 그룹에 각기 다른 PSP 할당
    • 클러스터 수준 리소스이므로 특정 네임스페이스 지정 및 적용 불가
    • RBAC을 사용해 사용자별 특정 PSP 할당 방법
      • 특정 권한을 정의한 특별한 정책, PSP 리소스를 생성
      • 해당 PSP 리소스를 기반으로 클러스터롤을 생성
      • 클러스터롤바인딩을 사용해 특정 유저에게만 바인딩

**구체적인 실습 내용은 생략함. 그 이유는 상기 설명과 같이 곧 사용되지 않을 리소스이기 때문. 

 

 

파드 네트워크 분리

  • 배경
    • 파드 간 통신 대상 제한을 통한 파드 간 네트워크 보호
    • 구성 가능 여부는 컨테이너 네트워킹 플러그인 따라 결정.
      (플러그인 지원 시 NetworkPolicy 리소스 생성해 격리 구성)
      클러스터에 사용된 CNI 플러그인 또는 다른 유형의 네트워킹 솔루션이 해당 유형 리소스 지원해야 사용 가능.
  • NetworkPolicy 리소스
    • 해당 레이블 셀렉터와 일치하는 파드에 적용.
      파드 셀렉터, 네임스페이스 셀렉터, CIDR 표기법 지정 네트워크 IP 대역 일치 파드 등에 적용.
    • 파드에 접근 가능한 소스 [인그레스], 파드에서 접근 가능한 대상 [이그레스] 지정. 
  • 네임스페이스에서 네트워크 격리 사용
    • 기본 지정 네임스페이스 파드 : 누구나 액세스 가능
    • default-deny NetworkPolicy : 모든 클라이언트가 네임스페이스 내 모든 파드 연결 불가.
  • NetworkPolicy 리소스 spec 조정으로 네임스페이스 일부 클라이언트 파드만 서버 파드 연결 허용
    • spec.podSelector.matchLabels._____ 원하는 레이블 사용 (ex. app: database)
      연결할 파드를 특정 레이블 파드로 제한
    • spec.ingress.from.podSelector.matchLabels._____ 원하는 레이블 사용 (ex. app: webserver)
      해당 레이블의 파드에서 들어오는 연결만 허용하도록 제한
    • spec.ingress.ports.port: ____ 특정 포트로 들어오는 연결만 허용.
$ cat network-policy-postgres.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: postgres-netpolicy
spec:
  podSelector:
    matchLabels:
      app: database
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: webserver
    ports:
    - port: 5432

 

 

 

  • 쿠버네티스 네임스페이스 간 네트워크 격리
    • 여러 테넌트가 동일 클러스터를 사용하는 경우 : 
      다른 테넌트가 현재 테넌트의 마이크로서비스에 액세스하지 못하도록 격리하는 방법
      • NetworkPolicy 리소스의 spec.podSelector.matchLabels._____ (ex. app: shopping-cart)
        격리하고자 하는 마이크로서비스 파드의 레이블 명시
      • NetworkPolicy 리소스의 spec.ingress.from.namespaceSelector.matchLabels.tenant: {tenant_label_of_ns}
        해당 레이블이 지정된 네임스페이스 내 실행 중인 파드만 해당 마이크로서비스에 액세스 가능
      • 다른 테넌트에게도 액세스 권한 부여하기 위해서는 추가 NetworkPolicy 리소스 생성 혹은 기존 리소스 내 인그레스 규칙 추가로 구현 가능
    • 다중 테넌트 쿠버네티스 클러스에서, 테넌트는 자신이 속한 네임스페이스에 레이블 혹은 어노테이션을 직접 추가할 수 없음. 직접 추가 가능하면 네임스페이스 셀렉터 기반 인그레스 규칙 우회 가능. 
  • CIDR 표기법으로 격리
    • CIDR 표기법으로 IP 블록을 지정해, 해당 범위의 IP에서만 액세스 가능하도록 규칙 지정
      • spec.ingress.from.ipBlock.cidr: 192.168.1.0/24 로 해당 대역의 클라이언트 트래픽만 허용
  • 이그레스 규칙으로 아웃바운드 트래픽도 제한 가능
    • spec.podSelector.matchLabels.app: {pod_label} 로 규칙 대상 파드를 레이블 통해 정의
    • spec.egress.to.podSelector.matchLables.app: {pod_label} 로 해당 레이블 파드로만 연결 가능

 

 

 

 

[부록] 실습 중 나타난 minikube start 오류

RBAC 규칙을 구성하는 중 ...\ E0321 14:23:34.117986 15172 kubeadm.go:270] unable to create cluster role binding, some addons might not work: apply sa: sudo /var/lib/minikube/binaries/v1.23.1/kubectl create clusterrolebinding minikube-rbac --clusterrole=cluster-admin --serviceaccount=kube-system:default --kubeconfig=/var/lib/minikube/kubeconfig: Process exited with status 1 stdout: stderr: error: failed to create clusterrolebinding: clusterrolebindings.rbac.authorization.k8s.io "minikube-rbac" already exists

[해설]
https://github.com/kubernetes/minikube/issues/13431
"looks like an issue in minikue1.25. Downgrade to minikube1.24 works."
"Thanks for reporting this issue, I did some testing and it was introduced with my PR #13121. It seems to be specific to VM drivers, it wasn't caught by the tests as it doesn't break the start, it just outputs the error to the screen. Also, I'm not sure if it will cause any actual errors as it's failing to create something that already exists, but if anyone has any reports of an addon not working after seeing this message please report it."