mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
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:
parent
56efa75173
commit
8b7003aff4
@ -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, "")
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -234,6 +234,7 @@ var (
|
||||
"ReservedSystemCPUs",
|
||||
"RuntimeRequestTimeout.Duration",
|
||||
"RunOnce",
|
||||
"SeccompDefault",
|
||||
"SerializeImagePulls",
|
||||
"ShowHiddenMetricsForVersion",
|
||||
"StreamingConnectionIdleTimeout.Duration",
|
||||
|
@ -69,6 +69,7 @@ registryBurst: 10
|
||||
registryPullQPS: 5
|
||||
resolvConf: /etc/resolv.conf
|
||||
runtimeRequestTimeout: 2m0s
|
||||
seccompDefault: false
|
||||
serializeImagePulls: true
|
||||
shutdownGracePeriod: 0s
|
||||
shutdownGracePeriodCriticalPods: 0s
|
||||
|
@ -69,6 +69,7 @@ registryBurst: 10
|
||||
registryPullQPS: 5
|
||||
resolvConf: /etc/resolv.conf
|
||||
runtimeRequestTimeout: 2m0s
|
||||
seccompDefault: false
|
||||
serializeImagePulls: true
|
||||
shutdownGracePeriod: 0s
|
||||
shutdownGracePeriodCriticalPods: 0s
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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{
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user