mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-07-31 12:32:44 +00:00
add support for setting SecurityContext (user, group) for restic restore (#2621)
* add support for setting SecurityContext (user, group) for restic restore Signed-off-by: Martin Odstrcilik <martin.odstrcilik@gmail.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
restic: add support for setting SecurityContext (runAsUser, runAsGroup) for restore
|
||||
@@ -88,6 +88,12 @@ func (b *ContainerBuilder) Resources(resources *corev1api.ResourceRequirements)
|
||||
return b
|
||||
}
|
||||
|
||||
// SecurityContext sets the container's SecurityContext.
|
||||
func (b *ContainerBuilder) SecurityContext(securityContext *corev1api.SecurityContext) *ContainerBuilder {
|
||||
b.object.SecurityContext = securityContext
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *ContainerBuilder) Env(vars ...*corev1api.EnvVar) *ContainerBuilder {
|
||||
for _, v := range vars {
|
||||
b.object.Env = append(b.object.Env, *v)
|
||||
|
||||
@@ -130,8 +130,16 @@ func (a *ResticRestoreAction) Execute(input *velero.RestoreItemActionExecuteInpu
|
||||
)
|
||||
}
|
||||
|
||||
runAsRoot, runAsGroup := getSecurityContext(log, config)
|
||||
|
||||
securityContext, err := kube.ParseSecurityContext(runAsRoot, runAsGroup)
|
||||
if err != nil {
|
||||
log.Errorf("Using default resource values, couldn't parse resource requirements: %s.", err)
|
||||
}
|
||||
|
||||
initContainerBuilder := newResticInitContainerBuilder(image, string(input.Restore.UID))
|
||||
initContainerBuilder.Resources(&resourceReqs)
|
||||
initContainerBuilder.SecurityContext(&securityContext)
|
||||
|
||||
for volumeName := range volumeSnapshots {
|
||||
mount := &corev1.VolumeMount{
|
||||
@@ -211,6 +219,16 @@ func getResourceLimits(log logrus.FieldLogger, config *corev1.ConfigMap) (string
|
||||
return config.Data["cpuLimit"], config.Data["memLimit"]
|
||||
}
|
||||
|
||||
// getSecurityContext extracts securityContext runAsUser and runAsGroup from a ConfigMap.
|
||||
func getSecurityContext(log logrus.FieldLogger, config *corev1.ConfigMap) (string, string) {
|
||||
if config == nil {
|
||||
log.Debug("No config found for plugin")
|
||||
return "", ""
|
||||
}
|
||||
|
||||
return config.Data["secCtxRunAsUser"], config.Data["secCtxRunAsGroup"]
|
||||
}
|
||||
|
||||
// TODO eventually this can move to pkg/plugin/framework since it'll be used across multiple
|
||||
// plugins.
|
||||
func getPluginConfig(kind framework.PluginKind, name string, client corev1client.ConfigMapInterface) (*corev1.ConfigMap, error) {
|
||||
|
||||
@@ -111,6 +111,8 @@ func TestResticRestoreActionExecute(t *testing.T) {
|
||||
defaultCPURequestLimit, defaultMemRequestLimit, // limits
|
||||
)
|
||||
|
||||
securityContext, _ := kube.ParseSecurityContext("", "")
|
||||
|
||||
var (
|
||||
restoreName = "my-restore"
|
||||
backupName = "test-backup"
|
||||
@@ -134,6 +136,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
|
||||
InitContainers(
|
||||
newResticInitContainerBuilder(initContainerImage(defaultImageBase), "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&securityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).Result()).
|
||||
Result(),
|
||||
},
|
||||
@@ -150,6 +153,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
|
||||
InitContainers(
|
||||
newResticInitContainerBuilder(initContainerImage(defaultImageBase), "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&securityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).Result(),
|
||||
builder.ForContainer("first-container", "").Result()).
|
||||
Result(),
|
||||
@@ -189,6 +193,7 @@ func TestResticRestoreActionExecute(t *testing.T) {
|
||||
InitContainers(
|
||||
newResticInitContainerBuilder(initContainerImage(defaultImageBase), "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&securityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("vol-1", "/restores/vol-1").Result(), builder.ForVolumeMount("vol-2", "/restores/vol-2").Result()).Result(),
|
||||
builder.ForContainer("first-container", "").Result()).
|
||||
Result(),
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright 2019 the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func ParseSecurityContext(runAsUser string, runAsGroup string) (corev1.SecurityContext, error) {
|
||||
securityContext := corev1.SecurityContext{}
|
||||
|
||||
if runAsUser != "" {
|
||||
parsedRunAsUser, err := strconv.ParseInt(runAsUser, 10, 64)
|
||||
if err != nil {
|
||||
return securityContext, errors.WithStack(errors.Errorf(`Security context runAsUser "%s" is not a number`, runAsUser))
|
||||
}
|
||||
|
||||
securityContext.RunAsUser = &parsedRunAsUser
|
||||
}
|
||||
|
||||
if runAsGroup != "" {
|
||||
parsedRunAsGroup, err := strconv.ParseInt(runAsGroup, 10, 64)
|
||||
if err != nil {
|
||||
return securityContext, errors.WithStack(errors.Errorf(`Security context runAsGroup "%s" is not a number`, runAsGroup))
|
||||
}
|
||||
|
||||
securityContext.RunAsGroup = &parsedRunAsGroup
|
||||
}
|
||||
|
||||
return securityContext, nil
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
Copyright 2019 the Velero contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package kube
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
func TestParseSecurityContext(t *testing.T) {
|
||||
type args struct {
|
||||
runAsUser string
|
||||
runAsGroup string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
expected *corev1.SecurityContext
|
||||
}{
|
||||
{"valid security context", args{"1001", "999"}, false, &corev1.SecurityContext{
|
||||
RunAsUser: pointInt64(1001),
|
||||
RunAsGroup: pointInt64(999),
|
||||
}},
|
||||
{"security context without runAsGroup", args{"1001", ""}, false, &corev1.SecurityContext{
|
||||
RunAsUser: pointInt64(1001),
|
||||
}},
|
||||
{"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},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseSecurityContext(tt.args.runAsUser, tt.args.runAsGroup)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
if tt.expected == nil {
|
||||
tt.expected = &corev1.SecurityContext{}
|
||||
}
|
||||
|
||||
assert.Equal(t, tt.expected.RunAsUser, got.RunAsUser)
|
||||
assert.Equal(t, tt.expected.RunAsGroup, got.RunAsGroup)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func pointInt64(i int64) *int64 {
|
||||
return &i
|
||||
}
|
||||
@@ -380,7 +380,11 @@ 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: 1001
|
||||
|
||||
# "secCtxRunAsGroup sets the securityContext.runAsGroup value on the restic init containers during restore."
|
||||
secCtxRunAsGroup: 999
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Reference in New Issue
Block a user