From 32b9ac7d0cbc2b598d7a72146baa4c4037601c25 Mon Sep 17 00:00:00 2001 From: Mrunal Patel Date: Thu, 5 Nov 2020 13:58:59 -0800 Subject: [PATCH] kubelet: Use CRI SecurityProfile for Seccomp We set both the old and the new fields for now and will remove the old field in the next release. Signed-off-by: Mrunal Patel --- pkg/kubelet/kuberuntime/helpers.go | 44 ++++++++- pkg/kubelet/kuberuntime/helpers_test.go | 96 ++++++++++++++++++- .../kuberuntime/kuberuntime_sandbox.go | 5 + pkg/kubelet/kuberuntime/security_context.go | 5 +- 4 files changed, 147 insertions(+), 3 deletions(-) diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go index cdf4121a38c..6db2698cb59 100644 --- a/pkg/kubelet/kuberuntime/helpers.go +++ b/pkg/kubelet/kuberuntime/helpers.go @@ -228,7 +228,7 @@ func annotationProfile(profile, profileRootPath string) string { return profile } -func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]string, containerName string, +func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string]string, containerName string, podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext) string { // container fields are applied first if containerSecContext != nil && containerSecContext.SeccompProfile != nil { @@ -255,6 +255,48 @@ func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]str return "" } +func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string) *runtimeapi.SecurityProfile { + // TODO: Move to RuntimeDefault as the default instead of Unconfined after discussion + // with sig-node. + if scmp == nil { + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, + } + } + if scmp.Type == v1.SeccompProfileTypeRuntimeDefault { + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, + } + } + if scmp.Type == v1.SeccompProfileTypeLocalhost && scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 { + fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile) + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Localhost, + LocalhostRef: fname, + } + } + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, + } +} + +func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]string, containerName string, + podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext) *runtimeapi.SecurityProfile { + // container fields are applied first + if containerSecContext != nil && containerSecContext.SeccompProfile != nil { + return fieldSeccompProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot) + } + + // when container seccomp is not defined, try to apply from pod field + if podSecContext != nil && podSecContext.SeccompProfile != nil { + return fieldSeccompProfile(podSecContext.SeccompProfile, m.seccompProfileRoot) + } + + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, + } +} + func ipcNamespaceForPod(pod *v1.Pod) runtimeapi.NamespaceMode { if pod != nil && pod.Spec.HostIPC { return runtimeapi.NamespaceMode_NODE diff --git a/pkg/kubelet/kuberuntime/helpers_test.go b/pkg/kubelet/kuberuntime/helpers_test.go index 921fc04a948..1628cb3cf24 100644 --- a/pkg/kubelet/kuberuntime/helpers_test.go +++ b/pkg/kubelet/kuberuntime/helpers_test.go @@ -226,7 +226,7 @@ func TestFieldProfile(t *testing.T) { } } -func TestGetSeccompProfile(t *testing.T) { +func TestGetSeccompProfilePath(t *testing.T) { _, _, m, err := createTestRuntimeManager() require.NoError(t, err) @@ -410,6 +410,100 @@ func TestGetSeccompProfile(t *testing.T) { }, } + for i, test := range tests { + seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc) + assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) + } +} + +func TestGetSeccompProfile(t *testing.T) { + _, _, m, err := createTestRuntimeManager() + require.NoError(t, err) + + unconfinedProfile := &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, + } + + runtimeDefaultProfile := &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, + } + + tests := []struct { + description string + annotation map[string]string + podSc *v1.PodSecurityContext + containerSc *v1.SecurityContext + containerName string + expectedProfile *runtimeapi.SecurityProfile + }{ + { + description: "no seccomp should return unconfined", + expectedProfile: unconfinedProfile, + }, + { + description: "pod seccomp profile set to unconfined returns unconfined", + podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}}, + expectedProfile: unconfinedProfile, + }, + { + description: "container seccomp profile set to unconfined returns unconfined", + containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}}, + expectedProfile: unconfinedProfile, + }, + { + description: "pod seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default", + podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}}, + expectedProfile: runtimeDefaultProfile, + }, + { + description: "container seccomp profile set to SeccompProfileTypeRuntimeDefault returns runtime/default", + containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeRuntimeDefault}}, + expectedProfile: runtimeDefaultProfile, + }, + { + description: "pod seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", + podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename")}}, + expectedProfile: &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Localhost, + LocalhostRef: filepath.Join(fakeSeccompProfileRoot, "filename"), + }, + }, + { + description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined", + podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, + expectedProfile: unconfinedProfile, + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined", + containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, + expectedProfile: unconfinedProfile, + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", + containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost, LocalhostProfile: getLocal("filename2")}}, + expectedProfile: &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Localhost, + LocalhostRef: 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: runtimeDefaultProfile, + }, + { + description: "prioritise container field over pod field", + 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")}}, + containerName: "container1", + expectedProfile: &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Localhost, + LocalhostRef: filepath.Join(fakeSeccompProfileRoot, "field-cont-profile.json"), + }, + }, + } + for i, test := range tests { seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc) assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) diff --git a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go index ce5ac7c74ea..d590c4b0562 100644 --- a/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go +++ b/pkg/kubelet/kuberuntime/kuberuntime_sandbox.go @@ -154,9 +154,14 @@ func (m *kubeGenericRuntimeManager) generatePodSandboxLinuxConfig(pod *v1.Pod) ( SecurityContext: &runtimeapi.LinuxSandboxSecurityContext{ Privileged: kubecontainer.HasPrivilegedContainer(pod), + // TODO: Deprecated, remove after we switch to Seccomp field // Forcing sandbox to run as `runtime/default` allow users to // use least privileged seccomp profiles at pod level. Issue #84623 SeccompProfilePath: v1.SeccompProfileRuntimeDefault, + + Seccomp: &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, + }, }, } diff --git a/pkg/kubelet/kuberuntime/security_context.go b/pkg/kubelet/kuberuntime/security_context.go index 0401125d092..45f0d599541 100644 --- a/pkg/kubelet/kuberuntime/security_context.go +++ b/pkg/kubelet/kuberuntime/security_context.go @@ -34,8 +34,11 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po } } + // TODO: Deprecated, remove after we switch to Seccomp field // set SeccompProfilePath. - synthesized.SeccompProfilePath = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext) + synthesized.SeccompProfilePath = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext) + + synthesized.Seccomp = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext) // set ApparmorProfile. synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)