仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

Kubernetes 1.17でPod Security Policiesを試してみる

[注釈] Pod Security PoliciesはKubernetes 1.17ではBeta版の機能です。現在Kubernetes 1.18がRC版、Kubernetes 1.19が開発版としてリリースされていますが、どこかのタイムングでGA機能になると思われます。GAになった場合に再検証が必要であると考えています。

はじめに

Kubernetes 1.17ではPod Security PoliciesがBetaになりました。 Pod Security Policiesを使うと、Podの作成や更新に対してセキュリティに関わるルールを定義し、それに従ったPodの運用が可能です。

Pod Security Policiesについてはこちらのブログで利用を推奨されています。

www.cncf.io

Pod Security Policiesは例えば特権コンテナーの可否、利用可能なボリューム、Linux capabilities、実行ユーザーやグループなどの指定が可能です。

Pod Security Policiesでは様々なことを設定できますが、今回はLinux capabilitiesを設定するのを試してみました。

Kube API ServerでPod Security Policyサポートを追加

Pod Security PolicyはまだBeta版のため、デフォルト設定では有効化されていません。Admission PluginとしてKube API Serverに追加する必要があります。

以下の方法で有効化できますが、あくまでBeta版であることを理解してからご利用ください。少なくとも安定稼働中のKubernetesクラスターの設定を変えて利用するのは筆者は推奨しませんし、設定変更で何らかの問題が発生しても保証いたしません。

# vi /etc/kubernetes/manifests/kube-apiserver.yaml
apiVersion: v1
kind: Pod
metadata:
  creationTimestamp: null
  labels:
    component: kube-apiserver
    tier: control-plane
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.17.28.119
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction,PodSecurityPolicy  <-追記
    - --enable-bootstrap-token-auth=true
    - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.crt

--enable-admission-plugins= の行に PodSecurityPolicy を追加します。

設定変更後にこのままKube API Serverを再起動すると、クラスターでPod Security Policyが適用されます。KubernetesのPod Security Policyはホワイトリスト式のセキュリティ機能であるため、このままではPodは一切作成できません。当然困るので「こういったルールに則ったPodであれば作成しても良い」というルールを定義します。

ルールの定義については下記公式のドキュメントに書かれています。

kubernetes.io

今回はサンプルをベースに少々書き換えたルールをシステムに定義してみます。

ポリシーテンプレートをダウンロード

いずれかのコマンドを使って、ポリシーテンプレートをダウンロードします。

# wget https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/policy/example-psp.yaml
# curl -LO https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/policy/example-psp.yaml

ポリシーを追記

「requiredDropCapabilities」を追記して、SYS_CHROOTとNET_RAWを定義します。またこのポリシーには「privileged: false」が設定されているため、特権コンテナーの実行ができなくなります。

SYS_CHROOTを指定すると、コンテナーでchrootが利用できなくなります。また、NET_RAWを指定するとRAWソケットと PACKETソケットの利用に規制がかかり、例えばpingやtracerouteなどが利用不可になります。

LinuxのCapabilitiesについては次のドキュメントやシステムの「man capabilities」を実行して最新の情報を確認してください。

# vi example-psp.yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
  name: example
spec:
  privileged: false  # Don't allow privileged pods!
  # The rest fills in some required fields.
  requiredDropCapabilities:    <-追記
    - SYS_CHROOT     <-追記
    - NET_RAW     <-追記
  seLinux:
    rule: RunAsAny
...

ポリシーを適用

ポリシーを適用するには次のコマンドを実行します。

# kubectl apply -f example-psp.yaml

まだKube API Serverを再起動していない場合は再起動します。Masterノードを再起動しましょう。

Pod Security Policyをテストする

こういうYAMLを作ってみます。見るからに悪意のありそうなYAMLファイルです。ホストの/をPodの/rootfsにマウントしています。

今回は定義しませんでしたが、AllowedHostPathsで定義すれば、安全なパスを利用できると思います。

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
   - image: nginx:alpine
     tty: true
     name: test-container
     volumeMounts:
     - mountPath: /rootfs
       name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /
      # this field is optional
      type: Directory

先ほどのYAMLを使ってPodを作成してみます。

# kubectl create -f testpd.yaml

SYS_CHROOT をdropしたことにより、chrootを実行して拒否されることを確認します。未設定の場合はシェルがホストに抜けてしまいます。rootユーザーでプロセスが動いているわけですし、DockerデフォルトのSecure computing modeにはchrootはパーミッションがあたっていないので当然です。

# kubectl exec -it test-pd -- chroot /rootfs
chroot: can't change root directory to '/rootfs': Operation not permitted
command terminated with exit code 1

NET_RAW をdropしたことにより、pingやtracerouteが拒否されることを確認します。 pingやtracerouteを実行するとpermission deniedになることが確認できます。

/ # ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
/ # traceroute 8.8.8.8
traceroute: socket(AF_INET,3,1): Operation not permitted

しかしIPアドレスはCNIから取得できているため、パッケージアップデートなどは普通にできます。 example-psp.yamlのNET_RAWを除去して「kubectl apply」しなおせばping tracerouteは可能になりますが、常時全てのPodでそれができる必要はほぼ無いのでそのままの方が良いです。

/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN 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
3: eth0@if10: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1450 qdisc noqueue state UP 
    link/ether 22:0c:e7:7c:e6:ac brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.8/24 scope global eth0
       valid_lft forever preferred_lft forever


/ # apk update
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.10/community/x86_64/APKINDEX.tar.gz
v3.10.4-37-g4579131755 [http://dl-cdn.alpinelinux.org/alpine/v3.10/main]
v3.10.4-38-g2185d3b8d2 [http://dl-cdn.alpinelinux.org/alpine/v3.10/community]
OK: 10348 distinct packages available

コンテナーのeth0に設定されたIPアドレスにpingが通ることを確認します。コンテナーの中ではコマンド実行できませんが、外からはアクセスできることが確認できます。

# ping -c3 10.244.0.8 
PING 10.244.0.8 (10.244.0.8) 56(84) bytes of data.
64 bytes from 10.244.0.8: icmp_seq=1 ttl=64 time=0.296 ms
64 bytes from 10.244.0.8: icmp_seq=2 ttl=64 time=0.084 ms
64 bytes from 10.244.0.8: icmp_seq=3 ttl=64 time=0.157 ms

--- 10.244.0.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2049ms
rtt min/avg/max/mdev = 0.084/0.179/0.296/0.087 ms

今回はKubernetes 1.17のPod Security Policies機能で設定できるセキュリティ機能のうち、Linux Capabilitiesの強制を試してみました。

Kubernetes 1.17のPod Security Policies機能を使えば、実行するPodにセキュリティ的なルールを定義できます。セキュアなKubernetesクラスターを構築する上で設定しておきたい設定であると言えそうです。