本連載では、エンタープライズシステムでコンテナやKubernetesを活用した業務システムを開発・運用するエンジニアに向けて、知っておくべきKubernetesセキュリティの基礎知識、Microsoftが提供するパブリッククラウド「Azure」を使ったクラウドでのKubernetesセキュリティ対策のポイント、気を付けておきたい注意点などの実践的なノウハウを紹介します。

今回も引き続き、コンテナセキュリティの基礎となるコンテナ技術について説明します。前回と前前回は、コンテナの基礎になっているLinuxカーネルの機能「namespace」「cgroup」「Capability」について説明しました。

コンテナ技術の基礎の最終回である第3回では、これらの技術がKubernetesとどのような関係があるのかを説明していきます。

Kubenetesとnamespace

まずは、Kubenetesのノードの中にどのようなnamespaceがあるのかを見てみます。ノードの中を探索するための特権を持ったDaemonSetをデプロイします。DaemonSetのイメージの素になるDockerfileは以下にしました。

FROM ubuntu:20.04

ENTRYPOINT ["tail","-f", "/etc/profile"]

DaemonSetを定義するyamlは以下です。securityContextで特権を与えており、hostPIDとhostNetworkをtrueにし、hostPathのルートをマウントする極めて危険なものです。この中にkubectl exec -it root-daemon-xxxxx -- bashで入り、ノードの中を探索します。

apiVersion: apps/v1

kind: DaemonSet

metadata:

name: root-daemon

spec:

selector:

matchLabels:

app: root-daemon

template:

metadata:

labels:

app: root-daemon

spec:

hostPID: true

hostNetwork: true

containers:

- name: network-collect

image: xxxxxxxxxxxxxxxxxx.azurecr.io/root-daemon:v0.0.1

securityContext:

privileged: true

resources:

limits:

cpu: 50m

memory: 50Mi

requests:

cpu: 50m

memory: 50Mi

volumeMounts:

- mountPath: /hostroot

name: hostroot

volumes:

- name: hostroot

hostPath:

path: /

ベーシックなnginxのDeploymentも適用してみます。

apiVersion: apps/v1

kind: Deployment

metadata:

name: nginx-deployment-seccomp

spec:

selector:

matchLabels:

app: nginx-seccomp

replicas: 2

template:

metadata:

labels:

app: nginx-seccomp

spec:

securityContext:

seccompProfile:

type: RuntimeDefault

containers:

- name: nginx

image: nginx:1.14.2

ports:

- containerPort: 80

resources:

limits:

cpu: 100m

memory: 125Mi

requests:

cpu: 90m

memory: 115Mi

kubectl exec -it root-daemon-xxxxx -- bashでノードの中を探索する前に、同じノードのnginxのContainer IDを確認しておきます。今回は、f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437dがContainer IDです。

$ kubectl get pods -o wide

NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES

nginx-deployment-seccomp-55449744c7-g4zp9 1/1 Running 0 124m 10.240.0.104 aks-agentpool-23069546-vmss000000

nginx-deployment-seccomp-55449744c7-z2zd5 1/1 Running 0 124m 10.240.0.200 aks-agentpool-23069546-vmss000002

root-daemon-gzcrq 1/1 Running 0 149m 10.240.0.115 aks-agentpool-23069546-vmss000002

root-daemon-jbzcd 1/1 Running 0 149m 10.240.0.4 aks-agentpool-23069546-vmss000000

$ kubectl describe pod nginx-deployment-seccomp-55449744c7-z2zd5 | grep "Container ID"

Container ID: containerd://f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d

次に、nginxのnamespaceがどうなっているのかを確認してみます。

# ls -l /proc/14611/ns

lrwxrwxrwx 1 root root 0 Nov 30 01:57 cgroup -> 'cgroup:[4026531835]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 ipc -> 'ipc:[4026532474]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 mnt -> 'mnt:[4026532476]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 net -> 'net:[4026532402]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid -> 'pid:[4026532477]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 pid_for_children -> 'pid:[4026532477]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 user -> 'user:[4026531837]'

lrwxrwxrwx 1 root root 0 Nov 30 01:57 uts -> 'uts:[4026532473]'



nginxのコンテナに入ってみる

続いて、出てきたnamespaceのIDとlsnsの結果をひもづけていきましょう。userのnamespaceは分離されておらず、networkとuts、ipcなどの通信関連もpauseというプロセスのnamespaceのものを使っていることが分かります。

# lsns | grep 4026531835

4026531835 cgroup 209 1 root /sbin/init

# lsns | grep 4026532474

4026532474 ipc 3 14509 65535 /pause

# lsns | grep 4026532476

4026532476 mnt 2 14611 root nginx: master process nginx -g daemon off;

# lsns | grep 4026532402

4026532402 net 3 14509 65535 /pause

# lsns | grep 4026532477

4026532477 pid 2 14611 root nginx: master process nginx -g daemon off;

# lsns | grep 4026531837

4026531837 user 209 1 root /sbin/init

# lsns | grep 4026532473

4026532473 uts 3 14509 65535 /pause

ps auxfでプロセスを確認してみます。containerdからcontainerd-shimが立ち上がり、そこからpauseとnginxのプロセスが起動していることが分かります。pauseは複数のコンテナを同じPodにまとめて同じOS上で動いているように見せるために存在しているコンテナです。

root 2413 2.1 2.3 2215532 94044 ? Ssl Nov25 152:52 /usr/bin/containerd

root 14484 0.0 0.2 709076 10304 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io

-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/652192845922a51436085d74c89c7d21f64feb62568432681f845633a174eb31

-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd

65535 14509 0.0 0.0 964 4 ? Ss 01:32 0:00 | _ /pause

root 14590 0.0 0.1 708820 7356 ? Sl 01:32 0:00 _ containerd-shim -namespace k8s.io

-workdir /var/lib/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d

-address /run/containerd/containerd.sock -containerd-binary /usr/bin/containerd

root 14611 0.0 0.1 32628 5188 ? Ss 01:32 0:00 _ nginx: master process nginx -g daemon off;

101 14656 0.0 0.0 33076 2344 ? S 01:32 0:00 _ nginx: worker process

nginxのコンテナに入ってみましょう。nginxのコンテナには最低限のものしか含まれていないので、psコマンドを使うこともできません。

# nsenter --target 14611 --all bash

# ps

-sh: 2: ps: not found

# cat /etc/debian_version

9.8

今度はnsenterでホスト側のnamespaceに入り、そこからnginxコンテナのrootfsの中をのぞいてみます。mountの結果を事前に確認したnginxコンテナのIDでgrepすると、overlay on 〜が表示されます。

ここがnginxコンテナのrootfsです。cdで移動して、cat ./etc/debian_versionを叩いてみると、さきほどと同じ9.8が表示されました。

# nsenter --target 1 --all bash

# mount | grep f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d

overlay on /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs type overlay (rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/522/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/521/fs:/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/518/fs,upperdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/fs,workdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/552/work,xino=off)

# cd /run/containerd/io.containerd.runtime.v1.linux/k8s.io/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/rootfs

# cat ./etc/debian_version

9.8



ネットワークを確認する

ネットワークについても確認してみます。5つのnamespaceとnamespaceにひもづくvethが作られていることが分かります。

# nsenter --target 1 --all bash

# ip netns list

cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 (id: 2)

cni-2ff38e97-62c6-90c9-5f23-2525ff3c9292 (id: 5)

cni-3e8372c7-7b9c-1325-f3bc-60d4443e2811 (id: 1)

cni-27dd6491-b667-2f36-a5ab-8cb404276547 (id: 7)

cni-6f11b87d-1a2b-e95b-0c4d-8b57d4aaf822 (id: 0)

# ip link show

1: lo: mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

2: eth0: mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000

link/ether 00:22:48:68:f4:bd brd ff:ff:ff:ff:ff:ff

4: azveb78922e0be@if3: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 2a:c6:90:c3:66:b3 brd ff:ff:ff:ff:ff:ff link-netnsid 0

18: azv233e3487410@if17: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 66:d8:33:ab:3a:6f brd ff:ff:ff:ff:ff:ff link-netnsid 7

20: azvdeb7c0733b9@if19: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 6e:18:fd:7a:63:6d brd ff:ff:ff:ff:ff:ff link-netnsid 1

26: azv19d57a972e9@if25: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 92:de:0e:c5:07:41 brd ff:ff:ff:ff:ff:ff link-netnsid 5

30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2

# ip -d link show azv6bf1c5f71ac

30: azv6bf1c5f71ac@if29: mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000

link/ether 5a:37:35:af:83:e6 brd ff:ff:ff:ff:ff:ff link-netnsid 2 promiscuity 0

veth addrgenmode eui64 numtxqueues 1 numrxqueues 1 gso_max_size 65536 gso_max_segs 65535

# ip route

default via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100

10.240.0.0/16 dev eth0 proto kernel scope link src 10.240.0.115

10.240.0.120 dev azveb78922e0be proto static

10.240.0.122 dev azv19d57a972e9 proto static

10.240.0.185 dev azvdeb7c0733b9 proto static

10.240.0.192 dev azv233e3487410 proto static

10.240.0.200 dev azv6bf1c5f71ac proto static

168.63.129.16 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100

169.254.169.254 via 10.240.0.1 dev eth0 proto dhcp src 10.240.0.115 metric 100

nginxのPIDに割り当てられているnamespaceを確認してみます。

ip netns identify 14611

cni-a78e763e-11b1-03c0-889b-ccf6e2290d92

今度は、cni-a78e763e-11b1-03c0-889b-ccf6e2290d92にひもづくPIDを確認してみます。

ip netns pids cni-a78e763e-11b1-03c0-889b-ccf6e2290d92

14509 ★pauseコンテナ

14611 ★nginxのMaster

14656 ★nginxのWorker

cni-a78e763e-11b1-03c0-889b-ccf6e2290d92の中でip addressを実行すると、10.240.0.200/16のeth0@if30がでてきます。これはnginxのPodに割り当てられているIPアドレスと同じものになっているのが分かります。

ip netns exec cni-a78e763e-11b1-03c0-889b-ccf6e2290d92 ip address

1: lo: mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000

link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00

inet 127.0.0.1/8 scope host lo

valid_lft forever preferred_lft forever

inet6 ::1/128 scope host

valid_lft forever preferred_lft forever

29: eth0@if30: mtu 1500 qdisc noqueue state UP group default qlen 1000

link/ether 86:0d:b8:e5:96:1e brd ff:ff:ff:ff:ff:ff link-netnsid 0

inet 10.240.0.200/16 scope global eth0

valid_lft forever preferred_lft forever

inet6 fe80::840d:b8ff:fee5:961e/64 scope link

valid_lft forever preferred_lft forever



Kubenetesとcgroups

Kubernetesでコンテナのリソースの上限を指定するのに使われるlimitはcgroupを使って実現されています。

- name: nginx

image: nginx:1.14.2

ports:

- containerPort: 80

resources:

limits:

cpu: 100m

memory: 125Mi

requests:

cpu: 90m

memory: 115Mi

nginxのmasterプロセスのPIDは14611でした。このプロセスのcgroupがどこに定義されているかは、/proc/プロセスID/cgroupを見れば分かります。

cat /proc/14611/cgroup

7:memory:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d

3:cpu,cpuacct:/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d

CPU

まずは、CPUから確認していきましょう。cgroupのtasksをみると、nginxのmasterプロセスの14611とworkerプロセスの14656が対象になっています。

# cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/tasks

14611

14656

limitのcpu: 100mは以下の2つの値で表現されています。

・cpu.cfs_quota_us 10000

・cpu.cfs_period_us 100000

# cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca7003c66bbb437d/cpu.cfs_quota_us

10000

cat /hostroot/sys/fs/cgroup/cpu,cpuacct/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597

ca7003c66bbb437d/cpu.cfs_period_us

100000

6bbb437d/cpu.cfs_period_us

100000

メモリ

次に、メモリを確認します。memory.limit_in_bytesを見てみると131072000と出てきますね。計算すると分かりますが、これは125MBのことです。想定どおり、limitの値が反映されていることが分かります。

# cat /hostroot/sys/fs/cgroup/memory/kubepods/burstable/pod0684166a-df5e-4c54-b489-5b1e3c38dd1d/f66dd3c1e442f0da10aaf0d6954fcafa4d3533da5f3bb597ca700

3c66bbb437d/memory.limit_in_bytes

131072000

KubenetesとCapability

確認したように、User namespaceは分離されていません。したがって、ホストのrootはコンテナのrootと違うものではありません。その状態でコンテナの権限を制限しているのはCapabilityです。nginxのプロセスのCapabilityを確認してみましょう。getcapsコマンドを使います。

nginxのプロセスには、cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcapだけを許可されていることが分かります。

$ nsenter --target 1 --all

$ getpcaps 1

Capabilities for `1': = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep

$ getpcaps 14611

Capabilities for `14611': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

許可されているものの一覧は、以下になります。説明はMan page of CAPABILITIESからの引用です。

今回は、コンテナセキュリティを学ぶ上での基礎となる技術について説明しました。次回は、Kubernetes クラスタの保護について説明します。

○野村総合研究所(NRI) 湯川勇太(ゆかわ ゆうた)

前職で大手家電量販店のECモールの立ち上げとECサイトの刷新プロジェクトを経験。NRI入社後は製造業の商品検索システムを担当した。現在は大手物流企業向けの基幹システムの方式設計や技術検証、トラブル対応を行っている。