From effa09a42f57898427bb55aac23ff646378586e7 Mon Sep 17 00:00:00 2001 From: MatthieuFin Date: Tue, 31 Aug 2021 17:03:25 +0200 Subject: [PATCH] Add full support for setting securityContext for restic restore container Signed-off-by: MatthieuFin --- go.mod | 1 + pkg/restore/restic_restore_action.go | 18 +-- pkg/util/kube/security_context.go | 10 +- pkg/util/kube/security_context_test.go | 181 +++++++++++++++++++++++-- 4 files changed, 186 insertions(+), 24 deletions(-) diff --git a/go.mod b/go.mod index bfea33926..bcce08eb7 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,7 @@ require ( k8s.io/kube-aggregator v0.19.12 sigs.k8s.io/cluster-api v0.3.11-0.20210106212952-b6c1b5b3db3d sigs.k8s.io/controller-runtime v0.7.1-0.20201215171748-096b2e07c091 + sigs.k8s.io/yaml v1.2.0 // indirect ) replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2 diff --git a/pkg/restore/restic_restore_action.go b/pkg/restore/restic_restore_action.go index c20af38c7..f72923efe 100644 --- a/pkg/restore/restic_restore_action.go +++ b/pkg/restore/restic_restore_action.go @@ -139,11 +139,11 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu ) } - runAsRoot, runAsGroup, allowPrivilegeEscalation := getSecurityContext(log, config) + runAsUser, runAsGroup, allowPrivilegeEscalation, secCtx := getSecurityContext(log, config) - securityContext, err := kube.ParseSecurityContext(runAsRoot, runAsGroup, allowPrivilegeEscalation) + securityContext, err := kube.ParseSecurityContext(runAsUser, runAsGroup, allowPrivilegeEscalation, secCtx) if err != nil { - log.Errorf("Using default resource values, couldn't parse resource requirements: %s.", err) + log.Errorf("Using default securityContext values, couldn't parse securityContext requirements: %s.", err) } initContainerBuilder := newResticInitContainerBuilder(image, string(input.Restore.UID)) @@ -245,15 +245,17 @@ func getResourceLimits(log logrus.FieldLogger, config *corev1.ConfigMap) (string return config.Data["cpuLimit"], config.Data["memLimit"] } - -// getSecurityContext extracts securityContext runAsUser, runAsGroup, and allowPrivilegeEscalation from a ConfigMap. -func getSecurityContext(log logrus.FieldLogger, config *corev1.ConfigMap) (string, string, string) { +// getSecurityContext extracts securityContext runAsUser, runAsGroup, allowPrivilegeEscalation, and securityContext from a ConfigMap. +func getSecurityContext(log logrus.FieldLogger, config *corev1.ConfigMap) (string, string, string, string) { if config == nil { log.Debug("No config found for plugin") - return "", "", "" + return "", "", "", "" } - return config.Data["secCtxRunAsUser"], config.Data["secCtxRunAsGroup"], config.Data["secCtxAllowPrivilegeEscalation"] + return config.Data["secCtxRunAsUser"], + config.Data["secCtxRunAsGroup"], + config.Data["secCtxAllowPrivilegeEscalation"], + config.Data["secCtx"] } // TODO eventually this can move to pkg/plugin/framework since it'll be used across multiple diff --git a/pkg/util/kube/security_context.go b/pkg/util/kube/security_context.go index 2011515ec..22c2c4ce8 100644 --- a/pkg/util/kube/security_context.go +++ b/pkg/util/kube/security_context.go @@ -21,9 +21,10 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/yaml" ) -func ParseSecurityContext(runAsUser string, runAsGroup string, allowPrivilegeEscalation string) (corev1.SecurityContext, error) { +func ParseSecurityContext(runAsUser string, runAsGroup string, allowPrivilegeEscalation string, secCtx string) (corev1.SecurityContext, error) { securityContext := corev1.SecurityContext{} if runAsUser != "" { @@ -53,5 +54,12 @@ func ParseSecurityContext(runAsUser string, runAsGroup string, allowPrivilegeEsc securityContext.AllowPrivilegeEscalation = &parsedAllowPrivilegeEscalation } + if secCtx != "" { + err := yaml.UnmarshalStrict([]byte(secCtx), &securityContext) + if err != nil { + return securityContext, errors.WithStack(errors.Errorf(`Security context secCtx error: "%s"`, err)) + } + } + return securityContext, nil } diff --git a/pkg/util/kube/security_context_test.go b/pkg/util/kube/security_context_test.go index 2e20974bc..8ad72e49c 100644 --- a/pkg/util/kube/security_context_test.go +++ b/pkg/util/kube/security_context_test.go @@ -30,6 +30,7 @@ func TestParseSecurityContext(t *testing.T) { runAsUser string runAsGroup string allowPrivilegeEscalation string + secCtx string } tests := []struct { name string @@ -37,33 +38,184 @@ func TestParseSecurityContext(t *testing.T) { wantErr bool expected *corev1.SecurityContext }{ - {"valid security context", args{"1001", "999", "true"}, false, &corev1.SecurityContext{ - RunAsUser: pointInt64(1001), - RunAsGroup: pointInt64(999), - AllowPrivilegeEscalation: boolptr.True(), - }}, + { + "valid security context", + args{"1001", "999", "true", ``}, + false, + &corev1.SecurityContext{ + RunAsUser: pointInt64(1001), + RunAsGroup: pointInt64(999), + AllowPrivilegeEscalation: boolptr.True(), + }, + }, + { + "valid security context with override runAsUser", + args{"1001", "999", "true", `runAsUser: 2000`}, + false, + &corev1.SecurityContext{ + RunAsUser: pointInt64(2000), + RunAsGroup: pointInt64(999), + AllowPrivilegeEscalation: boolptr.True(), + }, + }, + { + "valid securityContext with comments only secCtx key", + args{"", "", "",` +capabilities: + drop: + - ALL + add: + - cap1 + - cap2 +sELinuxOptions: + user: userLabel + role: roleLabel + type: typeLabel + level: levelLabel +# user www-data +runAsUser: 3333 +# group www-data +runAsGroup: 3333 +runAsNonRoot: true +readOnlyRootFilesystem: true +allowPrivilegeEscalation: false`}, + false, + &corev1.SecurityContext{ + RunAsUser: pointInt64(3333), + RunAsGroup: pointInt64(3333), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"cap1", "cap2"}, + }, + SELinuxOptions: &corev1.SELinuxOptions{ + User: "userLabel", + Role: "roleLabel", + Type: "typeLabel", + Level: "levelLabel", + }, + RunAsNonRoot: boolptr.True(), + ReadOnlyRootFilesystem: boolptr.True(), + AllowPrivilegeEscalation: boolptr.False(), + }, + }, + { + "valid securityContext with secCtx key override runAsUser runAsGroup and allowPrivilegeEscalation", + args{"1001", "999", "true",` +capabilities: + drop: + - ALL + add: + - cap1 + - cap2 +sELinuxOptions: + user: userLabel + role: roleLabel + type: typeLabel + level: levelLabel +# user www-data +runAsUser: 3333 +# group www-data +runAsGroup: 3333 +runAsNonRoot: true +readOnlyRootFilesystem: true +allowPrivilegeEscalation: false`}, + false, + &corev1.SecurityContext{ + RunAsUser: pointInt64(3333), + RunAsGroup: pointInt64(3333), + Capabilities: &corev1.Capabilities{ + Drop: []corev1.Capability{"ALL"}, + Add: []corev1.Capability{"cap1", "cap2"}, + }, + SELinuxOptions: &corev1.SELinuxOptions{ + User: "userLabel", + Role: "roleLabel", + Type: "typeLabel", + Level: "levelLabel", + }, + RunAsNonRoot: boolptr.True(), + ReadOnlyRootFilesystem: boolptr.True(), + AllowPrivilegeEscalation: boolptr.False(), + }, + }, { "another valid security context", - args{"1001", "999", "false"}, false, &corev1.SecurityContext{ + args{"1001", "999", "false", ""}, + false, + &corev1.SecurityContext{ RunAsUser: pointInt64(1001), RunAsGroup: pointInt64(999), AllowPrivilegeEscalation: boolptr.False(), }, }, - {"security context without runAsGroup", args{"1001", "", ""}, false, &corev1.SecurityContext{ + {"security context without runAsGroup", args{"1001", "", "", ""}, false, &corev1.SecurityContext{ RunAsUser: pointInt64(1001), }}, - {"security context without runAsUser", args{"", "999", ""}, false, &corev1.SecurityContext{ + {"security context without runAsUser", args{"", "999", "", ""}, false, &corev1.SecurityContext{ RunAsGroup: pointInt64(999), }}, - {"empty context without runAsUser", args{"", "", ""}, false, &corev1.SecurityContext{}}, - {"invalid security context runAsUser", args{"not a number", "", ""}, true, nil}, - {"invalid security context runAsGroup", args{"", "not a number", ""}, true, nil}, - {"invalid security context allowPrivilegeEscalation", args{"", "", "not a bool"}, true, nil}, + {"empty context without runAsUser", args{"", "", "", ""}, false, &corev1.SecurityContext{}}, + { + "invalid securityContext secCtx unknown key", + args{"", "", "",` +capabilitiesUnknownkey: + drop: + - ALL + add: + - cap1 + - cap2 +# user www-data +runAsUser: 3333 +# group www-data +runAsGroup: 3333 +runAsNonRoot: true +readOnlyRootFilesystem: true +allowPrivilegeEscalation: false`}, + true, nil, + }, + { + "invalid securityContext secCtx wrong value type string instead of bool", + args{"", "", "",` +capabilitiesUnknownkey: + drop: + - ALL + add: + - cap1 + - cap2 +# user www-data +runAsUser: 3333 +# group www-data +runAsGroup: 3333 +runAsNonRoot: plop +readOnlyRootFilesystem: true +allowPrivilegeEscalation: false`}, + true, nil, + }, + { + "invalid securityContext secCtx wrong value type string instead of int", + args{"", "", "",` +capabilitiesUnknownkey: + drop: + - ALL + add: + - cap1 + - cap2 +# user www-data +runAsUser: plop +# group www-data +runAsGroup: 3333 +runAsNonRoot: true +readOnlyRootFilesystem: true +allowPrivilegeEscalation: false`}, + true, nil, + }, + {"invalid security context runAsUser", args{"not a number", "", "", ""}, true, nil}, + {"invalid security context runAsGroup", args{"", "not a number", "", ""}, true, nil}, + {"invalid security context allowPrivilegeEscalation", args{"", "", "not a bool", ""}, true, nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got, err := ParseSecurityContext(tt.args.runAsUser, tt.args.runAsGroup, tt.args.allowPrivilegeEscalation) + got, err := ParseSecurityContext(tt.args.runAsUser, tt.args.runAsGroup, tt.args.allowPrivilegeEscalation, tt.args.secCtx) if tt.wantErr { assert.Error(t, err) return @@ -74,8 +226,7 @@ func TestParseSecurityContext(t *testing.T) { tt.expected = &corev1.SecurityContext{} } - assert.Equal(t, tt.expected.RunAsUser, got.RunAsUser) - assert.Equal(t, tt.expected.RunAsGroup, got.RunAsGroup) + assert.Equal(t, *tt.expected, got) }) } }