mirror of
https://github.com/vmware-tanzu/velero.git
synced 2026-08-15 11:46:06 +00:00
Fix restore-wait init container ignoring pod-level securityContext (#10047)
restore-wait's securityContext fallback chain checked the fs-restore ConfigMap, then the first container's SecurityContext, then hardcoded runAsUser 1000. It never consulted pod.Spec.SecurityContext, so pods that set identity only at the pod level got a helper running as uid 1000 regardless of the workload's actual uid. On volumes where restored content is owner-only-visible to a non-1000 uid, the helper's stat on the done-file returns EACCES forever and the pod deadlocks at Init:0/1. Add pod-level spec.securityContext.runAsUser/runAsGroup as a fallback between the container-level check and the hardcoded default, since the workload's own identity is the one that can read what it restored. Defer to the pod's own RunAsNonRoot setting when runAsUser is 0, since the hardcoded RunAsNonRoot: true would otherwise contradict a root uid. Also add a test case covering both container-level and pod-level SecurityContext set together, confirming container-level still wins. Fixes #10046 Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
This commit is contained in:
@@ -0,0 +1 @@
|
||||
Fix restore-wait init container ignoring pod-level securityContext, falling back to hardcoded runAsUser 1000 instead of the workload's own uid/gid, causing fs-backup restores to deadlock at Init:0/1 on owner-restricted volumes
|
||||
@@ -198,6 +198,25 @@ func (a *PodVolumeRestoreAction) Execute(input *velero.RestoreItemActionExecuteI
|
||||
securityContext = *pod.Spec.Containers[0].SecurityContext.DeepCopy()
|
||||
securityContextSet = true
|
||||
}
|
||||
// if no configmap or container-level securityContext is set, fall back to the pod-level
|
||||
// spec.securityContext runAsUser/runAsGroup: the workload's own identity is the one that
|
||||
// wrote the restored files, so it's the one that can read them back
|
||||
if !securityContextSet && pod.Spec.SecurityContext != nil &&
|
||||
(pod.Spec.SecurityContext.RunAsUser != nil || pod.Spec.SecurityContext.RunAsGroup != nil) {
|
||||
securityContext = defaultSecurityCtx()
|
||||
if pod.Spec.SecurityContext.RunAsUser != nil {
|
||||
securityContext.RunAsUser = pod.Spec.SecurityContext.RunAsUser
|
||||
// defaultSecurityCtx() hardcodes RunAsNonRoot: true, which contradicts a pod-level
|
||||
// RunAsUser of 0 (root); defer to the pod's own RunAsNonRoot setting in that case
|
||||
if *pod.Spec.SecurityContext.RunAsUser == 0 {
|
||||
securityContext.RunAsNonRoot = pod.Spec.SecurityContext.RunAsNonRoot
|
||||
}
|
||||
}
|
||||
if pod.Spec.SecurityContext.RunAsGroup != nil {
|
||||
securityContext.RunAsGroup = pod.Spec.SecurityContext.RunAsGroup
|
||||
}
|
||||
securityContextSet = true
|
||||
}
|
||||
if !securityContextSet {
|
||||
securityContext = defaultSecurityCtx()
|
||||
}
|
||||
|
||||
@@ -156,6 +156,155 @@ func TestPodVolumeRestoreActionExecute(t *testing.T) {
|
||||
|
||||
defaultRestoreHelperImage := "velero/velero:v1.0"
|
||||
|
||||
podLevelUID := int64(999)
|
||||
podLevelGID := int64(999)
|
||||
podLevelSecurityContext := corev1api.SecurityContext{
|
||||
AllowPrivilegeEscalation: boolptr.False(),
|
||||
Capabilities: &corev1api.Capabilities{
|
||||
Drop: []corev1api.Capability{"ALL"},
|
||||
},
|
||||
SeccompProfile: &corev1api.SeccompProfile{
|
||||
Type: corev1api.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
RunAsUser: &podLevelUID,
|
||||
RunAsGroup: &podLevelGID,
|
||||
RunAsNonRoot: boolptr.True(),
|
||||
}
|
||||
|
||||
podWithPodLevelSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
Result()
|
||||
podWithPodLevelSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &podLevelUID, RunAsGroup: &podLevelGID}
|
||||
|
||||
wantPodWithPodLevelSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
InitContainers(
|
||||
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&podLevelSecurityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
|
||||
Command([]string{"/velero-restore-helper"}).Result()).
|
||||
Result()
|
||||
wantPodWithPodLevelSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &podLevelUID, RunAsGroup: &podLevelGID}
|
||||
|
||||
podLevelRootUID := int64(0)
|
||||
podLevelRootSecurityContext := corev1api.SecurityContext{
|
||||
AllowPrivilegeEscalation: boolptr.False(),
|
||||
Capabilities: &corev1api.Capabilities{
|
||||
Drop: []corev1api.Capability{"ALL"},
|
||||
},
|
||||
SeccompProfile: &corev1api.SeccompProfile{
|
||||
Type: corev1api.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
RunAsUser: &podLevelRootUID,
|
||||
}
|
||||
|
||||
podWithPodLevelRootSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
Result()
|
||||
podWithPodLevelRootSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &podLevelRootUID}
|
||||
|
||||
wantPodWithPodLevelRootSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
InitContainers(
|
||||
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&podLevelRootSecurityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
|
||||
Command([]string{"/velero-restore-helper"}).Result()).
|
||||
Result()
|
||||
wantPodWithPodLevelRootSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &podLevelRootUID}
|
||||
|
||||
podLevelGroupOnlyGID := int64(777)
|
||||
podLevelGroupOnlySecurityContext := corev1api.SecurityContext{
|
||||
AllowPrivilegeEscalation: boolptr.False(),
|
||||
Capabilities: &corev1api.Capabilities{
|
||||
Drop: []corev1api.Capability{"ALL"},
|
||||
},
|
||||
SeccompProfile: &corev1api.SeccompProfile{
|
||||
Type: corev1api.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
RunAsUser: &id,
|
||||
RunAsGroup: &podLevelGroupOnlyGID,
|
||||
RunAsNonRoot: boolptr.True(),
|
||||
}
|
||||
|
||||
podWithPodLevelGroupOnlySecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
Result()
|
||||
podWithPodLevelGroupOnlySecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsGroup: &podLevelGroupOnlyGID}
|
||||
|
||||
wantPodWithPodLevelGroupOnlySecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
InitContainers(
|
||||
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&podLevelGroupOnlySecurityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
|
||||
Command([]string{"/velero-restore-helper"}).Result()).
|
||||
Result()
|
||||
wantPodWithPodLevelGroupOnlySecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsGroup: &podLevelGroupOnlyGID}
|
||||
|
||||
bothLevelsPodUID := int64(500)
|
||||
bothLevelsContainerUID := int64(999)
|
||||
bothLevelsContainerSecurityContext := corev1api.SecurityContext{
|
||||
AllowPrivilegeEscalation: boolptr.False(),
|
||||
Capabilities: &corev1api.Capabilities{
|
||||
Drop: []corev1api.Capability{"ALL"},
|
||||
},
|
||||
SeccompProfile: &corev1api.SeccompProfile{
|
||||
Type: corev1api.SeccompProfileTypeRuntimeDefault,
|
||||
},
|
||||
RunAsUser: &bothLevelsContainerUID,
|
||||
RunAsNonRoot: boolptr.True(),
|
||||
}
|
||||
|
||||
podWithBothLevelsSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
Containers(
|
||||
builder.ForContainer("app-container", "app-image").
|
||||
SecurityContext(&bothLevelsContainerSecurityContext).Result()).
|
||||
Result()
|
||||
podWithBothLevelsSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &bothLevelsPodUID}
|
||||
|
||||
wantPodWithBothLevelsSecurityContext := builder.ForPod("ns-1", "my-pod").
|
||||
ObjectMeta(builder.WithAnnotations("snapshot.velero.io/myvol", "")).
|
||||
Volumes(
|
||||
builder.ForVolume("myvol").PersistentVolumeClaimSource("pvc-1").Result(),
|
||||
).
|
||||
Containers(
|
||||
builder.ForContainer("app-container", "app-image").
|
||||
SecurityContext(&bothLevelsContainerSecurityContext).Result()).
|
||||
InitContainers(
|
||||
newRestoreInitContainerBuilder(defaultRestoreHelperImage, "").
|
||||
Resources(&resourceReqs).
|
||||
SecurityContext(&bothLevelsContainerSecurityContext).
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
|
||||
Command([]string{"/velero-restore-helper"}).Result()).
|
||||
Result()
|
||||
wantPodWithBothLevelsSecurityContext.Spec.SecurityContext = &corev1api.PodSecurityContext{RunAsUser: &bothLevelsPodUID}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
pod *corev1api.Pod
|
||||
@@ -350,6 +499,62 @@ func TestPodVolumeRestoreActionExecute(t *testing.T) {
|
||||
VolumeMounts(builder.ForVolumeMount("myvol", "/restores/myvol").Result()).
|
||||
Command([]string{"/velero-restore-helper"}).Result()).Result(),
|
||||
},
|
||||
{
|
||||
name: "Restoring pod with pod-level securityContext (no container-level SecurityContext) uses pod-level runAsUser/runAsGroup for the restore initContainer",
|
||||
pod: podWithPodLevelSecurityContext,
|
||||
podVolumeBackups: []runtime.Object{
|
||||
builder.ForPodVolumeBackup(veleroNs, "pvb-1").
|
||||
PodName("my-pod").
|
||||
PodNamespace("ns-1").
|
||||
Volume("myvol").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, backupName)).
|
||||
SnapshotID("foo").
|
||||
Result(),
|
||||
},
|
||||
want: wantPodWithPodLevelSecurityContext,
|
||||
},
|
||||
{
|
||||
name: "Restoring pod with pod-level securityContext.runAsUser=0 does not force RunAsNonRoot on the restore initContainer",
|
||||
pod: podWithPodLevelRootSecurityContext,
|
||||
podVolumeBackups: []runtime.Object{
|
||||
builder.ForPodVolumeBackup(veleroNs, "pvb-1").
|
||||
PodName("my-pod").
|
||||
PodNamespace("ns-1").
|
||||
Volume("myvol").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, backupName)).
|
||||
SnapshotID("foo").
|
||||
Result(),
|
||||
},
|
||||
want: wantPodWithPodLevelRootSecurityContext,
|
||||
},
|
||||
{
|
||||
name: "Restoring pod with pod-level securityContext.runAsGroup only (no runAsUser) still applies the group to the restore initContainer",
|
||||
pod: podWithPodLevelGroupOnlySecurityContext,
|
||||
podVolumeBackups: []runtime.Object{
|
||||
builder.ForPodVolumeBackup(veleroNs, "pvb-1").
|
||||
PodName("my-pod").
|
||||
PodNamespace("ns-1").
|
||||
Volume("myvol").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, backupName)).
|
||||
SnapshotID("foo").
|
||||
Result(),
|
||||
},
|
||||
want: wantPodWithPodLevelGroupOnlySecurityContext,
|
||||
},
|
||||
{
|
||||
name: "Restoring pod with both container-level and pod-level SecurityContext set uses the container-level SecurityContext for the restore initContainer (container-level takes priority)",
|
||||
pod: podWithBothLevelsSecurityContext,
|
||||
podVolumeBackups: []runtime.Object{
|
||||
builder.ForPodVolumeBackup(veleroNs, "pvb-1").
|
||||
PodName("my-pod").
|
||||
PodNamespace("ns-1").
|
||||
Volume("myvol").
|
||||
ObjectMeta(builder.WithLabels(velerov1api.BackupNameLabel, backupName)).
|
||||
SnapshotID("foo").
|
||||
Result(),
|
||||
},
|
||||
want: wantPodWithBothLevelsSecurityContext,
|
||||
},
|
||||
{
|
||||
name: "pod volume backups in a different namespace are ignored when looking for matches due to namespace scoping",
|
||||
pod: builder.ForPod("ns-1", "my-pod").
|
||||
|
||||
Reference in New Issue
Block a user