Repository: kubernetes-sigs/security-profiles-operator
Pull request #378 (2021-03-19 08:33:13)

Add `release-note-none` label to dependabot updates
#### What type of PR is this?


/kind cleanup

#### What this PR does / why we need it:
This saves one more comment before merging those PRs.

#### Which issue(s) this PR fixes:

<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.

Fixes #

or

None
-->
None
#### Does this PR have test?
None
<!--
If tests aren't applicable just write N/A.
-->

#### Special notes for your reviewer:
None
#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://git.k8s.io/community/contributors/guide/release-notes.md
-->

```release-note
None
```


k8s-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes-sigs/security-profiles-operator/pull/378#" title="Author self-approved">saschagrunert</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes-sigs%2Fsecurity-profiles-operator).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/kubernetes-sigs/security-profiles-operator/blob/master/OWNERS)~~ [saschagrunert]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
k8s-ci-robot: @saschagrunert: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
pull-security-profiles-operator-test-e2e | 79d4410922afdfee7fb9990cf3ffe4763f833931 | [link](https://prow.k8s.io/view/gs/kubernetes-jenkins/pr-logs/pull/kubernetes-sigs_security-profiles-operator/378/pull-security-profiles-operator-test-e2e/1372828495448641536) | `/test pull-security-profiles-operator-test-e2e`

[Full PR test history](https://prow.k8s.io/pr-history?org=kubernetes-sigs&repo=security-profiles-operator&pr=378). [Your PR dashboard](https://prow.k8s.io/pr?query=is%3Apr%20state%3Aopen%20author%3Asaschagrunert). Please help us cut down on flakes by [linking to](https://git.k8s.io/community/contributors/devel/sig-testing/flaky-tests.md#filing-issues-for-flaky-tests) an [open issue](https://github.com/kubernetes-sigs/security-profiles-operator/issues?q=is:issue+is:open) when you hit one in your PR.

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index d921d0ffd..d663426c2 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -4,4 +4,6 @@ updates:
   directory: "/"
   schedule:
     interval: daily
+  labels:
+    - "release-note-none"
   open-pull-requests-limit: 10
diff --git a/deploy/base/kustomization.yaml b/deploy/base/kustomization.yaml
index edc16b573..4214595c4 100644
--- a/deploy/base/kustomization.yaml
+++ b/deploy/base/kustomization.yaml
@@ -41,6 +41,7 @@ configMapGenerator:
   - name: security-profiles-operator-profile
     files:
       - profiles/security-profiles-operator.json
+      - profiles/selinuxd.cil
 
 generatorOptions:
   disableNameSuffixHash: true
diff --git a/deploy/base/profiles/selinuxd.cil b/deploy/base/profiles/selinuxd.cil
new file mode 100644
index 000000000..0e69bd504
--- /dev/null
+++ b/deploy/base/profiles/selinuxd.cil
@@ -0,0 +1,34 @@
+(block selinuxd
+    (blockinherit container)
+    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) 
+
+    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process default_context_t ( sock_file ( append getattr open read write )))
+    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process etc_t ( sock_file ( append getattr open read write )))
+    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process file_context_t ( sock_file ( append getattr open read write )))
+    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process security_t ( security ( load_policy )))
+    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process selinux_config_t ( sock_file ( append getattr open read write )))
+    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))
+    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))
+    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))
+    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process semanage_store_t ( sock_file ( append getattr open read write )))
+    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))
+    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))
+    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))
+    (allow process sysfs_t ( sock_file ( append getattr open read write )))
+)
diff --git a/deploy/namespace-operator.yaml b/deploy/namespace-operator.yaml
index 5dc46d3ba..4d71b7e28 100644
--- a/deploy/namespace-operator.yaml
+++ b/deploy/namespace-operator.yaml
@@ -943,6 +943,7 @@ data:
         }
       ]
     }
+  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
 kind: ConfigMap
 metadata:
   labels:
diff --git a/deploy/operator.yaml b/deploy/operator.yaml
index 0f1f2a88f..e05d31ec1 100644
--- a/deploy/operator.yaml
+++ b/deploy/operator.yaml
@@ -941,6 +941,7 @@ data:
         }
       ]
     }
+  selinuxd.cil: "(block selinuxd\n    (blockinherit container)\n    (allow process process ( capability ( dac_override dac_read_search lease audit_write audit_control ))) \n\n    (allow process default_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process default_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process default_context_t ( sock_file ( append getattr open read write )))\n    (allow process etc_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process etc_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process etc_t ( sock_file ( append getattr open read write )))\n    (allow process file_context_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process file_context_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process file_context_t ( sock_file ( append getattr open read write )))\n    (allow process security_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process security_t ( security ( load_policy )))\n    (allow process selinux_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_config_t ( sock_file ( append getattr open read write )))\n    (allow process selinux_login_config_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process selinux_login_config_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process selinux_login_config_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_read_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_read_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_read_lock_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_store_t ( dir ( add_name create getattr ioctl lock open read rename remove_name rmdir search setattr write )))\n    (allow process semanage_store_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_store_t ( sock_file ( append getattr open read write )))\n    (allow process semanage_trans_lock_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process semanage_trans_lock_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process semanage_trans_lock_t ( sock_file ( append getattr open read write )))\n    (allow process sysfs_t ( dir ( add_name create getattr ioctl lock open read remove_name rmdir search setattr write )))\n    (allow process sysfs_t ( file ( append create getattr ioctl lock map open read rename setattr unlink write )))\n    (allow process sysfs_t ( sock_file ( append getattr open read write )))\n)\n"
 kind: ConfigMap
 metadata:
   labels:
diff --git a/go.mod b/go.mod
index 3dce2e54f..75bb7a219 100644
--- a/go.mod
+++ b/go.mod
@@ -10,8 +10,8 @@ require (
 	github.com/pkg/errors v0.9.1
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli/v2 v2.3.0
-	k8s.io/api v0.20.4
-	k8s.io/apimachinery v0.20.4
+	k8s.io/api v0.20.5
+	k8s.io/apimachinery v0.20.5
 	k8s.io/client-go v0.20.4
 	k8s.io/klog/v2 v2.8.0
 	k8s.io/release v0.7.0
diff --git a/go.sum b/go.sum
index 2fe310ccd..9e381c87f 100644
--- a/go.sum
+++ b/go.sum
@@ -1360,8 +1360,9 @@ k8s.io/api v0.0.0-20190918155943-95b840bb6a1f/go.mod h1:uWuOHnjmNrtQomJrvEBg0c0H
 k8s.io/api v0.18.8/go.mod h1:d/CXqwWv+Z2XEG1LgceeDmHQwpUJhROPx16SlxJgERY=
 k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo=
 k8s.io/api v0.20.2/go.mod h1:d7n6Ehyzx+S+cE3VhTGfVNNqtGc/oL9DCdYYahlurV8=
-k8s.io/api v0.20.4 h1:xZjKidCirayzX6tHONRQyTNDVIR55TYVqgATqo6ZULY=
 k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ=
+k8s.io/api v0.20.5 h1:zsMTffV0Le2EiI0aKvlTHEnXGxk1HiqGRhJcCPiI7JI=
+k8s.io/api v0.20.5/go.mod h1:FQjAceXnVaWDeov2YUWhOb6Yt+5UjErkp6UO3nczO1Y=
 k8s.io/apiextensions-apiserver v0.0.0-20190918161926-8f644eb6e783/go.mod h1:xvae1SZB3E17UpV59AWc271W/Ph25N+bjPyR63X6tPY=
 k8s.io/apiextensions-apiserver v0.20.1/go.mod h1:ntnrZV+6a3dB504qwC5PN/Yg9PBiDNt1EVqbW2kORVk=
 k8s.io/apiextensions-apiserver v0.20.2 h1:rfrMWQ87lhd8EzQWRnbQ4gXrniL/yTRBgYH1x1+BLlo=
@@ -1370,8 +1371,9 @@ k8s.io/apimachinery v0.0.0-20190913080033-27d36303b655/go.mod h1:nL6pwRT8NgfF8TT
 k8s.io/apimachinery v0.18.8/go.mod h1:6sQd+iHEqmOtALqOFjSWp2KZ9F0wlU/nWm0ZgsYWMig=
 k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apimachinery v0.20.2/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
-k8s.io/apimachinery v0.20.4 h1:vhxQ0PPUUU2Ns1b9r4/UFp13UPs8cw2iOoTjnY9faa0=
 k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
+k8s.io/apimachinery v0.20.5 h1:wO/FxMVRn223rAKxnBbwCyuN96bS9MFTIvP0e/V7cps=
+k8s.io/apimachinery v0.20.5/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU=
 k8s.io/apiserver v0.0.0-20190918160949-bfa5e2e684ad/go.mod h1:XPCXEwhjaFN29a8NldXA901ElnKeKLrLtREO9ZhFyhg=
 k8s.io/apiserver v0.18.8/go.mod h1:12u5FuGql8Cc497ORNj79rhPdiXQC4bf53X/skR/1YM=
 k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU=
diff --git a/internal/pkg/manager/spod/bindata/spod.go b/internal/pkg/manager/spod/bindata/spod.go
index 1bd1d184c..9b0b2b7ba 100644
--- a/internal/pkg/manager/spod/bindata/spod.go
+++ b/internal/pkg/manager/spod/bindata/spod.go
@@ -76,8 +76,8 @@ var Manifest = &appsv1.DaemonSet{
 								MountPath: "/var/lib",
 							},
 							{
-								Name:      "operator-seccomp-profile-volume",
-								MountPath: "/opt/seccomp-profiles",
+								Name:      "operator-profiles-volume",
+								MountPath: "/opt/spo-profiles",
 								ReadOnly:  true,
 							},
 						},
@@ -125,18 +125,36 @@ var Manifest = &appsv1.DaemonSet{
 						// the chown+chmod to the previous init container and move the copying of
 						// the files into a lifecycle handler of selinuxd.
 						Command: []string{"bash", "-c"},
-						Args: []string{`
-						set -x
-
-						chown 65535:0 /etc/selinux.d
-						chmod 750 /etc/selinux.d
-						cp /usr/share/udica/templates/* /etc/selinux.d
-					`},
+						Args: []string{
+							`set -x
+chown 65535:0 /etc/selinux.d
+chmod 750 /etc/selinux.d
+semodule -i /usr/share/selinuxd/templates/*.cil
+semodule -i /opt/spo-profiles/selinuxd.cil
+`,
+						},
 						VolumeMounts: []v1.VolumeMount{
 							{
 								Name:      "selinux-drop-dir",
 								MountPath: SelinuxDropDirectory,
 							},
+							{
+								Name:      "operator-profiles-volume",
+								MountPath: "/opt/spo-profiles",
+								ReadOnly:  true,
+							},
+							{
+								Name:      "host-fsselinux-volume",
+								MountPath: "/sys/fs/selinux",
+							},
+							{
+								Name:      "host-etcselinux-volume",
+								MountPath: "/etc/selinux",
+							},
+							{
+								Name:      "host-varlibselinux-volume",
+								MountPath: "/var/lib/selinux",
+							},
 						},
 						SecurityContext: &v1.SecurityContext{
 							AllowPrivilegeEscalation: &falsely,
@@ -158,7 +176,8 @@ var Manifest = &appsv1.DaemonSet{
 								v1.ResourceEphemeralStorage: resource.MustParse("10Mi"),
 							},
 							Limits: v1.ResourceList{
-								v1.ResourceMemory:           resource.MustParse("64Mi"),
+								// libsemanage is very resource hungry...
+								v1.ResourceMemory:           resource.MustParse("1024Mi"),
 								v1.ResourceCPU:              resource.MustParse("250m"),
 								v1.ResourceEphemeralStorage: resource.MustParse("50Mi"),
 							},
@@ -262,12 +281,12 @@ var Manifest = &appsv1.DaemonSet{
 							ReadOnlyRootFilesystem: &truly,
 							RunAsUser:              &userRoot,
 							RunAsGroup:             &userRoot,
+							Capabilities: &v1.Capabilities{
+								Add: []v1.Capability{"CHOWN", "FOWNER", "FSETID", "DAC_OVERRIDE"},
+							},
 							SELinuxOptions: &v1.SELinuxOptions{
-								// TODO(jaosorior): Use a more restricted selinux type
-								Type: "spc_t",
+								Type: "selinuxd.process",
 							},
-							/* TODO(jhrozek) is this really needed? */
-							Privileged: &truly,
 						},
 						Resources: v1.ResourceRequirements{
 							Requests: v1.ResourceList{
@@ -348,7 +367,7 @@ var Manifest = &appsv1.DaemonSet{
 						},
 					},
 					{
-						Name: "operator-seccomp-profile-volume",
+						Name: "operator-profiles-volume",
 						VolumeSource: v1.VolumeSource{
 							ConfigMap: &v1.ConfigMapVolumeSource{
 								LocalObjectReference: v1.LocalObjectReference{
@@ -357,14 +376,13 @@ var Manifest = &appsv1.DaemonSet{
 							},
 						},
 					},
-					// Ephemeral emptyDirs for the selinuxd socket and the policies.
-					// As the policies are loaded based on API objects and contents of
-					// the init container and loaded on selinuxd start, we don't need
-					// to persist them
 					{
 						Name: "selinux-drop-dir",
 						VolumeSource: v1.VolumeSource{
-							EmptyDir: &v1.EmptyDirVolumeSource{},
+							HostPath: &v1.HostPathVolumeSource{
+								Path: SelinuxDropDirectory,
+								Type: &hostPathDirectoryOrCreate,
+							},
 						},
 					},
 					{
diff --git a/internal/pkg/manager/spod/spod_controller.go b/internal/pkg/manager/spod/spod_controller.go
index 7b29a859b..e2d5c3978 100644
--- a/internal/pkg/manager/spod/spod_controller.go
+++ b/internal/pkg/manager/spod/spod_controller.go
@@ -323,6 +323,7 @@ func (r *ReconcileSPOd) getConfiguredSPOd(
 	newSPOd.SetNamespace(config.GetOperatorNamespace())
 	templateSpec := &newSPOd.Spec.Template.Spec
 
+	templateSpec.InitContainers = []corev1.Container{r.baseSPOd.Spec.Template.Spec.InitContainers[0]}
 	// Set Images
 	// - Base workload
 	templateSpec.Containers = []corev1.Container{r.baseSPOd.Spec.Template.Spec.Containers[0]}
@@ -333,6 +334,9 @@ func (r *ReconcileSPOd) getConfiguredSPOd(
 
 	// SELinux parameters
 	if cfg.Spec.EnableSelinux {
+		templateSpec.InitContainers = append(
+			templateSpec.InitContainers,
+			r.baseSPOd.Spec.Template.Spec.InitContainers[1])
 		templateSpec.Containers = append(
 			templateSpec.Containers,
 			r.baseSPOd.Spec.Template.Spec.Containers[1])
diff --git a/internal/pkg/nonrootenabler/nonrootenabler.go b/internal/pkg/nonrootenabler/nonrootenabler.go
index ae00f6919..a0b8f509c 100644
--- a/internal/pkg/nonrootenabler/nonrootenabler.go
+++ b/internal/pkg/nonrootenabler/nonrootenabler.go
@@ -80,9 +80,9 @@ func (n *NonRootEnabler) Run() error {
 	}
 
 	if err := n.impl.CopyDirContentsLocal(
-		"/opt/seccomp-profiles", config.KubeletSeccompRootPath,
+		"/opt/spo-profiles", config.KubeletSeccompRootPath,
 	); err != nil {
-		return errors.Wrap(err, "copy local seccomp profiles")
+		return errors.Wrap(err, "copy local security profiles")
 	}
 
 	return nil
diff --git a/internal/pkg/webhooks/recording/impl.go b/internal/pkg/webhooks/recording/impl.go
new file mode 100644
index 000000000..8e921ce3d
--- /dev/null
+++ b/internal/pkg/webhooks/recording/impl.go
@@ -0,0 +1,72 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package recording
+
+import (
+	"context"
+
+	"github.com/go-logr/logr"
+	"github.com/pkg/errors"
+	corev1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/webhooks/utils"
+)
+
+type defaultImpl struct {
+	client  client.Client
+	decoder *admission.Decoder
+}
+
+//go:generate go run github.com/maxbrunsfeld/counterfeiter/v6 -generate
+//counterfeiter:generate . impl
+type impl interface {
+	ListProfileRecordings(context.Context, ...client.ListOption) (*v1alpha1.ProfileRecordingList, error)
+	UpdateResource(context.Context, logr.Logger, client.Object, string) error
+	SetDecoder(*admission.Decoder)
+	DecodePod(admission.Request) (*corev1.Pod, error)
+}
+
+func (d *defaultImpl) ListProfileRecordings(
+	ctx context.Context, opts ...client.ListOption,
+) (*v1alpha1.ProfileRecordingList, error) {
+	profileRecordings := &v1alpha1.ProfileRecordingList{}
+	err := d.client.List(ctx, profileRecordings, opts...)
+	return profileRecordings, errors.Wrap(err, "list profile recordings")
+}
+
+func (d *defaultImpl) UpdateResource(
+	ctx context.Context,
+	logger logr.Logger,
+	object client.Object,
+	name string,
+) error {
+	return utils.UpdateResource(ctx, logger, d.client.Status(), object, name)
+}
+
+func (d *defaultImpl) SetDecoder(decoder *admission.Decoder) {
+	d.decoder = decoder
+}
+
+// nolint: gocritic
+func (d *defaultImpl) DecodePod(req admission.Request) (*corev1.Pod, error) {
+	pod := &corev1.Pod{}
+	err := d.decoder.Decode(req, pod)
+	return pod, errors.Wrap(err, "decode pod")
+}
diff --git a/internal/pkg/webhooks/recording/recording.go b/internal/pkg/webhooks/recording/recording.go
index de9edde61..003f6d896 100644
--- a/internal/pkg/webhooks/recording/recording.go
+++ b/internal/pkg/webhooks/recording/recording.go
@@ -45,9 +45,8 @@ import (
 const finalizer = "active-seccomp-profile-recording-lock"
 
 type podSeccompRecorder struct {
-	client   client.Client
+	impl
 	log      logr.Logger
-	decoder  *admission.Decoder
 	replicas sync.Map
 }
 
@@ -56,8 +55,8 @@ func RegisterWebhook(server *webhook.Server, c client.Client) {
 		"/mutate-v1-pod-recording",
 		&webhook.Admission{
 			Handler: &podSeccompRecorder{
-				client: c,
-				log:    logf.Log.WithName("recording"),
+				impl: &defaultImpl{client: c},
+				log:  logf.Log.WithName("recording"),
 			},
 		},
 	)
@@ -82,10 +81,10 @@ func (p *podSeccompRecorder) Handle(
 	ctx context.Context,
 	req admission.Request,
 ) admission.Response {
-	profileRecordings := &profilerecordingv1alpha1.ProfileRecordingList{}
-	if err := p.client.List(
-		ctx, profileRecordings, client.InNamespace(req.Namespace),
-	); err != nil {
+	profileRecordings, err := p.impl.ListProfileRecordings(
+		ctx, client.InNamespace(req.Namespace),
+	)
+	if err != nil {
 		p.log.Error(err, "Could not list profile recordings")
 		return admission.Errored(http.StatusInternalServerError, err)
 	}
@@ -93,7 +92,7 @@ func (p *podSeccompRecorder) Handle(
 	podChanged := false
 	pod := &corev1.Pod{}
 	if req.Operation != admissionv1.Delete {
-		err := p.decoder.Decode(req, pod)
+		pod, err = p.impl.DecodePod(req)
 		if err != nil {
 			p.log.Error(err, "Failed to decode pod")
 			return admission.Errored(http.StatusBadRequest, err)
@@ -241,8 +240,8 @@ func (p *podSeccompRecorder) addPod(
 		profileRecording.Status.ActiveWorkloads, podName,
 	)
 
-	if err := utils.UpdateResource(
-		ctx, p.log, p.client.Status(), profileRecording, "profilerecording status",
+	if err := p.impl.UpdateResource(
+		ctx, p.log, profileRecording, "profilerecording status",
 	); err != nil {
 		return errors.Wrap(err, "update resource on adding pod")
 	}
@@ -251,7 +250,7 @@ func (p *podSeccompRecorder) addPod(
 		controllerutil.AddFinalizer(profileRecording, finalizer)
 	}
 
-	return utils.UpdateResource(ctx, p.log, p.client, profileRecording, "profilerecording")
+	return p.impl.UpdateResource(ctx, p.log, profileRecording, "profilerecording")
 }
 
 func (p *podSeccompRecorder) removePod(
@@ -267,8 +266,8 @@ func (p *podSeccompRecorder) removePod(
 	}
 	profileRecording.Status.ActiveWorkloads = newActiveWorkloads
 
-	if err := utils.UpdateResource(
-		ctx, p.log, p.client.Status(), profileRecording, "profilerecording status",
+	if err := p.impl.UpdateResource(
+		ctx, p.log, profileRecording, "profilerecording status",
 	); err != nil {
 		return errors.Wrap(err, "update resource on removing pod")
 	}
@@ -278,10 +277,10 @@ func (p *podSeccompRecorder) removePod(
 		controllerutil.RemoveFinalizer(profileRecording, finalizer)
 	}
 
-	return utils.UpdateResource(ctx, p.log, p.client, profileRecording, "profilerecording")
+	return p.impl.UpdateResource(ctx, p.log, profileRecording, "profilerecording")
 }
 
-func (p *podSeccompRecorder) InjectDecoder(d *admission.Decoder) error {
-	p.decoder = d
+func (p *podSeccompRecorder) InjectDecoder(decoder *admission.Decoder) error {
+	p.impl.SetDecoder(decoder)
 	return nil
 }
diff --git a/internal/pkg/webhooks/recording/recordingfakes/fake_impl.go b/internal/pkg/webhooks/recording/recordingfakes/fake_impl.go
new file mode 100644
index 000000000..c70c68066
--- /dev/null
+++ b/internal/pkg/webhooks/recording/recordingfakes/fake_impl.go
@@ -0,0 +1,319 @@
+// Code generated by counterfeiter. DO NOT EDIT.
+package recordingfakes
+
+import (
+	"context"
+	"sync"
+
+	"github.com/go-logr/logr"
+	v1 "k8s.io/api/core/v1"
+	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+	"sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1"
+)
+
+type FakeImpl struct {
+	DecodePodStub        func(admission.Request) (*v1.Pod, error)
+	decodePodMutex       sync.RWMutex
+	decodePodArgsForCall []struct {
+		arg1 admission.Request
+	}
+	decodePodReturns struct {
+		result1 *v1.Pod
+		result2 error
+	}
+	decodePodReturnsOnCall map[int]struct {
+		result1 *v1.Pod
+		result2 error
+	}
+	ListProfileRecordingsStub        func(context.Context, ...client.ListOption) (*v1alpha1.ProfileRecordingList, error)
+	listProfileRecordingsMutex       sync.RWMutex
+	listProfileRecordingsArgsForCall []struct {
+		arg1 context.Context
+		arg2 []client.ListOption
+	}
+	listProfileRecordingsReturns struct {
+		result1 *v1alpha1.ProfileRecordingList
+		result2 error
+	}
+	listProfileRecordingsReturnsOnCall map[int]struct {
+		result1 *v1alpha1.ProfileRecordingList
+		result2 error
+	}
+	SetDecoderStub        func(*admission.Decoder)
+	setDecoderMutex       sync.RWMutex
+	setDecoderArgsForCall []struct {
+		arg1 *admission.Decoder
+	}
+	UpdateResourceStub        func(context.Context, logr.Logger, client.Object, string) error
+	updateResourceMutex       sync.RWMutex
+	updateResourceArgsForCall []struct {
+		arg1 context.Context
+		arg2 logr.Logger
+		arg3 client.Object
+		arg4 string
+	}
+	updateResourceReturns struct {
+		result1 error
+	}
+	updateResourceReturnsOnCall map[int]struct {
+		result1 error
+	}
+	invocations      map[string][][]interface{}
+	invocationsMutex sync.RWMutex
+}
+
+func (fake *FakeImpl) DecodePod(arg1 admission.Request) (*v1.Pod, error) {
+	fake.decodePodMutex.Lock()
+	ret, specificReturn := fake.decodePodReturnsOnCall[len(fake.decodePodArgsForCall)]
+	fake.decodePodArgsForCall = append(fake.decodePodArgsForCall, struct {
+		arg1 admission.Request
+	}{arg1})
+	stub := fake.DecodePodStub
+	fakeReturns := fake.decodePodReturns
+	fake.recordInvocation("DecodePod", []interface{}{arg1})
+	fake.decodePodMutex.Unlock()
+	if stub != nil {
+		return stub(arg1)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeImpl) DecodePodCallCount() int {
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
+	return len(fake.decodePodArgsForCall)
+}
+
+func (fake *FakeImpl) DecodePodCalls(stub func(admission.Request) (*v1.Pod, error)) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = stub
+}
+
+func (fake *FakeImpl) DecodePodArgsForCall(i int) admission.Request {
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
+	argsForCall := fake.decodePodArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *FakeImpl) DecodePodReturns(result1 *v1.Pod, result2 error) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = nil
+	fake.decodePodReturns = struct {
+		result1 *v1.Pod
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) DecodePodReturnsOnCall(i int, result1 *v1.Pod, result2 error) {
+	fake.decodePodMutex.Lock()
+	defer fake.decodePodMutex.Unlock()
+	fake.DecodePodStub = nil
+	if fake.decodePodReturnsOnCall == nil {
+		fake.decodePodReturnsOnCall = make(map[int]struct {
+			result1 *v1.Pod
+			result2 error
+		})
+	}
+	fake.decodePodReturnsOnCall[i] = struct {
+		result1 *v1.Pod
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) ListProfileRecordings(arg1 context.Context, arg2 ...client.ListOption) (*v1alpha1.ProfileRecordingList, error) {
+	fake.listProfileRecordingsMutex.Lock()
+	ret, specificReturn := fake.listProfileRecordingsReturnsOnCall[len(fake.listProfileRecordingsArgsForCall)]
+	fake.listProfileRecordingsArgsForCall = append(fake.listProfileRecordingsArgsForCall, struct {
+		arg1 context.Context
+		arg2 []client.ListOption
+	}{arg1, arg2})
+	stub := fake.ListProfileRecordingsStub
+	fakeReturns := fake.listProfileRecordingsReturns
+	fake.recordInvocation("ListProfileRecordings", []interface{}{arg1, arg2})
+	fake.listProfileRecordingsMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2...)
+	}
+	if specificReturn {
+		return ret.result1, ret.result2
+	}
+	return fakeReturns.result1, fakeReturns.result2
+}
+
+func (fake *FakeImpl) ListProfileRecordingsCallCount() int {
+	fake.listProfileRecordingsMutex.RLock()
+	defer fake.listProfileRecordingsMutex.RUnlock()
+	return len(fake.listProfileRecordingsArgsForCall)
+}
+
+func (fake *FakeImpl) ListProfileRecordingsCalls(stub func(context.Context, ...client.ListOption) (*v1alpha1.ProfileRecordingList, error)) {
+	fake.listProfileRecordingsMutex.Lock()
+	defer fake.listProfileRecordingsMutex.Unlock()
+	fake.ListProfileRecordingsStub = stub
+}
+
+func (fake *FakeImpl) ListProfileRecordingsArgsForCall(i int) (context.Context, []client.ListOption) {
+	fake.listProfileRecordingsMutex.RLock()
+	defer fake.listProfileRecordingsMutex.RUnlock()
+	argsForCall := fake.listProfileRecordingsArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2
+}
+
+func (fake *FakeImpl) ListProfileRecordingsReturns(result1 *v1alpha1.ProfileRecordingList, result2 error) {
+	fake.listProfileRecordingsMutex.Lock()
+	defer fake.listProfileRecordingsMutex.Unlock()
+	fake.ListProfileRecordingsStub = nil
+	fake.listProfileRecordingsReturns = struct {
+		result1 *v1alpha1.ProfileRecordingList
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) ListProfileRecordingsReturnsOnCall(i int, result1 *v1alpha1.ProfileRecordingList, result2 error) {
+	fake.listProfileRecordingsMutex.Lock()
+	defer fake.listProfileRecordingsMutex.Unlock()
+	fake.ListProfileRecordingsStub = nil
+	if fake.listProfileRecordingsReturnsOnCall == nil {
+		fake.listProfileRecordingsReturnsOnCall = make(map[int]struct {
+			result1 *v1alpha1.ProfileRecordingList
+			result2 error
+		})
+	}
+	fake.listProfileRecordingsReturnsOnCall[i] = struct {
+		result1 *v1alpha1.ProfileRecordingList
+		result2 error
+	}{result1, result2}
+}
+
+func (fake *FakeImpl) SetDecoder(arg1 *admission.Decoder) {
+	fake.setDecoderMutex.Lock()
+	fake.setDecoderArgsForCall = append(fake.setDecoderArgsForCall, struct {
+		arg1 *admission.Decoder
+	}{arg1})
+	stub := fake.SetDecoderStub
+	fake.recordInvocation("SetDecoder", []interface{}{arg1})
+	fake.setDecoderMutex.Unlock()
+	if stub != nil {
+		fake.SetDecoderStub(arg1)
+	}
+}
+
+func (fake *FakeImpl) SetDecoderCallCount() int {
+	fake.setDecoderMutex.RLock()
+	defer fake.setDecoderMutex.RUnlock()
+	return len(fake.setDecoderArgsForCall)
+}
+
+func (fake *FakeImpl) SetDecoderCalls(stub func(*admission.Decoder)) {
+	fake.setDecoderMutex.Lock()
+	defer fake.setDecoderMutex.Unlock()
+	fake.SetDecoderStub = stub
+}
+
+func (fake *FakeImpl) SetDecoderArgsForCall(i int) *admission.Decoder {
+	fake.setDecoderMutex.RLock()
+	defer fake.setDecoderMutex.RUnlock()
+	argsForCall := fake.setDecoderArgsForCall[i]
+	return argsForCall.arg1
+}
+
+func (fake *FakeImpl) UpdateResource(arg1 context.Context, arg2 logr.Logger, arg3 client.Object, arg4 string) error {
+	fake.updateResourceMutex.Lock()
+	ret, specificReturn := fake.updateResourceReturnsOnCall[len(fake.updateResourceArgsForCall)]
+	fake.updateResourceArgsForCall = append(fake.updateResourceArgsForCall, struct {
+		arg1 context.Context
+		arg2 logr.Logger
+		arg3 client.Object
+		arg4 string
+	}{arg1, arg2, arg3, arg4})
+	stub := fake.UpdateResourceStub
+	fakeReturns := fake.updateResourceReturns
+	fake.recordInvocation("UpdateResource", []interface{}{arg1, arg2, arg3, arg4})
+	fake.updateResourceMutex.Unlock()
+	if stub != nil {
+		return stub(arg1, arg2, arg3, arg4)
+	}
+	if specificReturn {
+		return ret.result1
+	}
+	return fakeReturns.result1
+}
+
+func (fake *FakeImpl) UpdateResourceCallCount() int {
+	fake.updateResourceMutex.RLock()
+	defer fake.updateResourceMutex.RUnlock()
+	return len(fake.updateResourceArgsForCall)
+}
+
+func (fake *FakeImpl) UpdateResourceCalls(stub func(context.Context, logr.Logger, client.Object, string) error) {
+	fake.updateResourceMutex.Lock()
+	defer fake.updateResourceMutex.Unlock()
+	fake.UpdateResourceStub = stub
+}
+
+func (fake *FakeImpl) UpdateResourceArgsForCall(i int) (context.Context, logr.Logger, client.Object, string) {
+	fake.updateResourceMutex.RLock()
+	defer fake.updateResourceMutex.RUnlock()
+	argsForCall := fake.updateResourceArgsForCall[i]
+	return argsForCall.arg1, argsForCall.arg2, argsForCall.arg3, argsForCall.arg4
+}
+
+func (fake *FakeImpl) UpdateResourceReturns(result1 error) {
+	fake.updateResourceMutex.Lock()
+	defer fake.updateResourceMutex.Unlock()
+	fake.UpdateResourceStub = nil
+	fake.updateResourceReturns = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) UpdateResourceReturnsOnCall(i int, result1 error) {
+	fake.updateResourceMutex.Lock()
+	defer fake.updateResourceMutex.Unlock()
+	fake.UpdateResourceStub = nil
+	if fake.updateResourceReturnsOnCall == nil {
+		fake.updateResourceReturnsOnCall = make(map[int]struct {
+			result1 error
+		})
+	}
+	fake.updateResourceReturnsOnCall[i] = struct {
+		result1 error
+	}{result1}
+}
+
+func (fake *FakeImpl) Invocations() map[string][][]interface{} {
+	fake.invocationsMutex.RLock()
+	defer fake.invocationsMutex.RUnlock()
+	fake.decodePodMutex.RLock()
+	defer fake.decodePodMutex.RUnlock()
+	fake.listProfileRecordingsMutex.RLock()
+	defer fake.listProfileRecordingsMutex.RUnlock()
+	fake.setDecoderMutex.RLock()
+	defer fake.setDecoderMutex.RUnlock()
+	fake.updateResourceMutex.RLock()
+	defer fake.updateResourceMutex.RUnlock()
+	copiedInvocations := map[string][][]interface{}{}
+	for key, value := range fake.invocations {
+		copiedInvocations[key] = value
+	}
+	return copiedInvocations
+}
+
+func (fake *FakeImpl) recordInvocation(key string, args []interface{}) {
+	fake.invocationsMutex.Lock()
+	defer fake.invocationsMutex.Unlock()
+	if fake.invocations == nil {
+		fake.invocations = map[string][][]interface{}{}
+	}
+	if fake.invocations[key] == nil {
+		fake.invocations[key] = [][]interface{}{}
+	}
+	fake.invocations[key] = append(fake.invocations[key], args)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 2534ea978..10e25dceb 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -221,7 +221,7 @@ gopkg.in/inf.v0
 gopkg.in/yaml.v2
 # gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
 gopkg.in/yaml.v3
-# k8s.io/api v0.20.4
+# k8s.io/api v0.20.5
 ## explicit
 k8s.io/api/admission/v1
 k8s.io/api/admission/v1beta1
@@ -272,7 +272,7 @@ k8s.io/api/storage/v1beta1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1
 k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1
-# k8s.io/apimachinery v0.20.4
+# k8s.io/apimachinery v0.20.5
 ## explicit
 k8s.io/apimachinery/pkg/api/equality
 k8s.io/apimachinery/pkg/api/errors


Test patch:
diff --git a/internal/pkg/webhooks/recording/recording_test.go b/internal/pkg/webhooks/recording/recording_test.go
new file mode 100644
index 000000000..1a35d845f
--- /dev/null
+++ b/internal/pkg/webhooks/recording/recording_test.go
@@ -0,0 +1,81 @@
+/*
+Copyright 2021 The Kubernetes Authors.
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+*/
+
+package recording
+
+import (
+	"context"
+	"errors"
+	"net/http"
+	"testing"
+
+	"github.com/go-logr/logr"
+	"github.com/stretchr/testify/require"
+	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
+
+	"sigs.k8s.io/security-profiles-operator/api/profilerecording/v1alpha1"
+	"sigs.k8s.io/security-profiles-operator/internal/pkg/webhooks/recording/recordingfakes"
+)
+
+var errTest = errors.New("error")
+
+func TestHandle(t *testing.T) {
+	t.Parallel()
+
+	for _, tc := range []struct {
+		prepare func(*recordingfakes.FakeImpl)
+		request admission.Request
+		assert  func(admission.Response)
+	}{
+		{ // success pod unchanged
+			prepare: func(mock *recordingfakes.FakeImpl) {
+				mock.ListProfileRecordingsReturns(&v1alpha1.ProfileRecordingList{}, nil)
+				mock.DecodePodReturns(&corev1.Pod{}, nil)
+			},
+			assert: func(resp admission.Response) {
+				require.True(t, resp.Allowed)
+				require.Equal(t, http.StatusOK, int(resp.Result.Code))
+				require.Equal(t, metav1.StatusReason("pod unchanged"), resp.Result.Reason)
+			},
+		},
+		{ // error could not list profile recordings
+			prepare: func(mock *recordingfakes.FakeImpl) {
+				mock.ListProfileRecordingsReturns(nil, errTest)
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusInternalServerError, int(resp.Result.Code))
+			},
+		},
+		{ // error failed to decode pod
+			prepare: func(mock *recordingfakes.FakeImpl) {
+				mock.ListProfileRecordingsReturns(&v1alpha1.ProfileRecordingList{}, nil)
+				mock.DecodePodReturns(nil, errTest)
+			},
+			assert: func(resp admission.Response) {
+				require.Equal(t, http.StatusBadRequest, int(resp.Result.Code))
+			},
+		},
+	} {
+		mock := &recordingfakes.FakeImpl{}
+		tc.prepare(mock)
+
+		recorder := podSeccompRecorder{impl: mock, log: logr.Discard()}
+		resp := recorder.Handle(context.Background(), admission.Request{})
+		tc.assert(resp)
+	}
+}