From: Soumya Sambu <soumya.sa...@windriver.com> A security issue was discovered in Kubelet that allows pods to bypass the seccomp profile enforcement. Pods that use localhost type for seccomp profile but specify an empty profile field, are affected by this issue. In this scenario, this vulnerability allows the pod to run in unconfined (seccomp disabled) mode. This bug affects Kubelet.
References: https://nvd.nist.gov/vuln/detail/CVE-2023-2431 Signed-off-by: Soumya Sambu <soumya.sa...@windriver.com> --- .../kubernetes/kubernetes/CVE-2023-2431.patch | 863 ++++++++++++++++++ .../kubernetes/kubernetes_git.bb | 1 + 2 files changed, 864 insertions(+) create mode 100644 recipes-containers/kubernetes/kubernetes/CVE-2023-2431.patch diff --git a/recipes-containers/kubernetes/kubernetes/CVE-2023-2431.patch b/recipes-containers/kubernetes/kubernetes/CVE-2023-2431.patch new file mode 100644 index 00000000..56c3a6e1 --- /dev/null +++ b/recipes-containers/kubernetes/kubernetes/CVE-2023-2431.patch @@ -0,0 +1,863 @@ +From 73174f870735251e7d4240cdc36983d1bef7db5f Mon Sep 17 00:00:00 2001 +From: Craig Ingram <cjing...@google.com> +Date: Fri, 24 Feb 2023 15:24:49 -0500 +Subject: [PATCH] Return error for localhost seccomp type with no localhost + profile defined + +CVE: CVE-2023-2431 + +Upstream-Status: Backport [https://github.com/kubernetes/kubernetes/commit/73174f870735251e7d4240cdc36983d1bef7db5f] + +Signed-off-by: Soumya Sambu <soumya.sa...@windriver.com> +--- + pkg/kubelet/kuberuntime/helpers.go | 66 ++-- + pkg/kubelet/kuberuntime/helpers_test.go | 350 ++++-------------- + .../kuberuntime_container_linux.go | 16 +- + .../kuberuntime_container_linux_test.go | 22 +- + pkg/kubelet/kuberuntime/security_context.go | 15 +- + 5 files changed, 153 insertions(+), 316 deletions(-) + +diff --git a/pkg/kubelet/kuberuntime/helpers.go b/pkg/kubelet/kuberuntime/helpers.go +index fa580335cf8..b36e01166f8 100644 +--- a/pkg/kubelet/kuberuntime/helpers.go ++++ b/pkg/kubelet/kuberuntime/helpers.go +@@ -209,28 +209,32 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim + return &kubecontainer.RuntimeStatus{Conditions: conditions} + } + +-func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) string { ++func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) (string, error) { + if scmp == nil { + if fallbackToRuntimeDefault { +- return v1.SeccompProfileRuntimeDefault ++ return v1.SeccompProfileRuntimeDefault, nil + } +- return "" ++ return "", nil + } + if scmp.Type == v1.SeccompProfileTypeRuntimeDefault { +- return v1.SeccompProfileRuntimeDefault +- } +- if scmp.Type == v1.SeccompProfileTypeLocalhost && scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 { +- fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile) +- return v1.SeccompLocalhostProfileNamePrefix + fname ++ return v1.SeccompProfileRuntimeDefault, nil ++ } ++ if scmp.Type == v1.SeccompProfileTypeLocalhost { ++ if scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 { ++ fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile) ++ return v1.SeccompLocalhostProfileNamePrefix + fname, nil ++ } else { ++ return "", fmt.Errorf("localhostProfile must be set if seccompProfile type is Localhost.") ++ } + } + if scmp.Type == v1.SeccompProfileTypeUnconfined { +- return v1.SeccompProfileNameUnconfined ++ return v1.SeccompProfileNameUnconfined, nil + } + + if fallbackToRuntimeDefault { +- return v1.SeccompProfileRuntimeDefault ++ return v1.SeccompProfileRuntimeDefault, nil + } +- return "" ++ return "", nil + } + + func annotationProfile(profile, profileRootPath string) string { +@@ -243,7 +247,7 @@ func annotationProfile(profile, profileRootPath string) string { + } + + func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string]string, containerName string, +- podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) string { ++ podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) (string, error) { + // container fields are applied first + if containerSecContext != nil && containerSecContext.SeccompProfile != nil { + return fieldProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault) +@@ -252,7 +256,7 @@ func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string + // if container field does not exist, try container annotation (deprecated) + if containerName != "" { + if profile, ok := annotations[v1.SeccompContainerAnnotationKeyPrefix+containerName]; ok { +- return annotationProfile(profile, m.seccompProfileRoot) ++ return annotationProfile(profile, m.seccompProfileRoot), nil + } + } + +@@ -263,46 +267,50 @@ func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string + + // as last resort, try to apply pod annotation (deprecated) + if profile, ok := annotations[v1.SeccompPodAnnotationKey]; ok { +- return annotationProfile(profile, m.seccompProfileRoot) ++ return annotationProfile(profile, m.seccompProfileRoot), nil + } + + if fallbackToRuntimeDefault { +- return v1.SeccompProfileRuntimeDefault ++ return v1.SeccompProfileRuntimeDefault, nil + } + +- return "" ++ return "", nil + } + +-func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile { ++func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) (*runtimeapi.SecurityProfile, error) { + if scmp == nil { + if fallbackToRuntimeDefault { + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, +- } ++ }, nil + } + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, +- } ++ }, nil + } + if scmp.Type == v1.SeccompProfileTypeRuntimeDefault { + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, +- } ++ }, nil + } +- 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, ++ if scmp.Type == v1.SeccompProfileTypeLocalhost { ++ if scmp.LocalhostProfile != nil && len(*scmp.LocalhostProfile) > 0 { ++ fname := filepath.Join(profileRootPath, *scmp.LocalhostProfile) ++ return &runtimeapi.SecurityProfile{ ++ ProfileType: runtimeapi.SecurityProfile_Localhost, ++ LocalhostRef: fname, ++ }, nil ++ } else { ++ return nil, fmt.Errorf("localhostProfile must be set if seccompProfile type is Localhost.") + } + } + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, +- } ++ }, nil + } + + func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]string, containerName string, +- podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile { ++ podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) (*runtimeapi.SecurityProfile, error) { + // container fields are applied first + if containerSecContext != nil && containerSecContext.SeccompProfile != nil { + return fieldSeccompProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault) +@@ -316,12 +324,12 @@ func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]str + if fallbackToRuntimeDefault { + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_RuntimeDefault, +- } ++ }, nil + } + + return &runtimeapi.SecurityProfile{ + ProfileType: runtimeapi.SecurityProfile_Unconfined, +- } ++ }, nil + } + + func ipcNamespaceForPod(pod *v1.Pod) runtimeapi.NamespaceMode { +diff --git a/pkg/kubelet/kuberuntime/helpers_test.go b/pkg/kubelet/kuberuntime/helpers_test.go +index 25065f30411..70ad7250ce2 100644 +--- a/pkg/kubelet/kuberuntime/helpers_test.go ++++ b/pkg/kubelet/kuberuntime/helpers_test.go +@@ -242,17 +242,18 @@ func TestFieldProfile(t *testing.T) { + scmpProfile *v1.SeccompProfile + rootPath string + expectedProfile string ++ expectedError string + }{ + { + description: "no seccompProfile should return empty", + expectedProfile: "", + }, + { +- description: "type localhost without profile should return empty", ++ description: "type localhost without profile should return error", + scmpProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeLocalhost, + }, +- expectedProfile: "", ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "unknown type should return empty", +@@ -279,7 +280,7 @@ func TestFieldProfile(t *testing.T) { + description: "SeccompProfileTypeLocalhost should return localhost", + scmpProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeLocalhost, +- LocalhostProfile: utilpointer.StringPtr("profile.json"), ++ LocalhostProfile: utilpointer.String("profile.json"), + }, + rootPath: "/test/", + expectedProfile: "localhost//test/profile.json", +@@ -287,8 +288,13 @@ func TestFieldProfile(t *testing.T) { + } + + for i, test := range tests { +- seccompProfile := fieldProfile(test.scmpProfile, test.rootPath, false) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := fieldProfile(test.scmpProfile, test.rootPath, false) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +@@ -298,17 +304,18 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) { + scmpProfile *v1.SeccompProfile + rootPath string + expectedProfile string ++ expectedError string + }{ + { + description: "no seccompProfile should return runtime/default", + expectedProfile: v1.SeccompProfileRuntimeDefault, + }, + { +- description: "type localhost without profile should return runtime/default", ++ description: "type localhost without profile should return error", + scmpProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeLocalhost, + }, +- expectedProfile: v1.SeccompProfileRuntimeDefault, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "unknown type should return runtime/default", +@@ -335,7 +342,7 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) { + description: "SeccompProfileTypeLocalhost should return localhost", + scmpProfile: &v1.SeccompProfile{ + Type: v1.SeccompProfileTypeLocalhost, +- LocalhostProfile: utilpointer.StringPtr("profile.json"), ++ LocalhostProfile: utilpointer.String("profile.json"), + }, + rootPath: "/test/", + expectedProfile: "localhost//test/profile.json", +@@ -343,8 +350,13 @@ func TestFieldProfileDefaultSeccomp(t *testing.T) { + } + + for i, test := range tests { +- seccompProfile := fieldProfile(test.scmpProfile, test.rootPath, true) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := fieldProfile(test.scmpProfile, test.rootPath, true) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +@@ -359,6 +371,7 @@ func TestGetSeccompProfilePath(t *testing.T) { + containerSc *v1.SecurityContext + containerName string + expectedProfile string ++ expectedError string + }{ + { + description: "no seccomp should return empty", +@@ -369,91 +382,6 @@ func TestGetSeccompProfilePath(t *testing.T) { + containerName: "container1", + expectedProfile: "", + }, +- { +- description: "annotations: pod runtime/default seccomp profile should return runtime/default", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault, +- }, +- expectedProfile: "runtime/default", +- }, +- { +- description: "annotations: pod docker/default seccomp profile should return docker/default", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, +- }, +- expectedProfile: "docker/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: "runtime/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: "docker/default", +- }, +- { +- description: "annotations: pod unconfined seccomp profile should return unconfined", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- }, +- expectedProfile: "unconfined", +- }, +- { +- description: "annotations: pod unconfined seccomp profile with containerName should return unconfined", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- }, +- containerName: "container1", +- expectedProfile: "unconfined", +- }, +- { +- description: "annotations: pod localhost seccomp profile should return local profile path", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: "localhost/chmod.json", +- }, +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: pod localhost seccomp profile with containerName should return local profile path", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: container localhost seccomp profile with containerName should return local profile path", +- annotation: map[string]string{ +- v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: container localhost seccomp profile should override pod profile", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- 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}}, +@@ -480,14 +408,14 @@ func TestGetSeccompProfilePath(t *testing.T) { + expectedProfile: seccompLocalhostPath("filename"), + }, + { +- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns empty", +- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, +- expectedProfile: "", ++ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error", ++ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { +- 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 with empty LocalhostProfile returns error", ++ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", +@@ -500,41 +428,16 @@ func TestGetSeccompProfilePath(t *testing.T) { + 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: seccompLocalhostPath("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: seccompLocalhostPath("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: seccompLocalhostPath("field-pod-profile.json"), +- }, + } + + for i, test := range tests { +- seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, false) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, false) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +@@ -549,6 +452,7 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) { + containerSc *v1.SecurityContext + containerName string + expectedProfile string ++ expectedError string + }{ + { + description: "no seccomp should return runtime/default", +@@ -559,91 +463,6 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) { + containerName: "container1", + expectedProfile: v1.SeccompProfileRuntimeDefault, + }, +- { +- description: "annotations: pod runtime/default seccomp profile should return runtime/default", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileRuntimeDefault, +- }, +- expectedProfile: v1.SeccompProfileRuntimeDefault, +- }, +- { +- description: "annotations: pod docker/default seccomp profile should return docker/default", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, +- }, +- expectedProfile: "docker/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, +- }, +- { +- description: "annotations: pod docker/default seccomp profile with containerName should return docker/default", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.DeprecatedSeccompProfileDockerDefault, +- }, +- containerName: "container1", +- expectedProfile: "docker/default", +- }, +- { +- description: "annotations: pod unconfined seccomp profile should return unconfined", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- }, +- expectedProfile: "unconfined", +- }, +- { +- description: "annotations: pod unconfined seccomp profile with containerName should return unconfined", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- }, +- containerName: "container1", +- expectedProfile: "unconfined", +- }, +- { +- description: "annotations: pod localhost seccomp profile should return local profile path", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: "localhost/chmod.json", +- }, +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: pod localhost seccomp profile with containerName should return local profile path", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: container localhost seccomp profile with containerName should return local profile path", +- annotation: map[string]string{ +- v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: container localhost seccomp profile should override pod profile", +- annotation: map[string]string{ +- v1.SeccompPodAnnotationKey: v1.SeccompProfileNameUnconfined, +- v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", +- }, +- containerName: "container1", +- expectedProfile: seccompLocalhostPath("chmod.json"), +- }, +- { +- description: "annotations: container localhost seccomp profile with unmatched containerName should return runtime/default", +- annotation: map[string]string{ +- v1.SeccompContainerAnnotationKeyPrefix + "container1": "localhost/chmod.json", +- }, +- containerName: "container2", +- expectedProfile: v1.SeccompProfileRuntimeDefault, +- }, + { + description: "pod seccomp profile set to unconfined returns unconfined", + podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeUnconfined}}, +@@ -670,14 +489,14 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) { + expectedProfile: seccompLocalhostPath("filename"), + }, + { +- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns runtime/default", +- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, +- expectedProfile: v1.SeccompProfileRuntimeDefault, ++ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error", ++ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { +- description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns runtime/default", +- containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, +- expectedProfile: v1.SeccompProfileRuntimeDefault, ++ description: "container seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error", ++ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", +@@ -690,41 +509,16 @@ func TestGetSeccompProfilePathDefaultSeccomp(t *testing.T) { + 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: seccompLocalhostPath("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: seccompLocalhostPath("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: seccompLocalhostPath("field-pod-profile.json"), +- }, + } + + for i, test := range tests { +- seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, true) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, true) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +@@ -747,6 +541,7 @@ func TestGetSeccompProfile(t *testing.T) { + containerSc *v1.SecurityContext + containerName string + expectedProfile *runtimeapi.SecurityProfile ++ expectedError string + }{ + { + description: "no seccomp should return unconfined", +@@ -781,14 +576,14 @@ func TestGetSeccompProfile(t *testing.T) { + }, + }, + { +- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined", +- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, +- expectedProfile: unconfinedProfile, ++ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error", ++ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { +- 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 with empty LocalhostProfile returns error", ++ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", +@@ -817,8 +612,13 @@ func TestGetSeccompProfile(t *testing.T) { + } + + for i, test := range tests { +- seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +@@ -841,6 +641,7 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) { + containerSc *v1.SecurityContext + containerName string + expectedProfile *runtimeapi.SecurityProfile ++ expectedError string + }{ + { + description: "no seccomp should return RuntimeDefault", +@@ -875,14 +676,14 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) { + }, + }, + { +- description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns unconfined", +- podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, +- expectedProfile: unconfinedProfile, ++ description: "pod seccomp profile set to SeccompProfileTypeLocalhost with empty LocalhostProfile returns error", ++ podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { +- 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 with empty LocalhostProfile returns error", ++ containerSc: &v1.SecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}}, ++ expectedError: "localhostProfile must be set if seccompProfile type is Localhost.", + }, + { + description: "container seccomp profile set to SeccompProfileTypeLocalhost returns 'localhost/' + LocalhostProfile", +@@ -911,8 +712,13 @@ func TestGetSeccompProfileDefaultSeccomp(t *testing.T) { + } + + for i, test := range tests { +- seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true) +- assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ seccompProfile, err := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, true) ++ if test.expectedError != "" { ++ assert.EqualError(t, err, test.expectedError, "TestCase[%d]: %s", i, test.description) ++ } else { ++ assert.NoError(t, err, "TestCase[%d]: %s", i, test.description) ++ assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description) ++ } + } + } + +diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go +index 6cb9e54729e..54670673bcd 100644 +--- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go ++++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux.go +@@ -46,15 +46,23 @@ func (m *kubeGenericRuntimeManager) applyPlatformSpecificContainerConfig(config + libcontainercgroups.IsCgroup2UnifiedMode() { + enforceMemoryQoS = true + } +- config.Linux = m.generateLinuxContainerConfig(container, pod, uid, username, nsTarget, enforceMemoryQoS) ++ cl, err := m.generateLinuxContainerConfig(container, pod, uid, username, nsTarget, enforceMemoryQoS) ++ if err != nil { ++ return err ++ } ++ config.Linux = cl + return nil + } + + // generateLinuxContainerConfig generates linux container config for kubelet runtime v1. +-func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.Container, pod *v1.Pod, uid *int64, username string, nsTarget *kubecontainer.ContainerID, enforceMemoryQoS bool) *runtimeapi.LinuxContainerConfig { ++func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.Container, pod *v1.Pod, uid *int64, username string, nsTarget *kubecontainer.ContainerID, enforceMemoryQoS bool) (*runtimeapi.LinuxContainerConfig, error) { ++ sc, err := m.determineEffectiveSecurityContext(pod, container, uid, username) ++ if err != nil { ++ return nil, err ++ } + lc := &runtimeapi.LinuxContainerConfig{ + Resources: &runtimeapi.LinuxContainerResources{}, +- SecurityContext: m.determineEffectiveSecurityContext(pod, container, uid, username), ++ SecurityContext: sc, + } + + if nsTarget != nil && lc.SecurityContext.NamespaceOptions.Pid == runtimeapi.NamespaceMode_CONTAINER { +@@ -125,7 +133,7 @@ func (m *kubeGenericRuntimeManager) generateLinuxContainerConfig(container *v1.C + } + } + +- return lc ++ return lc, nil + } + + // calculateLinuxResources will create the linuxContainerResources type based on the provided CPU and memory resource requests, limits +diff --git a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go +index 46817e00fb0..98f635cc932 100644 +--- a/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go ++++ b/pkg/kubelet/kuberuntime/kuberuntime_container_linux_test.go +@@ -47,6 +47,8 @@ func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerInde + restartCountUint32 := uint32(restartCount) + envs := make([]*runtimeapi.KeyValue, len(opts.Envs)) + ++ l, _ := m.generateLinuxContainerConfig(container, pod, new(int64), "", nil, enforceMemoryQoS) ++ + expectedConfig := &runtimeapi.ContainerConfig{ + Metadata: &runtimeapi.ContainerMetadata{ + Name: container.Name, +@@ -64,7 +66,7 @@ func makeExpectedConfig(m *kubeGenericRuntimeManager, pod *v1.Pod, containerInde + Stdin: container.Stdin, + StdinOnce: container.StdinOnce, + Tty: container.TTY, +- Linux: m.generateLinuxContainerConfig(container, pod, new(int64), "", nil, enforceMemoryQoS), ++ Linux: l, + Envs: envs, + } + return expectedConfig +@@ -215,7 +217,8 @@ func TestGenerateLinuxContainerConfigResources(t *testing.T) { + }, + } + +- linuxConfig := m.generateLinuxContainerConfig(&pod.Spec.Containers[0], pod, new(int64), "", nil, false) ++ linuxConfig, err := m.generateLinuxContainerConfig(&pod.Spec.Containers[0], pod, new(int64), "", nil, false) ++ assert.NoError(t, err) + assert.Equal(t, test.expected.CpuPeriod, linuxConfig.GetResources().CpuPeriod, test.name) + assert.Equal(t, test.expected.CpuQuota, linuxConfig.GetResources().CpuQuota, test.name) + assert.Equal(t, test.expected.CpuShares, linuxConfig.GetResources().CpuShares, test.name) +@@ -329,6 +332,8 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { + memoryLow int64 + memoryHigh int64 + } ++ l1, _ := m.generateLinuxContainerConfig(&pod1.Spec.Containers[0], pod1, new(int64), "", nil, true) ++ l2, _ := m.generateLinuxContainerConfig(&pod2.Spec.Containers[0], pod2, new(int64), "", nil, true) + tests := []struct { + name string + pod *v1.Pod +@@ -338,7 +343,7 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { + name: "Request128MBLimit256MB", + pod: pod1, + expected: &expectedResult{ +- m.generateLinuxContainerConfig(&pod1.Spec.Containers[0], pod1, new(int64), "", nil, true), ++ l1, + 128 * 1024 * 1024, + int64(float64(256*1024*1024) * m.memoryThrottlingFactor), + }, +@@ -347,7 +352,7 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { + name: "Request128MBWithoutLimit", + pod: pod2, + expected: &expectedResult{ +- m.generateLinuxContainerConfig(&pod2.Spec.Containers[0], pod2, new(int64), "", nil, true), ++ l2, + 128 * 1024 * 1024, + int64(pod2MemoryHigh), + }, +@@ -355,7 +360,8 @@ func TestGenerateContainerConfigWithMemoryQoSEnforced(t *testing.T) { + } + + for _, test := range tests { +- linuxConfig := m.generateLinuxContainerConfig(&test.pod.Spec.Containers[0], test.pod, new(int64), "", nil, true) ++ linuxConfig, err := m.generateLinuxContainerConfig(&test.pod.Spec.Containers[0], test.pod, new(int64), "", nil, true) ++ assert.NoError(t, err) + assert.Equal(t, test.expected.containerConfig, linuxConfig, test.name) + assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.min"], strconv.FormatInt(test.expected.memoryLow, 10), test.name) + assert.Equal(t, linuxConfig.GetResources().GetUnified()["memory.high"], strconv.FormatInt(test.expected.memoryHigh, 10), test.name) +@@ -578,7 +584,8 @@ func TestGenerateLinuxContainerConfigNamespaces(t *testing.T) { + }, + } { + t.Run(tc.name, func(t *testing.T) { +- got := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target, false) ++ got, err := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", tc.target, false) ++ assert.NoError(t, err) + if diff := cmp.Diff(tc.want, got.SecurityContext.NamespaceOptions); diff != "" { + t.Errorf("%v: diff (-want +got):\n%v", t.Name(), diff) + } +@@ -669,7 +676,8 @@ func TestGenerateLinuxContainerConfigSwap(t *testing.T) { + } { + t.Run(tc.name, func(t *testing.T) { + m.memorySwapBehavior = tc.swapSetting +- actual := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", nil, false) ++ actual, err := m.generateLinuxContainerConfig(&tc.pod.Spec.Containers[0], tc.pod, nil, "", nil, false) ++ assert.NoError(t, err) + assert.Equal(t, tc.expected, actual.Resources.MemorySwapLimitInBytes, "memory swap config for %s", tc.name) + }) + } +diff --git a/pkg/kubelet/kuberuntime/security_context.go b/pkg/kubelet/kuberuntime/security_context.go +index c9d33e44305..3b575c8e974 100644 +--- a/pkg/kubelet/kuberuntime/security_context.go ++++ b/pkg/kubelet/kuberuntime/security_context.go +@@ -24,7 +24,7 @@ import ( + ) + + // determineEffectiveSecurityContext gets container's security context from v1.Pod and v1.Container. +-func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Pod, container *v1.Container, uid *int64, username string) *runtimeapi.LinuxContainerSecurityContext { ++func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Pod, container *v1.Container, uid *int64, username string) (*runtimeapi.LinuxContainerSecurityContext, error) { + effectiveSc := securitycontext.DetermineEffectiveSecurityContext(pod, container) + synthesized := convertToRuntimeSecurityContext(effectiveSc) + if synthesized == nil { +@@ -36,9 +36,16 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po + + // TODO: Deprecated, remove after we switch to Seccomp field + // set SeccompProfilePath. +- synthesized.SeccompProfilePath = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault) ++ var err error ++ synthesized.SeccompProfilePath, err = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault) ++ if err != nil { ++ return nil, err ++ } + +- synthesized.Seccomp = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault) ++ synthesized.Seccomp, err = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault) ++ if err != nil { ++ return nil, err ++ } + + // set ApparmorProfile. + synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name) +@@ -74,7 +81,7 @@ func (m *kubeGenericRuntimeManager) determineEffectiveSecurityContext(pod *v1.Po + synthesized.MaskedPaths = securitycontext.ConvertToRuntimeMaskedPaths(effectiveSc.ProcMount) + synthesized.ReadonlyPaths = securitycontext.ConvertToRuntimeReadonlyPaths(effectiveSc.ProcMount) + +- return synthesized ++ return synthesized, nil + } + + // convertToRuntimeSecurityContext converts v1.SecurityContext to runtimeapi.SecurityContext. +-- +2.40.0 diff --git a/recipes-containers/kubernetes/kubernetes_git.bb b/recipes-containers/kubernetes/kubernetes_git.bb index 59892c92..dc741bbf 100644 --- a/recipes-containers/kubernetes/kubernetes_git.bb +++ b/recipes-containers/kubernetes/kubernetes_git.bb @@ -30,6 +30,7 @@ SRC_URI:append = " \ file://0001-cross-don-t-build-tests-by-default.patch;patchdir=src/import \ file://0001-build-golang.sh-convert-remaining-go-calls-to-use.patch;patchdir=src/import \ file://0001-Makefile.generated_files-Fix-race-issue-for-installi.patch;patchdir=src/import \ + file://CVE-2023-2431.patch;patchdir=src/import \ file://cni-containerd-net.conflist \ file://k8s-init \ file://99-kubernetes.conf \ -- 2.40.0
-=-=-=-=-=-=-=-=-=-=-=- Links: You receive all messages sent to this group. View/Reply Online (#8411): https://lists.yoctoproject.org/g/meta-virtualization/message/8411 Mute This Topic: https://lists.yoctoproject.org/mt/102341409/21656 Group Owner: meta-virtualization+ow...@lists.yoctoproject.org Unsubscribe: https://lists.yoctoproject.org/g/meta-virtualization/unsub [arch...@mail-archive.com] -=-=-=-=-=-=-=-=-=-=-=-