[release-1.17] Backport #10047: Fix restore-wait init container ignoring pod-level securityContext (#10223)
Run the E2E test on kind / build (push) Failing after 3m48s
Run the E2E test on kind / setup-test-matrix (push) Successful in 4s
Run the E2E test on kind / run-e2e-test (push) Skipped
Main CI / Build (push) Failing after 52s

* Backport PR #10047: Fix restore-wait init container ignoring pod-level securityContext

Co-authored-by: kaovilai <11228024+kaovilai@users.noreply.github.com>

* Create changelog for unreleased version 10223

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>

---------

Signed-off-by: Tiger Kaovilai <tkaovila@redhat.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kaovilai <11228024+kaovilai@users.noreply.github.com>
Co-authored-by: Tiger Kaovilai <tkaovila@redhat.com>
This commit is contained in:
Copilot
2026-08-11 16:26:28 +00:00
committed by GitHub
co-authored by kaovilai copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Tiger Kaovilai
parent 98b5a28884
commit 0fb6a37bbf
3 changed files with 225 additions and 0 deletions
+1
View File
@@ -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
@@ -190,6 +190,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()
}
@@ -154,6 +154,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
@@ -348,6 +497,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,
},
}
veleroDeployment := &appsv1api.Deployment{