CRI-O: OCIレジストリからのseccompプロファイルの適用
seccompはセキュアなコンピューティングモードを意味し、Linuxカーネルのバージョン2.6.12以降の機能として提供されました。 これは、プロセスの特権をサンドボックス化し、ユーザースペースからカーネルへの呼び出しを制限するために使用できます。 Kubernetesでは、ノードに読み込まれたseccompプロファイルをPodやコンテナに自動的に適用することができます。
しかし、Kubernetesでseccompプロファイルを配布することは大きな課題です。 なぜなら、JSONファイルがワークロードが実行可能なすべてのノードで利用可能でなければならないからです。 Security Profiles Operatorなどのプロジェクトは、クラスター内でデーモンとして実行することでこの問題を解決しています。 この設定から、コンテナランタイムがこの配布プロセスの一部を担当できるかどうかが興味深い点です。
通常、ランタイムはローカルパスからプロファイルを適用します。たとえば:
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: container
image: nginx:1.25.3
securityContext:
seccompProfile:
type: Localhost
localhostProfile: nginx-1.25.3.json
プロファイルnginx-1.25.3.json
はkubeletのルートディレクトリ内にseccomp
ディレクトリを追加して利用可能でなければなりません。
これは、ディスク上のプロファイルのデフォルトの場所が/var/lib/kubelet/seccomp/nginx-1.25.3.json
になることを指しています。
プロファイルが利用できない場合、ランタイムは次のようにコンテナの作成に失敗します。
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod 0/1 CreateContainerError 0 38s
kubectl describe pod/pod | tail
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled 117s default-scheduler Successfully assigned default/pod to 127.0.0.1
Normal Pulling 117s kubelet Pulling image "nginx:1.25.3"
Normal Pulled 111s kubelet Successfully pulled image "nginx:1.25.3" in 5.948s (5.948s including waiting)
Warning Failed 7s (x10 over 111s) kubelet Error: setup seccomp: unable to load local profile "/var/lib/kubelet/seccomp/nginx-1.25.3.json": open /var/lib/kubelet/seccomp/nginx-1.25.3.json: no such file or directory
Normal Pulled 7s (x9 over 111s) kubelet Container image "nginx:1.25.3" already present on machine
Localhost
プロファイルを手動で配布する必要があるという大きな障害は、多くのエンドユーザーがRuntimeDefault
に戻るか、さらにはUnconfined
(seccompが無効になっている)でワークロードを実行することになる可能性が高いということです。
CRI-Oが救世主
KubernetesのコンテナランタイムCRI-Oは、カスタムアノテーションを使用してさまざまな機能を提供しています。
v1.30のリリースでは、アノテーションの新しい集合であるseccomp-profile.kubernetes.cri-o.io/POD
とseccomp-profile.kubernetes.cri-o.io/<CONTAINER>
のサポートが追加されました。
これらのアノテーションを使用すると、以下を指定することができます:
- 特定のコンテナ用のseccompプロファイルは、次のように使用されます:
seccomp-profile.kubernetes.cri-o.io/<CONTAINER>
(例:seccomp-profile.kubernetes.cri-o.io/webserver: 'registry.example/example/webserver:v1'
) - Pod内のすべてのコンテナに対するseccompプロファイルは、コンテナ名の接尾辞を使用せず、予約された名前
POD
を使用して次のように使用されます:seccomp-profile.kubernetes.cri-o.io/POD
- イメージ全体のseccompプロファイルは、イメージ自体がアノテーション
seccomp-profile.kubernetes.cri-o.io/POD
またはseccomp-profile.kubernetes.cri-o.io/<CONTAINER>
を含んでいる場合に使用されます
CRI-Oは、ランタイムがそれを許可するように構成されている場合、およびUnconfined
として実行されるワークロードに対してのみ、そのアノテーションを尊重します。
それ以外のすべてのワークロードは、引き続きsecurityContext
からの値を優先して使用します。
アノテーション単体では、プロファイルの配布にはあまり役立ちませんが、それらの参照方法が役立ちます! たとえば、OCIアーティファクトを使用して、通常のコンテナイメージのようにseccompプロファイルを指定できるようになりました。
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/POD: quay.io/crio/seccomp:v2
spec: …
イメージquay.io/crio/seccomp:v2
には、実際のプロファイル内容を含むseccomp.json
ファイルが含まれています。
ORASやSkopeoなどのツールを使用して、イメージの内容を検査できます。
oras pull quay.io/crio/seccomp:v2
Downloading 92d8ebfa89aa seccomp.json
Downloaded 92d8ebfa89aa seccomp.json
Pulled [registry] quay.io/crio/seccomp:v2
Digest: sha256:f0205dac8a24394d9ddf4e48c7ac201ca7dcfea4c554f7ca27777a7f8c43ec1b
jq . seccomp.json | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"defaultErrno": "ENOSYS",
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": [
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
# イメージのプレーンマニフェストを調べる
skopeo inspect --raw docker://quay.io/crio/seccomp:v2 | jq .
{
"schemaVersion": 2,
"mediaType": "application/vnd.oci.image.manifest.v1+json",
"config":
{
"mediaType": "application/vnd.cncf.seccomp-profile.config.v1+json",
"digest": "sha256:ca3d163bab055381827226140568f3bef7eaac187cebd76878e0b63e9e442356",
"size": 3,
},
"layers":
[
{
"mediaType": "application/vnd.oci.image.layer.v1.tar",
"digest": "sha256:92d8ebfa89aa6dd752c6443c27e412df1b568d62b4af129494d7364802b2d476",
"size": 18853,
"annotations": { "org.opencontainers.image.title": "seccomp.json" },
},
],
"annotations": { "org.opencontainers.image.created": "2024-02-26T09:03:30Z" },
}
イメージマニフェストには、特定の必要な構成メディアタイプ(application/vnd.cncf.seccomp-profile.config.v1+json
)への参照と、seccomp.json
ファイルを指す単一のレイヤー(application/vnd.oci.image.layer.v1.tar
)が含まれています。
それでは、この新機能を試してみましょう!
特定のコンテナやPod全体に対してアノテーションを使用する
CRI-Oは、アノテーションを利用する前に適切に構成する必要があります。
これを行うには、ランタイムの allowed_annotations
配列にアノテーションを追加します。
これは、次のようなドロップイン構成/etc/crio/crio.conf.d/10-crun.conf
を使用して行うことができます:
[crio.runtime]
default_runtime = "crun"
[crio.runtime.runtimes.crun]
allowed_annotations = [
"seccomp-profile.kubernetes.cri-o.io",
]
それでは、CRI-Oを最新のmain
コミットから実行します。
これは、ソースからビルドするか、静的バイナリバンドルを使用するか、プレリリースパッケージを使用することで行うことができます。
これを実証するために、local-up-cluster.sh
を使って単一ノードのKubernetesクラスターをセットアップし、コマンドラインからcrio
バイナリを実行しました。
クラスターが起動して実行されているので、seccomp Unconfined
として実行されているアノテーションのないPodを試してみましょう:
cat pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: container
image: nginx:1.25.3
securityContext:
seccompProfile:
type: Unconfined
kubectl apply -f pod.yaml
ワークロードが起動して実行中です:
kubectl get pods
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 15s
crictl
を使用してコンテナを検査しても、seccompプロファイルが適用されていないことがわかります:
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp
null
では、Podを変更して、コンテナにプロファイルquay.io/crio/seccomp:v2
を適用します:
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/container: quay.io/crio/seccomp:v2
spec:
containers:
- name: container
image: nginx:1.25.3
新しいseccompプロファイルを適用するには、Podを削除して再作成する必要があります。 再作成のみが新しいseccompプロファイルを適用するためです:
kubectl delete pod/pod
pod "pod" deleted
kubectl apply -f pod.yaml
pod/pod created
CRI-Oのログには、ランタイムがアーティファクトを取得したことが示されます:
WARN[…] Allowed annotations are specified for workload [seccomp-profile.kubernetes.cri-o.io]
INFO[…] Found container specific seccomp profile annotation: seccomp-profile.kubernetes.cri-o.io/container=quay.io/crio/seccomp:v2 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Pulling OCI artifact from ref: quay.io/crio/seccomp:v2 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Retrieved OCI artifact seccomp profile of len: 18853 id=26ddcbe6-6efe-414a-88fd-b1ca91979e93 name=/runtime.v1.RuntimeService/CreateContainer
And the container is finally using the profile:
そして、コンテナは最終的にプロファイルを使用しています:
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
ユーザーが接尾辞/container
を予約名/POD
に置き換えると、Pod内のすべてのコンテナに対して同じことが機能します。
たとえば:
apiVersion: v1
kind: Pod
metadata:
name: pod
annotations:
seccomp-profile.kubernetes.cri-o.io/POD: quay.io/crio/seccomp:v2
spec:
containers:
- name: container
image: nginx:1.25.3
コンテナイメージにアノテーションを使用する
特定のワークロードにOCIアーティファクトとしてseccompプロファイルを指定する機能は素晴らしいですが、ほとんどのユーザーはseccompプロファイルを公開されたコンテナイメージに関連付けたいと考えています。 これは、コンテナイメージ自体に適用されるメタデータであるコンテナイメージアノテーションを使用して行うことができます。 たとえば、Podmanを使用して、イメージのビルド中に直接イメージアノテーションを追加することができます:
podman build \
--annotation seccomp-profile.kubernetes.cri-o.io=quay.io/crio/seccomp:v2 \
-t quay.io/crio/nginx-seccomp:v2 .
プッシュされたイメージには、そのアノテーションが含まれます:
skopeo inspect --raw docker://quay.io/crio/nginx-seccomp:v2 |
jq '.annotations."seccomp-profile.kubernetes.cri-o.io"'
"quay.io/crio/seccomp:v2"
そのイメージを使用して、CRI-OのテストPod定義に組み込む場合:
apiVersion: v1
kind: Pod
metadata:
name: pod
# Podのアノテーションが設定されていません
spec:
containers:
- name: container
image: quay.io/crio/nginx-seccomp:v2
その後、CRI-Oのログには、イメージのアノテーションが評価され、プロファイルが適用されたことが示されます:
kubectl delete pod/pod
pod "pod" deleted
kubectl apply -f pod.yaml
pod/pod created
INFO[…] Found image specific seccomp profile annotation: seccomp-profile.kubernetes.cri-o.io=quay.io/crio/seccomp:v2 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Pulling OCI artifact from ref: quay.io/crio/seccomp:v2 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Retrieved OCI artifact seccomp profile of len: 18853 id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
INFO[…] Created container 116a316cd9a11fe861dd04c43b94f45046d1ff37e2ed05a4e4194fcaab29ee63: default/pod/container id=c1f22c59-e30e-4046-931d-a0c0fdc2c8b7 name=/runtime.v1.RuntimeService/CreateContainer
export CONTAINER_ID=$(sudo crictl ps --name container -q)
sudo crictl inspect $CONTAINER_ID | jq .info.runtimeSpec.linux.seccomp | head
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 38,
"architectures": [
"SCMP_ARCH_X86_64",
"SCMP_ARCH_X86",
"SCMP_ARCH_X32"
],
"syscalls": [
{
コンテナイメージの場合、アノテーションseccomp-profile.kubernetes.cri-o.io
はseccomp-profile.kubernetes.cri-o.io/POD
と同様に扱われ、Pod全体に適用されます。
さらに、この機能は、イメージにコンテナ固有のアノテーションを使用する場合にも機能します。
たとえば、コンテナの名前がcontainer1
の場合:
skopeo inspect --raw docker://quay.io/crio/nginx-seccomp:v2-container |
jq '.annotations."seccomp-profile.kubernetes.cri-o.io/container1"'
"quay.io/crio/seccomp:v2"
この機能の素晴らしい点は、ユーザーが特定のコンテナイメージ用のseccompプロファイルを作成し、同じレジストリ内に並べて保存できることです。 イメージをプロファイルにリンクすることで、アプリケーション全体のライフサイクルを通じてそれらを維持する柔軟性が提供されます。
ORASを使用してプロファイルをプッシュする
OCIオブジェクトを作成してseccompプロファイルを含めるには、ORASを使用する場合、もう少し作業が必要です。
将来的には、Podmanなどのツールが全体のプロセスをより簡略化することを期待しています。
現時点では、コンテナレジストリがOCI互換である必要があります。
これはQuay.ioの場合も同様です。
CRI-Oは、seccompプロファイルオブジェクトがコンテナイメージメディアタイプ(application/vnd.cncf.seccomp-profile.config.v1+json
)を持っていることを期待していますが、ORASはデフォルトでapplication/vnd.oci.empty.v1+json
を使用します。
これを実現するために、次のコマンドを実行できます:
echo "{}" > config.json
oras push \
--config config.json:application/vnd.cncf.seccomp-profile.config.v1+json \
quay.io/crio/seccomp:v2 seccomp.json
結果として得られるイメージには、CRI-Oが期待するmediaType
が含まれています。
ORASは単一のレイヤーseccomp.json
をレジストリにプッシュします。
プロファイルの名前はあまり重要ではありません。
CRI-Oは最初のレイヤーを選択し、それがseccompプロファイルとして機能するかどうかを確認します。
将来の作業
CRI-OはOCIアーティファクトを通常のファイルと同様に内部で管理しています。
これにより、それらを移動したり、使用されなくなった場合に削除したり、seccompプロファイル以外のデータを利用したりする利点が得られます。
これにより、OCIアーティファクトをベースにしたCRI-Oの将来の拡張が可能になります。
また、OCIアーティファクトの中に複数のレイヤーを持つことを考える上で、seccompプロファイルの積層も可能になります。
v1.30.xリリースではUnconfined
ワークロードのみがサポートされているという制限は、将来CRI-Oが解決したい課題です。
セキュリティを損なうことなく、全体的なユーザーエクスペリエンスを簡素化することが、コンテナワークロードにおけるseccompの成功の鍵となるようです。
CRI-Oのメンテナーは、新機能に関するフィードバックや提案を歓迎します! このブログ投稿を読んでいただき、ぜひKubernetesのSlackチャンネル#crioを通じてメンテナーに連絡したり、GitHubリポジトリでIssueを作成したりしてください。