Merge pull request #101943 from saschagrunert/seccomp-default

Add kubelet `SeccompDefault` alpha feature
This commit is contained in:
Kubernetes Prow Robot 2021-06-24 13:07:41 -07:00 committed by GitHub
commit 2e93b3924a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
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 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. // This can be useful for debugging volume related issues.
KeepTerminatedPodVolumes bool 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 // 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(), ", ")) 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 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.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.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.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 // DEPRECATED FLAGS
fs.StringVar(&f.BootstrapKubeconfig, "experimental-bootstrap-kubeconfig", f.BootstrapKubeconfig, "") 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{} 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, k, err := createAndInitKubelet(&kubeServer.KubeletConfiguration,
kubeDeps, kubeDeps,
&kubeServer.ContainerRuntimeOptions, &kubeServer.ContainerRuntimeOptions,
@ -1164,7 +1168,9 @@ func RunKubelet(kubeServer *options.KubeletServer, kubeDeps *kubelet.Dependencie
kubeServer.KeepTerminatedPodVolumes, kubeServer.KeepTerminatedPodVolumes,
kubeServer.NodeLabels, kubeServer.NodeLabels,
kubeServer.SeccompProfileRoot, kubeServer.SeccompProfileRoot,
kubeServer.NodeStatusMaxImages) kubeServer.NodeStatusMaxImages,
kubeServer.KubeletFlags.SeccompDefault || kubeServer.KubeletConfiguration.SeccompDefault,
)
if err != nil { if err != nil {
return fmt.Errorf("failed to create kubelet: %w", err) return fmt.Errorf("failed to create kubelet: %w", err)
} }
@ -1238,7 +1244,9 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes bool, keepTerminatedPodVolumes bool,
nodeLabels map[string]string, nodeLabels map[string]string,
seccompProfileRoot 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 // TODO: block until all sources have delivered at least one update to the channel, or break the sync loop
// up into "per source" synchronizations // up into "per source" synchronizations
@ -1271,7 +1279,9 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes, keepTerminatedPodVolumes,
nodeLabels, nodeLabels,
seccompProfileRoot, seccompProfileRoot,
nodeStatusMaxImages) nodeStatusMaxImages,
seccompDefault,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -714,6 +714,12 @@ const (
// //
// Enables apiserver and kubelet to allow up to 32 DNSSearchPaths and up to 2048 DNSSearchListChars. // Enables apiserver and kubelet to allow up to 32 DNSSearchPaths and up to 2048 DNSSearchListChars.
ExpandedDNSConfig featuregate.Feature = "ExpandedDNSConfig" 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() { func init() {
@ -821,6 +827,7 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha}, DisableCloudProviders: {Default: false, PreRelease: featuregate.Alpha},
StatefulSetMinReadySeconds: {Default: false, PreRelease: featuregate.Alpha}, StatefulSetMinReadySeconds: {Default: false, PreRelease: featuregate.Alpha},
ExpandedDNSConfig: {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 // inherited features from generic apiserver, relisted here to get a conflict if it is changed
// unintentionally on either side: // unintentionally on either side:

View File

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

View File

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

View File

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

View File

@ -407,6 +407,8 @@ type KubeletConfiguration struct {
EnableProfilingHandler bool EnableProfilingHandler bool
// EnableDebugFlagsHandler enables/debug/flags/v handler. // EnableDebugFlagsHandler enables/debug/flags/v handler.
EnableDebugFlagsHandler bool 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 // KubeletAuthorizationMode denotes the authorization mode for the kubelet

View File

@ -252,4 +252,7 @@ func SetDefaults_KubeletConfiguration(obj *kubeletconfigv1beta1.KubeletConfigura
if obj.EnableDebugFlagsHandler == nil { if obj.EnableDebugFlagsHandler == nil {
obj.EnableDebugFlagsHandler = utilpointer.BoolPtr(true) 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 { if err := v1.Convert_Pointer_bool_To_bool(&in.EnableDebugFlagsHandler, &out.EnableDebugFlagsHandler, s); err != nil {
return err return err
} }
if err := v1.Convert_Pointer_bool_To_bool(&in.SeccompDefault, &out.SeccompDefault, s); err != nil {
return err
}
return nil 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 { if err := v1.Convert_bool_To_Pointer_bool(&in.EnableDebugFlagsHandler, &out.EnableDebugFlagsHandler, s); err != nil {
return err return err
} }
if err := v1.Convert_bool_To_Pointer_bool(&in.SeccompDefault, &out.SeccompDefault, s); err != nil {
return err
}
return nil return nil
} }

View File

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

View File

@ -202,8 +202,11 @@ func toKubeRuntimeStatus(status *runtimeapi.RuntimeStatus) *kubecontainer.Runtim
return &kubecontainer.RuntimeStatus{Conditions: conditions} 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 scmp == nil {
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
return "" return ""
} }
if scmp.Type == v1.SeccompProfileTypeRuntimeDefault { if scmp.Type == v1.SeccompProfileTypeRuntimeDefault {
@ -216,6 +219,10 @@ func fieldProfile(scmp *v1.SeccompProfile, profileRootPath string) string {
if scmp.Type == v1.SeccompProfileTypeUnconfined { if scmp.Type == v1.SeccompProfileTypeUnconfined {
return v1.SeccompProfileNameUnconfined return v1.SeccompProfileNameUnconfined
} }
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
return "" return ""
} }
@ -229,10 +236,10 @@ func annotationProfile(profile, profileRootPath string) string {
} }
func (m *kubeGenericRuntimeManager) getSeccompProfilePath(annotations map[string]string, containerName 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 // container fields are applied first
if containerSecContext != nil && containerSecContext.SeccompProfile != nil { 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) // 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 // when container seccomp is not defined, try to apply from pod field
if podSecContext != nil && podSecContext.SeccompProfile != nil { 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) // 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) return annotationProfile(profile, m.seccompProfileRoot)
} }
if fallbackToRuntimeDefault {
return v1.SeccompProfileRuntimeDefault
}
return "" return ""
} }
func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string) *runtimeapi.SecurityProfile { func fieldSeccompProfile(scmp *v1.SeccompProfile, profileRootPath string, fallbackToRuntimeDefault bool) *runtimeapi.SecurityProfile {
// TODO: Move to RuntimeDefault as the default instead of Unconfined after discussion
// with sig-node.
if scmp == nil { if scmp == nil {
if fallbackToRuntimeDefault {
return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_RuntimeDefault,
}
}
return &runtimeapi.SecurityProfile{ return &runtimeapi.SecurityProfile{
ProfileType: runtimeapi.SecurityProfile_Unconfined, 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, 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 // container fields are applied first
if containerSecContext != nil && containerSecContext.SeccompProfile != nil { 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 // when container seccomp is not defined, try to apply from pod field
if podSecContext != nil && podSecContext.SeccompProfile != nil { 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{ return &runtimeapi.SecurityProfile{

View File

@ -210,7 +210,7 @@ func TestFieldProfile(t *testing.T) {
expectedProfile: "unconfined", expectedProfile: "unconfined",
}, },
{ {
description: "SeccompProfileTypeLocalhost should return unconfined", description: "SeccompProfileTypeLocalhost should return localhost",
scmpProfile: &v1.SeccompProfile{ scmpProfile: &v1.SeccompProfile{
Type: v1.SeccompProfileTypeLocalhost, Type: v1.SeccompProfileTypeLocalhost,
LocalhostProfile: utilpointer.StringPtr("profile.json"), LocalhostProfile: utilpointer.StringPtr("profile.json"),
@ -221,7 +221,63 @@ func TestFieldProfile(t *testing.T) {
} }
for i, test := range tests { 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) 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 { 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) 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 { 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) assert.Equal(t, test.expectedProfile, seccompProfile, "TestCase[%d]: %s", i, test.description)
} }
} }

View File

@ -141,6 +141,9 @@ type kubeGenericRuntimeManager struct {
// PodState provider instance // PodState provider instance
podStateProvider podStateProvider 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. // KubeGenericRuntime is a interface contains interfaces for container runtime and command.
@ -182,6 +185,7 @@ func NewKubeGenericRuntimeManager(
legacyLogProvider LegacyLogProvider, legacyLogProvider LegacyLogProvider,
logManager logs.ContainerLogManager, logManager logs.ContainerLogManager,
runtimeClassManager *runtimeclass.Manager, runtimeClassManager *runtimeclass.Manager,
seccompDefault bool,
) (KubeGenericRuntime, error) { ) (KubeGenericRuntime, error) {
kubeRuntimeManager := &kubeGenericRuntimeManager{ kubeRuntimeManager := &kubeGenericRuntimeManager{
recorder: recorder, recorder: recorder,
@ -201,6 +205,7 @@ func NewKubeGenericRuntimeManager(
logManager: logManager, logManager: logManager,
runtimeClassManager: runtimeClassManager, runtimeClassManager: runtimeClassManager,
logReduction: logreduction.NewLogReduction(identicalErrorDelay), logReduction: logreduction.NewLogReduction(identicalErrorDelay),
seccompDefault: seccompDefault,
} }
typedVersion, err := kubeRuntimeManager.getTypedVersion() 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 // TODO: Deprecated, remove after we switch to Seccomp field
// set SeccompProfilePath. // 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. // set ApparmorProfile.
synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name) synthesized.ApparmorProfile = apparmor.GetProfileNameFromPodAnnotations(pod.Annotations, container.Name)

View File

@ -864,6 +864,11 @@ type KubeletConfiguration struct {
// Default: true // Default: true
// +optional // +optional
EnableDebugFlagsHandler *bool `json:"enableDebugFlagsHandler,omitempty"` 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 type KubeletAuthorizationMode string

View File

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