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:
Martin Odstrčilík
2020-07-22 12:10:25 -07:00
committed by GitHub
parent 91ccc4adb2
commit 86efd1577e
7 changed files with 154 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
restic: add support for setting SecurityContext (runAsUser, runAsGroup) for restore
+6
View File
@@ -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)
+18
View File
@@ -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(),
+48
View File
@@ -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
}
+72
View File
@@ -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
}
+4
View File
@@ -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