Runtimes usually apply the profiles from a local path, for example:
```yaml
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
```
The profile `nginx-1.25.3.json` has to be available in the root directory of the
kubelet, appended by the `seccomp` directory. This means the default location
for the profile on-disk would be `/var/lib/kubelet/seccomp/nginx-1.25.3.json`.
If the profile is not available, then runtimes will fail on container creation
like this:
```shell
kubectl get pods
```
```console
NAME READY STATUS RESTARTS AGE
pod 0/1 CreateContainerError 0 38s
```
```shell
kubectl describe pod/pod | tail
```
```console
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
```
The major obstacle of having to manually distribute the `Localhost` profiles
will lead many end-users to fall back to `RuntimeDefault` or even running their
workloads as `Unconfined` (with disabled seccomp).
## CRI-O to the rescue
The Kubernetes container runtime [CRI-O](https://github.com/cri-o/cri-o)
provides various features using custom annotations. The v1.30 release
[adds](https://github.com/cri-o/cri-o/pull/7719) support for a new set of
annotations called `seccomp-profile.kubernetes.cri-o.io/POD` and
`seccomp-profile.kubernetes.cri-o.io/<CONTAINER>`. Those annotations allow you
to specify:
- a seccomp profile for a specific container, when used as:
The image manifest contains a reference to a specific required config media type
(`application/vnd.cncf.seccomp-profile.config.v1+json`) and a single layer
(`application/vnd.oci.image.layer.v1.tar`) pointing to the `seccomp.json` file.
But now, let's give that new feature a try!
### Using the annotation for a specific container or whole pod
CRI-O needs to be configured adequately before it can utilize the annotation. To
do this, add the annotation to the `allowed_annotations` array for the runtime.
This can be done by using a drop-in configuration
`/etc/crio/crio.conf.d/10-crun.conf` like this:
```toml
[crio.runtime]
default_runtime = "crun"
[crio.runtime.runtimes.crun]
allowed_annotations = [
"seccomp-profile.kubernetes.cri-o.io",
]
```
Now, let's run CRI-O from the latest `main` commit. This can be done by either
building it from source, using the [static binary bundles](https://github.com/cri-o/packaging?tab=readme-ov-file#using-the-static-binary-bundles-directly)
or [the prerelease packages](https://github.com/cri-o/packaging?tab=readme-ov-file#usage).
To demonstrate this, I ran the `crio` binary from my command line using a single
node Kubernetes cluster via [`local-up-cluster.sh`](https://github.com/cri-o/cri-o?tab=readme-ov-file#running-kubernetes-with-cri-o).
Now that the cluster is up and running, let's try a pod without the annotation
running as seccomp `Unconfined`:
```shell
cat pod.yaml
```
```yaml
apiVersion: v1
kind: Pod
metadata:
name: pod
spec:
containers:
- name: container
image: nginx:1.25.3
securityContext:
seccompProfile:
type: Unconfined
```
```shell
kubectl apply -f pod.yaml
```
The workload is up and running:
```shell
kubectl get pods
```
```console
NAME READY STATUS RESTARTS AGE
pod 1/1 Running 0 15s
```
And no seccomp profile got applied if I inspect the container using
I have to delete and recreate the Pod, because only recreation will apply a new
seccomp profile:
```shell
kubectl delete pod/pod
```
```console
pod "pod" deleted
```
```shell
kubectl apply -f pod.yaml
```
```console
pod/pod created
```
The CRI-O logs will now indicate that the runtime pulled the artifact:
```console
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
If I now use that image in an CRI-O test pod definition:
```yaml
apiVersion: v1
kind: Pod
metadata:
name: pod
# no Pod annotations set
spec:
containers:
- name: container
image: quay.io/crio/nginx-seccomp:v2
```
Then the CRI-O logs will indicate that the image annotation got evaluated and
the profile got applied:
```shell
kubectl delete pod/pod
```
```console
pod "pod" deleted
```
```shell
kubectl apply -f pod.yaml
```
```console
pod/pod created
```
```console
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