Skip to content

Commit

Permalink
Add SeccompDefault feature
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
saschagrunert and pjbgf committed Jun 23, 2021
1 parent 56efa75 commit 8b7003a
Show file tree
Hide file tree
Showing 16 changed files with 437 additions and 20 deletions.
8 changes: 8 additions & 0 deletions cmd/kubelet/app/options/options.go
Expand Up @@ -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
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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, "")
Expand Down
16 changes: 13 additions & 3 deletions cmd/kubelet/app/server.go
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -1271,7 +1279,9 @@ func createAndInitKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
keepTerminatedPodVolumes,
nodeLabels,
seccompProfileRoot,
nodeStatusMaxImages)
nodeStatusMaxImages,
seccompDefault,
)
if err != nil {
return nil, err
}
Expand Down
7 changes: 7 additions & 0 deletions pkg/features/kube_features.go
Expand Up @@ -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() {
Expand Down Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions pkg/kubelet/apis/config/helpers_test.go
Expand Up @@ -234,6 +234,7 @@ var (
"ReservedSystemCPUs",
"RuntimeRequestTimeout.Duration",
"RunOnce",
"SeccompDefault",
"SerializeImagePulls",
"ShowHiddenMetricsForVersion",
"StreamingConnectionIdleTimeout.Duration",
Expand Down
Expand Up @@ -69,6 +69,7 @@ registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
seccompDefault: false
serializeImagePulls: true
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s
Expand Down
Expand Up @@ -69,6 +69,7 @@ registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
seccompDefault: false
serializeImagePulls: true
shutdownGracePeriod: 0s
shutdownGracePeriodCriticalPods: 0s
Expand Down
2 changes: 2 additions & 0 deletions pkg/kubelet/apis/config/types.go
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions pkg/kubelet/apis/config/v1beta1/defaults.go
Expand Up @@ -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)
}
}
6 changes: 6 additions & 0 deletions pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion pkg/kubelet/kubelet.go
Expand Up @@ -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)
}
Expand Down Expand Up @@ -649,6 +651,7 @@ func NewMainKubelet(kubeCfg *kubeletconfiginternal.KubeletConfiguration,
kubeDeps.dockerLegacyService,
klet.containerLogManager,
klet.runtimeClassManager,
seccompDefault,
)
if err != nil {
return nil, err
Expand Down
40 changes: 30 additions & 10 deletions pkg/kubelet/kuberuntime/helpers.go
Expand Up @@ -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 {
Expand All @@ -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 ""
}

Expand All @@ -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)
Expand All @@ -244,21 +251,28 @@ 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)
if profile, ok := annotations[v1.SeccompPodAnnotationKey]; ok {
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,
}
Expand All @@ -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{
Expand Down

0 comments on commit 8b7003a

Please sign in to comment.