mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-27 19:36:19 +00:00
Merge pull request #4084 from MatthieuFin/main
Add full support for setting securityContext for restic restore
This commit is contained in:
@@ -0,0 +1 @@
|
||||
restic: add full support for setting SecurityContext for restore init container from configMap.
|
||||
@@ -46,6 +46,7 @@ require (
|
||||
k8s.io/kube-aggregator v0.19.12
|
||||
sigs.k8s.io/cluster-api v1.0.0
|
||||
sigs.k8s.io/controller-runtime v0.10.2
|
||||
sigs.k8s.io/yaml v1.3.0
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -120,7 +121,6 @@ require (
|
||||
k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e // indirect
|
||||
k8s.io/utils v0.0.0-20210930125809-cb0fa318a74b // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.1.2 // indirect
|
||||
sigs.k8s.io/yaml v1.3.0 // indirect
|
||||
)
|
||||
|
||||
replace github.com/gogo/protobuf => github.com/gogo/protobuf v1.3.2
|
||||
|
||||
@@ -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))
|
||||
@@ -246,14 +246,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
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
|
||||
defaultCPURequestLimit, defaultMemRequestLimit, // limits
|
||||
)
|
||||
|
||||
securityContext, _ := kube.ParseSecurityContext("", "", "")
|
||||
securityContext, _ := kube.ParseSecurityContext("", "", "", "")
|
||||
|
||||
var (
|
||||
restoreName = "my-restore"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ func TestParseSecurityContext(t *testing.T) {
|
||||
runAsUser string
|
||||
runAsGroup string
|
||||
allowPrivilegeEscalation string
|
||||
secCtx string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -37,34 +38,225 @@ 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 comments only secCtx key check seLinuxOptions is correctly parsed",
|
||||
args{"", "", "", `
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add:
|
||||
- cap1
|
||||
- cap2
|
||||
seLinuxOptions:
|
||||
user: userLabelFail
|
||||
role: roleLabel
|
||||
type: typeLabel
|
||||
level: levelLabel
|
||||
# user www-data
|
||||
runAsUser: 3333
|
||||
# group www-data
|
||||
runAsGroup: 3333
|
||||
runAsNonRoot: true
|
||||
readOnlyRootFilesystem: true
|
||||
allowPrivilegeEscalation: false`},
|
||||
true,
|
||||
&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)
|
||||
if tt.wantErr {
|
||||
got, err := ParseSecurityContext(tt.args.runAsUser, tt.args.runAsGroup, tt.args.allowPrivilegeEscalation, tt.args.secCtx)
|
||||
if err != nil && tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
@@ -74,8 +266,11 @@ 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)
|
||||
if tt.wantErr {
|
||||
assert.NotEqual(t, *tt.expected, got)
|
||||
return
|
||||
}
|
||||
assert.Equal(t, *tt.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,11 +393,27 @@ data:
|
||||
# If not set, it will default to "128Mi". A value of "0" is treated as unbounded.
|
||||
memLimit: 128Mi
|
||||
|
||||
# "secCtxRunAsUser sets the securityContext.runAsUser value on the restic init containers during restore."
|
||||
# "secCtxRunAsUser" sets the securityContext.runAsUser value on the restic init containers during restore.
|
||||
secCtxRunAsUser: 1001
|
||||
|
||||
# "secCtxRunAsGroup sets the securityContext.runAsGroup value on the restic init containers during restore."
|
||||
# "secCtxRunAsGroup" sets the securityContext.runAsGroup value on the restic init containers during restore.
|
||||
secCtxRunAsGroup: 999
|
||||
|
||||
# "secCtxAllowPrivilegeEscalation" sets the securityContext.allowPrivilegeEscalation value on the restic init containers during restore.
|
||||
secCtxAllowPrivilegeEscalation: false
|
||||
|
||||
# "secCtx" sets the securityContext object value on the restic init containers during restore.
|
||||
# This key override `secCtxRunAsUser`, `secCtxRunAsGroup`, `secCtxAllowPrivilegeEscalation` if `secCtx.runAsUser`, `secCtx.runAsGroup` or `secCtx.allowPrivilegeEscalation` are set.
|
||||
secCtx: |
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
add: []
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
runAsUser: 1001
|
||||
runAsGroup: 999
|
||||
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Reference in New Issue
Block a user