Skip to content

Commit aa5105b

Browse files
mitchrossclaude
andcommitted
fix(kopiur): run mover as data-owner uid:gid (not root) for baseline PSS
Under baseline Pod Security the mover can't use CAP_DAC_READ_SEARCH and kopiur's privilegedMode adds no caps, so a root mover can't read non-root / mode-600/700 data (n8n, gitea 700-dir, mysql 999:568 all failed). Each per-PVC stub now runs its backup+restore mover as the DATA owner uid:gid + supplementalGroups[gid] (determined live); the component no longer injects a mover. Verified: zero PermissionDenied across all 24 PVCs (only failure was a Longhorn CSI transient on swarmui-output). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 7f56430 commit aa5105b

25 files changed

Lines changed: 400 additions & 35 deletions

File tree

my-apps/ai/open-webui/kopiur/storage.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 568
24+
runAsGroup: 568
25+
runAsNonRoot: true
26+
podSecurityContext:
27+
fsGroup: 568
28+
supplementalGroups:
29+
- 568
2130
---
2231
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2332
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +51,12 @@ spec:
4251
fromPolicy:
4352
name: storage
4453
offset: 0
54+
mover:
55+
securityContext:
56+
runAsUser: 568
57+
runAsGroup: 568
58+
runAsNonRoot: true
59+
podSecurityContext:
60+
fsGroup: 568
61+
supplementalGroups:
62+
- 568

my-apps/ai/perplexica/kopiur/perplexica-data.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 568
24+
runAsGroup: 568
25+
runAsNonRoot: true
26+
podSecurityContext:
27+
fsGroup: 568
28+
supplementalGroups:
29+
- 568
2130
---
2231
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2332
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +51,12 @@ spec:
4251
fromPolicy:
4352
name: perplexica-data
4453
offset: 0
54+
mover:
55+
securityContext:
56+
runAsUser: 568
57+
runAsGroup: 568
58+
runAsNonRoot: true
59+
podSecurityContext:
60+
fsGroup: 568
61+
supplementalGroups:
62+
- 568

my-apps/ai/swarmui/kopiur/swarmui-data.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 0
24+
runAsNonRoot: false
2125
---
2226
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2327
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +46,7 @@ spec:
4246
fromPolicy:
4347
name: swarmui-data
4448
offset: 0
49+
mover:
50+
securityContext:
51+
runAsUser: 0
52+
runAsNonRoot: false

my-apps/ai/swarmui/kopiur/swarmui-output.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 0
24+
runAsNonRoot: false
2125
---
2226
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2327
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +46,7 @@ spec:
4246
fromPolicy:
4347
name: swarmui-output
4448
offset: 0
49+
mover:
50+
securityContext:
51+
runAsUser: 0
52+
runAsNonRoot: false

my-apps/common/kopiur-backup/kustomization.yaml

Lines changed: 18 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,26 @@ kind: Component
55
# Include from an app kustomization with:
66
# components:
77
# - ../../common/kopiur-backup
8-
# then add a per-PVC stub (SnapshotPolicy + SnapshotSchedule + Restore carrying
9-
# only the VARYING bits: name, sources.pvc.name, identity, schedule.cron, and the
10-
# Restore mover UID) plus `dataSourceRef -> <pvc>-restore` on the app's PVC.
8+
# then add a per-PVC stub (SnapshotPolicy + SnapshotSchedule + Restore). The
9+
# stub carries the per-PVC VARYING bits — name, sources.pvc.name, identity,
10+
# schedule.cron, AND the mover securityContext (run the mover as the DATA owner
11+
# uid:gid + supplementalGroups[gid]). Plus `dataSourceRef -> <pvc>-restore` on
12+
# the app's PVC.
1113
#
12-
# This component injects every UNIFORM field by kind, so the per-PVC stub stays
13-
# tiny and the fumble-prone fields (repository ref, populator, mover SC nesting)
14-
# live in ONE place. Prereqs (once per namespace): label the namespace
15-
# `kopiur.home-operations.com/repo: cluster-kopia` (ESO cred fanout + repo
16-
# tenancy via the ClusterRepository allowedNamespaces selector).
14+
# WHY the mover UID is per-PVC, not injected here: under baseline Pod Security
15+
# the mover can't use capabilities (no CAP_DAC_READ_SEARCH), and kopiur's
16+
# privilegedMode does NOT add caps — so a root mover cannot read non-root /
17+
# mode-600/700 data. The mover MUST run as the data's owner (proven 2026-06-26:
18+
# n8n 1000, gitea 700-dir 1000, mysql 999:568). Ownership also varies within a
19+
# namespace (project-nomad: 1000 / 999:568 / 568), so it can't be uniform here.
20+
# This component injects only the truly-uniform, non-mover fields by kind.
1721
#
18-
# Why not selector-based (one policy backs up all labeled PVCs)? kopiur's
19-
# selector-policy + per-PVC populator-Restore combo is undocumented (0.4.x), so
20-
# we use the per-PVC pattern proven by the 2026-06-26 karakeep DR drill.
22+
# Prereqs (once per namespace): label `kopiur.home-operations.com/repo:
23+
# cluster-kopia` (ESO cred fanout + repo tenancy via the ClusterRepository
24+
# allowedNamespaces selector). The privileged-movers namespace annotation is
25+
# only needed for stubs whose mover runs as root (uid 0).
2126
patches:
22-
# --- SnapshotPolicy: uniform backup config -------------------------------
27+
# --- SnapshotPolicy: uniform backup config (mover lives in the stub) ------
2328
- target:
2429
kind: SnapshotPolicy
2530
group: kopiur.home-operations.com
@@ -35,18 +40,6 @@ patches:
3540
value:
3641
kind: ClusterRepository
3742
name: cluster-kopia
38-
# Privileged root mover — reads any source ownership and preserves
39-
# original UID/GID on restore (the analog of VolSync privileged-movers,
40-
# which every backed-up namespace already used). Uniform across apps, so
41-
# no per-app UID. Requires the namespace annotation
42-
# kopiur.home-operations.com/privileged-movers: "true" (else MoverPermitted=False).
43-
- op: add
44-
path: /spec/mover
45-
value:
46-
securityContext:
47-
runAsUser: 0
48-
runAsNonRoot: false
49-
privilegedMode: true
5043
# --- SnapshotSchedule: uniform scheduling --------------------------------
5144
- target:
5245
kind: SnapshotSchedule
@@ -58,7 +51,7 @@ patches:
5851
- op: add
5952
path: /spec/schedule/runOnCreate
6053
value: false
61-
# --- Restore: uniform passive-populator config ---------------------------
54+
# --- Restore: uniform passive-populator config (mover lives in the stub) --
6255
- target:
6356
kind: Restore
6457
group: kopiur.home-operations.com
@@ -76,13 +69,3 @@ patches:
7669
path: /spec/policy
7770
value:
7871
onMissingSnapshot: Continue
79-
# Same privileged root mover — restores files with their ORIGINAL owner
80-
# (the consumer pod doesn't exist during a cold DR restore, so inherit
81-
# can't resolve; privilegedMode chowns to the backed-up UID/GID).
82-
- op: add
83-
path: /spec/mover
84-
value:
85-
securityContext:
86-
runAsUser: 0
87-
runAsNonRoot: false
88-
privilegedMode: true

my-apps/development/gitea/kopiur/gitea-shared-storage.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,15 @@ spec:
1919
keepDaily: 14
2020
keepWeekly: 6
2121
keepMonthly: 3
22+
mover:
23+
securityContext:
24+
runAsUser: 1000
25+
runAsGroup: 1000
26+
runAsNonRoot: true
27+
podSecurityContext:
28+
fsGroup: 1000
29+
supplementalGroups:
30+
- 1000
2231
---
2332
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2433
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -43,3 +52,12 @@ spec:
4352
fromPolicy:
4453
name: gitea-shared-storage
4554
offset: 0
55+
mover:
56+
securityContext:
57+
runAsUser: 1000
58+
runAsGroup: 1000
59+
runAsNonRoot: true
60+
podSecurityContext:
61+
fsGroup: 1000
62+
supplementalGroups:
63+
- 1000

my-apps/development/nginx/kopiur/storage.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 0
24+
runAsNonRoot: false
2125
---
2226
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2327
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +46,7 @@ spec:
4246
fromPolicy:
4347
name: storage
4448
offset: 0
49+
mover:
50+
securityContext:
51+
runAsUser: 0
52+
runAsNonRoot: false

my-apps/home/frigate/kopiur/frigate-config.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ spec:
1818
keepDaily: 14
1919
keepWeekly: 6
2020
keepMonthly: 3
21+
mover:
22+
securityContext:
23+
runAsUser: 568
24+
runAsGroup: 568
25+
runAsNonRoot: true
26+
podSecurityContext:
27+
fsGroup: 568
28+
supplementalGroups:
29+
- 568
2130
---
2231
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2332
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +51,12 @@ spec:
4251
fromPolicy:
4352
name: frigate-config
4453
offset: 0
54+
mover:
55+
securityContext:
56+
runAsUser: 568
57+
runAsGroup: 568
58+
runAsNonRoot: true
59+
podSecurityContext:
60+
fsGroup: 568
61+
supplementalGroups:
62+
- 568

my-apps/home/home-assistant/kopiur/config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ spec:
1818
keepHourly: 24
1919
keepDaily: 7
2020
keepWeekly: 4
21+
mover:
22+
securityContext:
23+
runAsUser: 0
24+
runAsNonRoot: false
2125
---
2226
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2327
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +46,7 @@ spec:
4246
fromPolicy:
4347
name: config
4448
offset: 0
49+
mover:
50+
securityContext:
51+
runAsUser: 0
52+
runAsNonRoot: false

my-apps/home/n8n/kopiur/data.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,15 @@ spec:
1818
keepHourly: 24
1919
keepDaily: 7
2020
keepWeekly: 4
21+
mover:
22+
securityContext:
23+
runAsUser: 1000
24+
runAsGroup: 1000
25+
runAsNonRoot: true
26+
podSecurityContext:
27+
fsGroup: 1000
28+
supplementalGroups:
29+
- 1000
2130
---
2231
# yaml-language-server: $schema=https://k8s-schemas.home-operations.com/kopiur.home-operations.com/snapshotschedule_v1alpha1.json
2332
apiVersion: kopiur.home-operations.com/v1alpha1
@@ -42,3 +51,12 @@ spec:
4251
fromPolicy:
4352
name: data
4453
offset: 0
54+
mover:
55+
securityContext:
56+
runAsUser: 1000
57+
runAsGroup: 1000
58+
runAsNonRoot: true
59+
podSecurityContext:
60+
fsGroup: 1000
61+
supplementalGroups:
62+
- 1000

0 commit comments

Comments
 (0)