Add seccomp enforcement and validation based on new GA fields
Adds seccomp validation. This ensures that field and annotation values must match when present. Co-authored-by: Sascha Grunert <sgrunert@suse.com>
This commit is contained in:
@@ -23,11 +23,12 @@ import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
|
||||
runtimetesting "k8s.io/cri-api/pkg/apis/testing"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
utilpointer "k8s.io/utils/pointer"
|
||||
)
|
||||
|
||||
func TestStableKey(t *testing.T) {
|
||||
@@ -169,79 +170,137 @@ func TestGetImageUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSeccompProfileFromAnnotations(t *testing.T) {
|
||||
func TestFieldProfile(t *testing.T) {
|
||||
tests := []struct {
|
||||
description string
|
||||
scmpProfile *v1.SeccompProfile
|
||||
rootPath string
|
||||
expectedProfile string
|
||||
}{
|
||||
{
|
||||
description: "no seccompProfile should return empty",
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "type localhost without profile should return empty",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
},
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "unknown type should return empty",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: "",
|
||||
},
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "SeccompProfileTypeRuntimeDefault should return runtime/default",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "SeccompProfileTypeUnconfined should return unconfined",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
expectedProfile: "unconfined",
|
||||
},
|
||||
{
|
||||
description: "SeccompProfileTypeLocalhost should return unconfined",
|
||||
scmpProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: utilpointer.StringPtr("profile.json"),
|
||||
},
|
||||
rootPath: "/test/",
|
||||
expectedProfile: "localhost//test/profile.json",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
seccompProfile := fieldProfile(test.scmpProfile, test.rootPath)
|
||||
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSeccompProfile(t *testing.T) {
|
||||
_, _, m, err := createTestRuntimeManager()
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
description string
|
||||
annotation map[string]string
|
||||
podSc *v1.PodSecurityContext
|
||||
containerSc *v1.SecurityContext
|
||||
containerName string
|
||||
expectedProfile string
|
||||
}{
|
||||
{
|
||||
description: "no seccomp should return empty string",
|
||||
description: "no seccomp should return empty",
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "no seccomp with containerName should return exmpty string",
|
||||
description: "annotations: no seccomp with containerName should return empty",
|
||||
containerName: "container1",
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "pod runtime/default seccomp profile should return runtime/default",
|
||||
description: "annotations: pod runtime/default seccomp profile should return runtime/default",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault,
|
||||
},
|
||||
expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "pod docker/default seccomp profile should return docker/default",
|
||||
description: "annotations: pod docker/default seccomp profile should return docker/default",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault,
|
||||
},
|
||||
expectedProfile: v1.DeprecatedSeccompProfileDockerDefault,
|
||||
expectedProfile: "docker/default",
|
||||
},
|
||||
{
|
||||
description: "pod runtime/default seccomp profile with containerName should return runtime/default",
|
||||
description: "annotations: pod runtime/default seccomp profile with containerName should return runtime/default",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault,
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: v1.SeccompProfileRuntimeDefault,
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "pod docker/default seccomp profile with containerName should return docker/default",
|
||||
description: "annotations: pod docker/default seccomp profile with containerName should return docker/default",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault,
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: v1.DeprecatedSeccompProfileDockerDefault,
|
||||
expectedProfile: "docker/default",
|
||||
},
|
||||
{
|
||||
description: "pod unconfined seccomp profile should return unconfined",
|
||||
description: "annotations: pod unconfined seccomp profile should return unconfined",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "unconfined",
|
||||
v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined,
|
||||
},
|
||||
expectedProfile: "unconfined",
|
||||
},
|
||||
{
|
||||
description: "pod unconfined seccomp profile with containerName should return unconfined",
|
||||
description: "annotations: pod unconfined seccomp profile with containerName should return unconfined",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "unconfined",
|
||||
v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined,
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: "unconfined",
|
||||
},
|
||||
{
|
||||
description: "pod localhost seccomp profile should return local profile path",
|
||||
description: "annotations: pod localhost seccomp profile should return local profile path",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/chmod.json",
|
||||
},
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"),
|
||||
},
|
||||
{
|
||||
description: "pod localhost seccomp profile with containerName should return local profile path",
|
||||
description: "annotations: pod localhost seccomp profile with containerName should return local profile path",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/chmod.json",
|
||||
},
|
||||
@@ -249,7 +308,7 @@ func TestGetSeccompProfileFromAnnotations(t *testing.T) {
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"),
|
||||
},
|
||||
{
|
||||
description: "container localhost seccomp profile with containerName should return local profile path",
|
||||
description: "annotations: container localhost seccomp profile with containerName should return local profile path",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json",
|
||||
},
|
||||
@@ -257,30 +316,110 @@ func TestGetSeccompProfileFromAnnotations(t *testing.T) {
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"),
|
||||
},
|
||||
{
|
||||
description: "container localhost seccomp profile should override pod profile",
|
||||
description: "annotations: container localhost seccomp profile should override pod profile",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "unconfined",
|
||||
v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined,
|
||||
v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json",
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "chmod.json"),
|
||||
},
|
||||
{
|
||||
description: "container localhost seccomp profile with unmatched containerName should return empty string",
|
||||
description: "annotations: container localhost seccomp profile with unmatched containerName should return empty",
|
||||
annotation: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json",
|
||||
},
|
||||
containerName: "container2",
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "pod seccomp profile set to unconfined returns unconfined",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
|
||||
expectedProfile: "unconfined",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to unconfined returns unconfined",
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
|
||||
expectedProfile: "unconfined",
|
||||
},
|
||||
{
|
||||
description: "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default",
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}},
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "filename"),
|
||||
},
|
||||
{
|
||||
description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns empty",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns empty",
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
|
||||
expectedProfile: "",
|
||||
},
|
||||
{
|
||||
description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile",
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}},
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "filename2"),
|
||||
},
|
||||
{
|
||||
description: "prioritise container field over pod field",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}},
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}},
|
||||
expectedProfile: "runtime/default",
|
||||
},
|
||||
{
|
||||
description: "prioritise container field over container annotation, pod field and pod annotation",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
|
||||
containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-cont-profile.json")}},
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/annota-pod-profile.json",
|
||||
v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/annota-cont-profile.json",
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "field-cont-profile.json"),
|
||||
},
|
||||
{
|
||||
description: "prioritise container annotation over pod field",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/annota-pod-profile.json",
|
||||
v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/annota-cont-profile.json",
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "annota-cont-profile.json"),
|
||||
},
|
||||
{
|
||||
description: "prioritise pod field over pod annotation",
|
||||
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("field-pod-profile.json")}},
|
||||
annotation: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/annota-pod-profile.json",
|
||||
},
|
||||
containerName: "container1",
|
||||
expectedProfile: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "field-pod-profile.json"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
seccompProfile := m.getSeccompProfileFromAnnotations(test.annotation, test.containerName)
|
||||
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]", i)
|
||||
seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc)
|
||||
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
|
||||
}
|
||||
}
|
||||
|
||||
func getLocal(v string) *string {
|
||||
return &v
|
||||
}
|
||||
|
||||
func TestNamespacesForPod(t *testing.T) {
|
||||
for desc, test := range map[string]struct {
|
||||
input *v1.Pod
|
||||
|
Reference in New Issue
Block a user