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:
@@ -24,6 +24,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
asserttestify "github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@@ -7287,6 +7289,107 @@ func TestValidatePod(t *testing.T) {
|
||||
},
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
{ // runtime default seccomp profile for a pod
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // runtime default seccomp profile for a container
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
{ // unconfined seccomp profile for a pod
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // unconfined seccomp profile for a container
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
{ // localhost seccomp profile for a pod
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: utilpointer.StringPtr("filename.json"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{ // localhost seccomp profile for a container
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: utilpointer.StringPtr("filename.json"),
|
||||
},
|
||||
},
|
||||
}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
{ // default AppArmor profile for a container
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
@@ -7983,6 +8086,50 @@ func TestValidatePod(t *testing.T) {
|
||||
Spec: validPodSpec(nil),
|
||||
},
|
||||
},
|
||||
"must match seccomp profile type and pod annotation": {
|
||||
expectedError: "seccomp type in annotation and field must match",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SeccompPodAnnotationKey: "unconfined",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
},
|
||||
"must match seccomp profile type and container annotation": {
|
||||
expectedError: "seccomp type in annotation and field must match",
|
||||
spec: core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "123",
|
||||
Namespace: "ns",
|
||||
Annotations: map[string]string{
|
||||
core.SeccompContainerAnnotationKeyPrefix + "ctr": "unconfined",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{Name: "ctr", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
}}},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSClusterFirst,
|
||||
},
|
||||
},
|
||||
},
|
||||
"must be a relative path in a node-local seccomp profile annotation": {
|
||||
expectedError: "must be a relative path",
|
||||
spec: core.Pod{
|
||||
@@ -15200,3 +15347,456 @@ func TestValidateNodeCIDRs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSeccompAnnotationAndField(t *testing.T) {
|
||||
const containerName = "container"
|
||||
testProfile := "test"
|
||||
|
||||
for _, test := range []struct {
|
||||
description string
|
||||
pod *core.Pod
|
||||
validation func(*testing.T, string, field.ErrorList, *v1.Pod)
|
||||
}{
|
||||
{
|
||||
description: "Field type unconfined and annotation does not match",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type default and annotation does not match",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type localhost and annotation does not match",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: &testProfile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type localhost and localhost/ prefixed annotation does not match",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: &testProfile,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type unconfined and annotation does not match (container)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{
|
||||
Name: containerName,
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type default and annotation does not match (container)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{
|
||||
Name: containerName,
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type localhost and annotation does not match (container)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + containerName: "not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{
|
||||
Name: containerName,
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: &testProfile,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Field type localhost and localhost/ prefixed annotation does not match (container)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{
|
||||
Name: containerName,
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeLocalhost,
|
||||
LocalhostProfile: &testProfile,
|
||||
},
|
||||
},
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.NotNil(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Nil errors must not be appended (pod)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompPodAnnotationKey: "localhost/anyprofile",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: "Abc",
|
||||
},
|
||||
},
|
||||
Containers: []core.Container{{
|
||||
Name: containerName,
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.Empty(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "Nil errors must not be appended (container)",
|
||||
pod: &core.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
v1.SeccompContainerAnnotationKeyPrefix + containerName: "localhost/not-matching",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{{
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: "Abc",
|
||||
},
|
||||
},
|
||||
Name: containerName,
|
||||
}},
|
||||
},
|
||||
},
|
||||
validation: func(t *testing.T, desc string, allErrs field.ErrorList, pod *v1.Pod) {
|
||||
require.Empty(t, allErrs, desc)
|
||||
},
|
||||
},
|
||||
} {
|
||||
output := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{Annotations: map[string]string{}},
|
||||
}
|
||||
for i, ctr := range test.pod.Spec.Containers {
|
||||
output.Spec.Containers = append(output.Spec.Containers, v1.Container{})
|
||||
if ctr.SecurityContext != nil && ctr.SecurityContext.SeccompProfile != nil {
|
||||
output.Spec.Containers[i].SecurityContext = &v1.SecurityContext{
|
||||
SeccompProfile: &v1.SeccompProfile{
|
||||
Type: v1.SeccompProfileType(ctr.SecurityContext.SeccompProfile.Type),
|
||||
LocalhostProfile: ctr.SecurityContext.SeccompProfile.LocalhostProfile,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
errList := validateSeccompAnnotationsAndFields(test.pod.ObjectMeta, &test.pod.Spec, field.NewPath(""))
|
||||
test.validation(t, test.description, errList, output)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSeccompAnnotationsAndFieldsMatch(t *testing.T) {
|
||||
rootFld := field.NewPath("")
|
||||
tests := []struct {
|
||||
description string
|
||||
annotationValue string
|
||||
seccompField *core.SeccompProfile
|
||||
fldPath *field.Path
|
||||
expectedErr *field.Error
|
||||
}{
|
||||
{
|
||||
description: "seccompField nil should return empty",
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
description: "unconfined annotation and SeccompProfileTypeUnconfined should return empty",
|
||||
annotationValue: "unconfined",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
description: "runtime/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
|
||||
annotationValue: "runtime/default",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
description: "docker/default annotation and SeccompProfileTypeRuntimeDefault should return empty",
|
||||
annotationValue: "docker/default",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with correct profile should return empty",
|
||||
annotationValue: "localhost/test.json",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.StringPtr("test.json")},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
description: "localhost/test.json annotation and SeccompProfileTypeLocalhost without profile should error",
|
||||
annotationValue: "localhost/test.json",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost},
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
|
||||
},
|
||||
{
|
||||
description: "localhost/test.json annotation and SeccompProfileTypeLocalhost with different profile should error",
|
||||
annotationValue: "localhost/test.json",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeLocalhost, LocalhostProfile: utilpointer.StringPtr("different.json")},
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.Forbidden(rootFld.Child("localhostProfile"), "seccomp profile in annotation and field must match"),
|
||||
},
|
||||
{
|
||||
description: "localhost/test.json annotation and SeccompProfileTypeUnconfined with different profile should error",
|
||||
annotationValue: "localhost/test.json",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeUnconfined},
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
|
||||
},
|
||||
{
|
||||
description: "localhost/test.json annotation and SeccompProfileTypeRuntimeDefault with different profile should error",
|
||||
annotationValue: "localhost/test.json",
|
||||
seccompField: &core.SeccompProfile{Type: core.SeccompProfileTypeRuntimeDefault},
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.Forbidden(rootFld.Child("type"), "seccomp type in annotation and field must match"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
err := validateSeccompAnnotationsAndFieldsMatch(test.annotationValue, test.seccompField, test.fldPath)
|
||||
asserttestify.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePodTemplateSpecSeccomp(t *testing.T) {
|
||||
rootFld := field.NewPath("template")
|
||||
tests := []struct {
|
||||
description string
|
||||
spec *core.PodTemplateSpec
|
||||
fldPath *field.Path
|
||||
expectedErr field.ErrorList
|
||||
}{
|
||||
{
|
||||
description: "seccomp field and container annotation must match",
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.ErrorList{
|
||||
field.Forbidden(
|
||||
rootFld.Child("spec").Child("containers").Index(1).Child("securityContext").Child("seccompProfile").Child("type"),
|
||||
"seccomp type in annotation and field must match"),
|
||||
},
|
||||
spec: &core.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"container.seccomp.security.alpha.kubernetes.io/test2": "unconfined",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{
|
||||
Name: "test1",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
|
||||
},
|
||||
{
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
Name: "test2",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
|
||||
},
|
||||
},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "seccomp field and pod annotation must match",
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.ErrorList{
|
||||
field.Forbidden(
|
||||
rootFld.Child("spec").Child("securityContext").Child("seccompProfile").Child("type"),
|
||||
"seccomp type in annotation and field must match"),
|
||||
},
|
||||
spec: &core.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"seccomp.security.alpha.kubernetes.io/pod": "runtime/default",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
SecurityContext: &core.PodSecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeUnconfined,
|
||||
},
|
||||
},
|
||||
Containers: []core.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
|
||||
},
|
||||
},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
description: "init seccomp field and container annotation must match",
|
||||
fldPath: rootFld,
|
||||
expectedErr: field.ErrorList{
|
||||
field.Forbidden(
|
||||
rootFld.Child("spec").Child("initContainers").Index(0).Child("securityContext").Child("seccompProfile").Child("type"),
|
||||
"seccomp type in annotation and field must match"),
|
||||
},
|
||||
spec: &core.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"container.seccomp.security.alpha.kubernetes.io/init-test": "unconfined",
|
||||
},
|
||||
},
|
||||
Spec: core.PodSpec{
|
||||
Containers: []core.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
|
||||
},
|
||||
},
|
||||
InitContainers: []core.Container{
|
||||
{
|
||||
Name: "init-test",
|
||||
SecurityContext: &core.SecurityContext{
|
||||
SeccompProfile: &core.SeccompProfile{
|
||||
Type: core.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
},
|
||||
Image: "alpine",
|
||||
ImagePullPolicy: core.PullAlways,
|
||||
TerminationMessagePolicy: core.TerminationMessageFallbackToLogsOnError,
|
||||
},
|
||||
},
|
||||
RestartPolicy: core.RestartPolicyAlways,
|
||||
DNSPolicy: core.DNSDefault,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
err := ValidatePodTemplateSpec(test.spec, rootFld)
|
||||
asserttestify.Equal(t, test.expectedErr, err, "TestCase[%d]: %s", i, test.description)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user