CRI-O:從 OCI 登錄檔應用 seccomp 設定檔
Seccomp 代表安全運算模式,自 2.6.12 版以來一直是 Linux 核心的一項功能。它可用於沙箱化程序的權限,限制其從使用者空間向核心發出的呼叫。Kubernetes 可讓您自動將節點上載入的 seccomp 設定檔套用至您的 Pod 和容器。
但分發這些 seccomp 設定檔是 Kubernetes 中的一項主要挑戰,因為 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"
# Inspect the plain manifest of the image
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",
]
現在,讓我們從最新的 main
commit 運行 CRI-O。這可以透過從原始碼建置、使用 靜態二進位套件 或 預發布套件 來完成。
為了演示這一點,我使用 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
我必須刪除並重新建立 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
容器最終正在使用設定檔
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
針對容器映像檔使用註釋
雖然將 seccomp 設定檔指定為特定工作負載上的 OCI 成品是一項很酷的功能,但大多數終端使用者希望將 seccomp 設定檔連結到已發布的容器映像檔。這可以透過使用容器映像檔註釋來完成;註釋不是套用至 Kubernetes Pod,而是一些套用於容器映像檔本身的元資料。例如,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
# no Pod annotations set
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 推送設定檔
當使用 ORAS 時,包含 seccomp 設定檔的 OCI 物件的實際建立需要更多的工作。我希望諸如 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 設定檔之外的任何其他可用資料的好處。這使得 CRI-O 未來能夠在 OCI 成品之上進行增強,但也允許考慮堆疊 seccomp 設定檔,作為在 OCI 成品中擁有多個層的一部分。僅適用於 v1.30.x 版本的 Unconfined
工作負載的限制是 CRI-O 未來想要解決的另一個問題。在不損害安全性的情況下簡化整體使用者體驗似乎是 seccomp 在容器工作負載中成功未來的關鍵。
CRI-O 維護人員將很高興聽取關於新功能的任何回饋或建議!感謝您閱讀這篇網誌文章,請隨時透過 Kubernetes Slack 頻道 #crio 聯絡維護人員,或在 GitHub 儲存庫 中建立 issue。