Add SeccompDefault feature

This adds the gate `SeccompDefault` as new alpha feature. Seccomp path
and field fallbacks are now passed to the helper functions, whereas unit
tests covering those code paths have been added as well.

Beside enabling the feature gate, the feature has to be enabled by the
`SeccompDefault` kubelet configuration or its corresponding
`--seccomp-default` CLI flag.

Signed-off-by: Sascha Grunert <sgrunert@redhat.com>

Apply suggestions from code review

Co-authored-by: Paulo Gomes <pjbgf@linux.com>
Signed-off-by: Sascha Grunert <sgrunert@redhat.com>
This commit is contained in:
Sascha Grunert 2021-05-12 11:20:30 +02:00
parent 56efa75173
commit 8b7003aff4
No known key found for this signature in database
GPG Key ID: 09D97D153EF94D93
16 changed files with 437 additions and 20 deletions

View File

@ -166,6 +166,9 @@ type KubeletFlags struct {
// This flag, if set, instructs the kubelet to keep volumes from terminated pods mounted to the node.
// This can be useful for debugging volume related issues.
KeepTerminatedPodVolumes bool
// SeccompDefault enables the use of `RuntimeDefault` as the default seccomp profile for all workloads on the node.
// To use this flag, the corresponding SeccompDefault feature gate must be enabled.
SeccompDefault bool
}
// NewKubeletFlags will create a new KubeletFlags with default values
@ -211,6 +214,10 @@ func ValidateKubeletFlags(f *KubeletFlags) error {
return fmt.Errorf("unknown 'kubernetes.io' or 'k8s.io' labels specified with --node-labels: %v\n--node-labels in the 'kubernetes.io' namespace must begin with an allowed prefix (%s) or be in the specifically allowed set (%s)", unknownLabels.List(), strings.Join(kubeletapis.KubeletLabelNamespaces(), ", "), strings.Join(kubeletapis.KubeletLabels(), ", "))
}
if f.SeccompDefault && !utilfeature.DefaultFeatureGate.Enabled(features.SeccompDefault) {
return fmt.Errorf("the SeccompDefault feature gate must be enabled in order to use the --seccomp-default flag")
}
return nil
}
@ -346,6 +353,7 @@ func (f *KubeletFlags) AddFlags(mainfs *pflag.FlagSet) {
fs.Var(&bindableNodeLabels, "node-labels", fmt.Sprintf("<Warning: Alpha feature> Labels to add when registering the node in the cluster. Labels must be key=value pairs separated by ','. Labels in the 'kubernetes.io' namespace must begin with an allowed prefix (%s) or be in the specifically allowed set (%s)", strings.Join(kubeletapis.KubeletLabelNamespaces(), ", "), strings.Join(kubeletapis.KubeletLabels(), ", ")))
fs.StringVar(&f.LockFilePath, "lock-file", f.LockFilePath, "<Warning: Alpha feature> The path to file for kubelet to use as a lock file.")
fs.BoolVar(&f.ExitOnLockContention, "exit-on-lock-contention", f.ExitOnLockContention, "Whether kubelet should exit upon lock-file contention.")
fs.BoolVar(&f.SeccompDefault, "seccomp-default", f.SeccompDefault, "<Warning: Alpha feature> Enable the use of `RuntimeDefault` as the default seccomp profile for all workloads. The SeccompDefault feature gate must be enabled to allow this flag, which is disabled per default.")
// DEPRECATED FLAGS
fs.StringVar(&f.BootstrapKubeconfig, "experimental-bootstrap-kubeconfig", f.BootstrapKubeconfig, "")

View File

@ -1135,6 +1135,10 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
kubeDeps.OSInterface = kubecontainer.RealOS{}
}
if kubeServer.KubeletConfiguration.SeccompDefault && !utilfeature.DefaultFeatureGate.Enabled(features.SeccompDefault) {
return fmt.Errorf("the SeccompDefault feature gate must be enabled in order to use the SeccompDefault configuration")
}
k, err := createAndInitKubelet(&kubeServer.KubeletConfiguration,
kubeDeps,
&kubeServer.ContainerRuntimeOptions,
@ -1164,7 +1168,9 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
kubeServer.KeepTerminatedPodVolumes,
kubeServer.NodeLabels,
kubeServer.SeccompProfileRoot,
kubeServer.NodeStatusMaxImages)
kubeServer.NodeStatusMaxImages,
kubeServer.KubeletFlags.SeccompDefault || kubeServer.KubeletConfiguration.SeccompDefault,
)
if err != nil {
return fmt.Errorf("failed to create kubelet: %w", err)
}
@ -1238,7 +1244,9 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes bool,
nodeLabels map[string]string,
seccompProfileRoot string,
nodeStatusMaxImages int32) (k kubelet.Bootstrap, err error) {
nodeStatusMaxImages int32,
seccompDefault bool,
) (k kubelet.Bootstrap, err error) {
// TODO: block until all sources have delivered at least one update to the channel, or break the sync loop
// up into "per source" synchronizations
@ -1271,7 +1279,9 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes,
nodeLabels,
seccompProfileRoot,
nodeStatusMaxImages)
nodeStatusMaxImages,
seccompDefault,
)
if err != nil {
return nil, err
}

View File

@ -721,6 +721,12 @@ const (
//
// Enables apiserver and kubelet to allow up to 32 DNSSearchPaths and up to 2048 DNSSearchListChars.
ExpandedDNSConfig featuregate.Feature = "ExpandedDNSConfig"
// owner: @saschagrunert
// alpha: v1.22
//
// Enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
SeccompDefault featuregate.Feature = "SeccompDefault"
)
func init() {
@ -829,6 +835,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha},
StatefulSetMinReadySeconds: {Default: false, PreRelease: featuregate.Alpha},
ExpandedDNSConfig: {Default: false, PreRelease: featuregate.Alpha},
SeccompDefault: {Default: false, PreRelease: featuregate.Alpha},
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side:

View File

@ -234,6 +234,7 @@ var (
"ReservedSystemCPUs",
"RuntimeRequestTimeout.Duration",
"RunOnce",
"SeccompDefault",
"SerializeImagePulls",
"ShowHiddenMetricsForVersion",
"StreamingConnectionIdleTimeout.Duration",

View File

@ -69,6 +69,7 @@ registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
seccompDefault: false
serializeImagePulls: true
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s

View File

@ -69,6 +69,7 @@ registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
seccompDefault: false
serializeImagePulls: true
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s

View File

@ -407,6 +407,8 @@ type KubeletConfiguration struct {
EnableProfilingHandler bool
// EnableDebugFlagsHandler enables/debug/flags/v handler.
EnableDebugFlagsHandler bool
// SeccompDefault enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
SeccompDefault bool
}
// KubeletAuthorizationMode denotes the authorization mode for the kubelet

View File

@ -252,4 +252,7 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura
if obj.EnableDebugFlagsHandler == nil {
obj.EnableDebugFlagsHandler = utilpointer.BoolPtr(true)
}
if obj.SeccompDefault == nil {
obj.SeccompDefault = utilpointer.BoolPtr(false)
}
}

View File

@ -371,6 +371,9 @@ func autoConvert_v1beta1_KubeletConfiguration_To_config_KubeletConfiguration(in
if err := v1.Convert_Pointer_bool_To_bool(&in.EnableDebugFlagsHandler, &out.EnableDebugFlagsHandler, s); err != nil {
return err
}
if err := v1.Convert_Pointer_bool_To_bool(&in.SeccompDefault, &out.SeccompDefault, s); err != nil {
return err
}
return nil
}
@ -532,6 +535,9 @@ func autoConvert_config_KubeletConfiguration_To_v1beta1_KubeletConfiguration(in
if err := v1.Convert_bool_To_Pointer_bool(&in.EnableDebugFlagsHandler, &out.EnableDebugFlagsHandler, s); err != nil {
return err
}
if err := v1.Convert_bool_To_Pointer_bool(&in.SeccompDefault, &out.SeccompDefault, s); err != nil {
return err
}
return nil
}

View File

@ -367,7 +367,9 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes bool,
nodeLabels map[string]string,
seccompProfileRoot string,
nodeStatusMaxImages int32) (*Kubelet, error) {
nodeStatusMaxImages int32,
seccompDefault bool,
) (*Kubelet, error) {
if rootDirectory == "" {
return nil, fmt.Errorf("invalid root directory %q", rootDirectory)
}
@ -649,6 +651,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
kubeDeps.dockerLegacyService,
klet.containerLogManager,
klet.runtimeClassManager,
seccompDefault,
)
if err != nil {
return nil, err

View File

@ -202,8 +202,11 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim
return &kubecontainer.RuntimeStatus{Conditions: conditions}
}
func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string) string {
func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) string {
if scmp == nil {
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
return ""
}
if scmp.Type == v1.SeccompProfileTypeRuntimeDefault {
@ -216,6 +219,10 @@ func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string) string {
if scmp.Type == v1.SeccompProfileTypeUnconfined {
return v1.SeccompProfileNameUnconfined
}
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
return ""
}
@ -229,10 +236,10 @@ func annotationProfile(profile, profileRootPath string) string {
}
func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string]string, containerName string,
podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext) string {
podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) string {
// container fields are applied first
if containerSecContext != nil && containerSecContext.SeccompProfile != nil {
return fieldProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot)
return fieldProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
}
// if container field does not exist, try container annotation (deprecated)
@ -244,7 +251,7 @@ func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string
// when container seccomp is not defined, try to apply from pod field
if podSecContext != nil && podSecContext.SeccompProfile != nil {
return fieldProfile(podSecContext.SeccompProfile, m.seccompProfileRoot)
return fieldProfile(podSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
}
// as last resort, try to apply pod annotation (deprecated)
@ -252,13 +259,20 @@ func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string
return annotationProfile(profile, m.seccompProfileRoot)
}
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
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.
func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile {
if scmp == nil {
if fallbackToRuntimeDefault {
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
}
}
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined,
}
@ -281,15 +295,21 @@ func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string) *runti
}
func (m *kubeGenericRuntimeManager) getSeccompProfile(annotations map[string]string, containerName string,
podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext) *runtimeapi.SecurityProfile {
podSecContext *v1.PodSecurityContext, containerSecContext *v1.SecurityContext, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile {
// container fields are applied first
if containerSecContext != nil && containerSecContext.SeccompProfile != nil {
return fieldSeccompProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot)
return fieldSeccompProfile(containerSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
}
// 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 fieldSeccompProfile(podSecContext.SeccompProfile, m.seccompProfileRoot, fallbackToRuntimeDefault)
}
if fallbackToRuntimeDefault {
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
}
}
return &runtimeapi.SecurityProfile{

View File

@ -210,7 +210,7 @@ func TestFieldProfile(t *testing.T) {
expectedProfile: "unconfined",
},
{
description: "SeccompProfileTypeLocalhost should return unconfined",
description: "SeccompProfileTypeLocalhost should return localhost",
scmpProfile: &v1.SeccompProfile{
Type: v1.SeccompProfileTypeLocalhost,
LocalhostProfile: utilpointer.StringPtr("profile.json"),
@ -221,7 +221,63 @@ func TestFieldProfile(t *testing.T) {
}
for i, test := range tests {
seccompProfile := fieldProfile(test.scmpProfile, test.rootPath)
seccompProfile := fieldProfile(test.scmpProfile, test.rootPath, false)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}
func TestFieldProfileDefaultSeccomp(t *testing.T) {
tests := []struct {
description string
scmpProfile *v1.SeccompProfile
rootPath string
expectedProfile string
}{
{
description: "no seccompProfile should return runtime/default",
expectedProfile: v1.SeccompProfileRuntimeDefault,
},
{
description: "type localhost without profile should return runtime/default",
scmpProfile: &v1.SeccompProfile{
Type: v1.SeccompProfileTypeLocalhost,
},
expectedProfile: v1.SeccompProfileRuntimeDefault,
},
{
description: "unknown type should return runtime/default",
scmpProfile: &v1.SeccompProfile{
Type: "",
},
expectedProfile: v1.SeccompProfileRuntimeDefault,
},
{
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 localhost",
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, true)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}
@ -411,7 +467,197 @@ func TestGetSeccompProfilePath(t *testing.T) {
}
for i, test := range tests {
seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc)
seccompProfile := m.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, false)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}
func TestGetSeccompProfilePathDefaultSeccomp(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 runtime/default",
expectedProfile: v1.SeccompProfileRuntimeDefault,
},
{
description: "annotations: no seccomp with containerName should return runtime/default",
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: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "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: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "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: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "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: "localhost/" + filepath.Join(fakeSeccompProfileRoot, "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}},
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 runtime/default",
podSc: &v1.PodSecurityContext{SeccompProfile: &v1.SeccompProfile{Type: v1.SeccompProfileTypeLocalhost}},
expectedProfile: v1.SeccompProfileRuntimeDefault,
},
{
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 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.getSeccompProfilePath(test.annotation, test.containerName, test.podSc, test.containerSc, true)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}
@ -505,7 +751,101 @@ func TestGetSeccompProfile(t *testing.T) {
}
for i, test := range tests {
seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc)
seccompProfile := m.getSeccompProfile(test.annotation, test.containerName, test.podSc, test.containerSc, false)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}
func TestGetSeccompProfileDefaultSeccomp(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 RuntimeDefault",
expectedProfile: runtimeDefaultProfile,
},
{
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, true)
assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
}
}

View File

@ -141,6 +141,9 @@ type kubeGenericRuntimeManager struct {
// PodState provider instance
podStateProvider podStateProvider
// Use RuntimeDefault as the default seccomp profile for all workloads.
seccompDefault bool
}
// KubeGenericRuntime is a interface contains interfaces for container runtime and command.
@ -182,6 +185,7 @@ func NewKubeGenericRuntimeManager(
legacyLogProvider LegacyLogProvider,
logManager logs.ContainerLogManager,
runtimeClassManager *runtimeclass.Manager,
seccompDefault bool,
) (KubeGenericRuntime, error) {
kubeRuntimeManager := &kubeGenericRuntimeManager{
recorder: recorder,
@ -201,6 +205,7 @@ func NewKubeGenericRuntimeManager(
logManager: logManager,
runtimeClassManager: runtimeClassManager,
logReduction: logreduction.NewLogReduction(identicalErrorDelay),
seccompDefault: seccompDefault,
}
typedVersion, err := kubeRuntimeManager.getTypedVersion()

View File

@ -36,9 +36,9 @@ 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)
synthesized.SeccompProfilePath = m.getSeccompProfilePath(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
synthesized.Seccomp = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext)
synthesized.Seccomp = m.getSeccompProfile(pod.Annotations, container.Name, pod.Spec.SecurityContext, container.SecurityContext, m.seccompDefault)
// set ApparmorProfile.
synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)

View File

@ -864,6 +864,11 @@ type KubeletConfiguration struct {
// Default: true
// +optional
EnableDebugFlagsHandler *bool `json:"enableDebugFlagsHandler,omitempty"`
// SeccompDefault enables the use of `RuntimeDefault` as the default seccomp profile for all workloads.
// This requires the corresponding SeccompDefault feature gate to be enabled as well.
// Default: false
// +optional
SeccompDefault *bool `json:"seccompDefault,omitempty"`
}
type KubeletAuthorizationMode string

View File

@ -321,6 +321,11 @@ func (in *KubeletConfiguration) DeepCopyInto(out *KubeletConfiguration) {
*out = new(bool)
**out = **in
}
if in.SeccompDefault != nil {
in, out := &in.SeccompDefault, &out.SeccompDefault
*out = new(bool)
**out = **in
}
return
}