Kubernetesとseccomp
Kubernetesではコンテナが必要以上の権限を持たないようSandbox機能としてLinuxのCapabilityやseccompが利用できます。LinuxのCapabilityはルートユーザが持つ特権をいくつかのグループに分けたもので、そのグループ単位でコンテナに権限を与えられます。seccompはsyscallを制限するためのフィルターで、それを利用しコンテナとして実行されるアプリケーションが呼び出せるsyscallを制限できます。これら両方で許可された操作のみコンテナで許可された操作となります。どちらもコンテナのセキュリティを高めるために利用できる機能ですが、今回は特にseccompに関係する機能について検証した内容となっています。コンテナランタイムの実装にもよると思いますが、PodのSpecでseccompのprofileを指定しないとデフォルトではunconfined
として扱われるものがあるようで、例えばcontainerdだったりcri-oはそのような動作をするようです。そしてこのunconfined
ではコンテナランタイムでseccompを無効化する特別なプロファイル名として扱われます。
安全を考慮するとアプリケーション毎に利用する最低限のsyscallのみを許容すべきだとは思いますが、アプリケーション毎に設定・メンテナンスし続けるのは大変だと思います。メンテナンスを楽にしながらも可能な限りセキュアにコンテナを動かしたい!と考えるのは私だけではないはず。そのためRuntimeDefaultと呼ばれる特殊なProfileがあり、これを利用するとある程度コンテナアプリケーションで利用しないようなsyscallを制限したProfileが利用できます。しかも例えばcontainerdではLinuxのCapabilitiyに合わせたProfileを動的に生成してくれるようで、seccomp的には利用できるのにCapabilityで制限を外し忘れるということもないのもメリットです。
PodSecurityPolicyとseccomp
SeccompDefault
は名前からも想像できるようにseccomp profileをdefaultingしてくれる機能です。どんな値をdefaultingしてくれるかというと、先ほど紹介したRuntimeDefaultです。SeccompDefaultを利用しなくても、Kubernetesの機能だけでdefaultingは可能で、PodSecurityPolicyを利用することで実現できます。
以下のようにPSPリソースのannotationでdefaultのseccomp profileを指定できます。
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: sample-psp
annotations:
seccomp.security.alpha.kubernetes.io/defaultProfileName: 'runtime/default'
(省略)
このPSPが適用されたPod内のコンテナはseccompが指定されていないとRuntimeDefault
がPodSpecがdefaultingされます。実際に試してみましょう。
$ kubectl create deploy --image=nginx nginx
作成されたPodを確認すると以下のようにsecurityContextが指定されていることがわかります。
apiVersion: v1
kind: Pod
metadata:
annotations:
kubernetes.io/psp: sample-psp
seccomp.security.alpha.kubernetes.io/pod: runtime/default
labels:
app: nginx
pod-template-hash: 6799fc88d8
name: nginx-6799fc88d8-jvcbl
namespace: default
:
spec:
:
securityContext:
seccompProfile:
type: RuntimeDefault
:
このようにseccompProfileフィールドへRuntimeDefaultを指定せずにPodを作成しても、PSPがその値を埋めてくれます。しかしv1.25でPSPは廃止予定のため、この方法は利用できなくなってしまいます。新たなseccomp警察が必要です。そこでSeccompDefaultの出番です
SeccompDefaultとは?
SeccompDefaultはKubernetesのv1.22で新たにalpha機能として追加されました。これはPodのSpecでseccomp profileが指定されなかった時に、RuntimeDefaultでコンテナを動かすものです。
SeccompDefaultを動かしてみる
SeccompDefaultを実際に試してみましょう。今回はkindで動かします。SeccompDefaultはkubeletのfeature gatesで有効化し、かつ--seccomp-default
をセットする必要があります。例えば1ノードのクラスタを起動するときのコンフィグは以下のようになります。
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
image: kindest/node:v1.22.0
kubeadmConfigPatches:
- |
kind: InitConfiguration
nodeRegistration:
kubeletExtraArgs:
feature-gates: "SeccompDefault=true"
seccomp-default: "true"
kindのコンフィグでfeature gates専用のフィールドがありますが、どうやらそこではkubeletに対してはfeature gatesがセットされないようです(今回の検証でそのことに気づきました...)。そのため上記のようにkubeletのextra argsで直接追加する必要があります。
上記で作成されたクラスタに対してSeccompDefaultが有効になっているか確認してみましょう。amicontainedを利用するとブロックされるsyscallをチェックできるようなので、確認にはこのアプリケーションを利用しました。以下のようにseccompを明示的にRuntimeDefaultを指定したもの(seccomp-runtime-default)、seccompを明示的にUnconfinedを指定したもの(seccomp-unconfined)、そして何も指定していないもの(seccomp-nil)を動かして確認します。
apiVersion: v1
kind: Pod
metadata:
name: seccomp-runtime-default
namespace: default
spec:
securityContext:
seccompProfile:
type: RuntimeDefault
restartPolicy: Never
containers:
- image: alpine
imagePullPolicy: Always
name: alpine
command:
- /bin/sh
- -c
- |
apk add curl >/dev/null 2>&1
curl -sSfL -o amicontained https://github.com/genuinetools/amicontained/releases/download/v0.4.9/amicontained-linux-amd64
chmod +x amicontained
./amicontained
---
apiVersion: v1
kind: Pod
metadata:
name: seccomp-unconfined
namespace: default
spec:
securityContext:
seccompProfile:
type: Unconfined
restartPolicy: Never
containers:
- image: alpine
imagePullPolicy: Always
name: alpine
command:
- /bin/sh
- -c
- |
apk add curl >/dev/null 2>&1
curl -sSfL -o amicontained https://github.com/genuinetools/amicontained/releases/download/v0.4.9/amicontained-linux-amd64
chmod +x amicontained
./amicontained
---
apiVersion: v1
kind: Pod
metadata:
name: seccomp-nil
namespace: default
spec:
restartPolicy: Never
containers:
- image: alpine
imagePullPolicy: Always
name: alpine
command:
- /bin/sh
- -c
- |
apk add curl >/dev/null 2>&1
curl -sSfL -o amicontained https://github.com/genuinetools/amicontained/releases/download/v0.4.9/amicontained-linux-amd64
chmod +x amicontained
./amicontained
結果は以下です。seccompに関わる部分以外を省略しています。
結果からわかるようにRuntimeDefaultを指定したものと、nilのものはSeccompがfilteringとなり、Unconfinedのものよりブロックされている数がたくさんあります。
そしてRuntimeDefaultを指定したものとnilのもののブロックされているものが同じことから、nilのものにRuntimeDefaultが適用されていることがわかるかと思います。
$ kubectl logs seccomp-runtime-default
:
Seccomp: filtering
Blocked Syscalls (61):
MSGRCV PTRACE SYSLOG SETSID USELIB USTAT SYSFS VHANGUP PIVOT_ROOT _SYSCTL ACCT SETTIMEOFDAY MOUNT UMOUNT2 SWAPON SWAPOFF REBOOT SETHOSTNAME SETDOMAINNAME IOPL IOPERM CREATE_MODULE INIT_MODULE DELETE_MODULE GET_KERNEL_SYMS QUERY_MODULE QUOTACTL NFSSERVCTL GETPMSG PUTPMSG AFS_SYSCALL TUXCALL SECURITY LOOKUP_DCOOKIE CLOCK_SETTIME VSERVER MBIND SET_MEMPOLICY GET_MEMPOLICY KEXEC_LOAD ADD_KEY REQUEST_KEY KEYCTL MIGRATE_PAGES UNSHARE MOVE_PAGES PERF_EVENT_OPEN FANOTIFY_INIT NAME_TO_HANDLE_AT OPEN_BY_HANDLE_AT SETNS PROCESS_VM_READV PROCESS_VM_WRITEV KCMP FINIT_MODULE KEXEC_FILE_LOAD BPF USERFAULTFD PKEY_MPROTECT PKEY_ALLOC PKEY_FREE
$ kubectl logs seccomp-unconfined
:
Seccomp: disabled
Blocked Syscalls (19):
MSGRCV SYSLOG SETSID VHANGUP PIVOT_ROOT ACCT SETTIMEOFDAY SWAPON SWAPOFF REBOOT SETHOSTNAME SETDOMAINNAME INIT_MODULE DELETE_MODULE LOOKUP_DCOOKIE FANOTIFY_INIT OPEN_BY_HANDLE_AT FINIT_MODULE BPF
$ kubectl logs seccomp-nil
:
Seccomp: filtering
Blocked Syscalls (61):
MSGRCV PTRACE SYSLOG SETSID USELIB USTAT SYSFS VHANGUP PIVOT_ROOT _SYSCTL ACCT SETTIMEOFDAY MOUNT UMOUNT2 SWAPON SWAPOFF REBOOT SETHOSTNAME SETDOMAINNAME IOPL IOPERM CREATE_MODULE INIT_MODULE DELETE_MODULE GET_KERNEL_SYMS QUERY_MODULE QUOTACTL NFSSERVCTL GETPMSG PUTPMSG AFS_SYSCALL TUXCALL SECURITY LOOKUP_DCOOKIE CLOCK_SETTIME VSERVER MBIND SET_MEMPOLICY GET_MEMPOLICY KEXEC_LOAD ADD_KEY REQUEST_KEY KEYCTL MIGRATE_PAGES UNSHARE MOVE_PAGES PERF_EVENT_OPEN FANOTIFY_INIT NAME_TO_HANDLE_AT OPEN_BY_HANDLE_AT SETNS PROCESS_VM_READV PROCESS_VM_WRITEV KCMP FINIT_MODULE KEXEC_FILE_LOAD BPF USERFAULTFD PKEY_MPROTECT PKEY_ALLOC PKEY_FREE
しかしこれはPodSecurityPolicyのようにPodのSpecをdefaultingしてくれません。
どうやらCRIへリクエストを投げるときに行うdefaultingのようで、PodのSpecのdefaultingではありません。
これは機能を有効化するためのfeature gateやオプションをkube-apiserverやkube-controller-managerなどではなく、kubeletに対して設定することからも想像できるかと思います。
PodSecurity AdmissionとSeccompDefault
PodSecurityは作成・更新しようとするPodに対してPod Security Standardsで定義されているポリシーを適用するものです。作成しようとしているPodがそれを満たさない場合、作成を拒否したり、警告に表示したり、audit logへ記録するといったことを制御するAdmission Controllerとして実装されています。詳細は拙作のPodSecurityPolicyの廃止に備えて、一足先にPodSecurity Admissionを試してみよう!をご参照ください。PodSecurity Standardではもちろんseccompのprofileに関しても定義されています。PodSecurity AdmissionのBaselineと呼ばれるプロファイルでは、nil
,RuntimeDefault
,Localhost
のみが許可されており、Unconfined
は許容されません。(PodSeucirty AdmissionのBaselineの実装はこちらで確認できます)。nilが許容されているため、冒頭の説明通りnilを指定することでunconfined
として扱われてしまい、実質制限していないことと同じです。しかしこのRuntimeDefaultと組み合わせることで、PodSecurity AdmissionのBaselineが適用された場合seccompを確実に適用できます。
最後に
SeccompDefaultの動作を確認できました。Kubernetes v1.23時点でこの機能はまだalpha機能となっています。PodSecurityPolicyの廃止までに安心して利用できるようv1.25ではGAになってくれると嬉しいですね。