Skip to content

Commit 8d7fa9a

Browse files
feat(platform): add deletion-protection guardrail via ValidatingAdmissionPolicy
Block accidental DELETE on critical platform objects (tenant-root, cozy-system, LinstorCluster, CRDs, ClusterIssuers, cozystack-version ConfigMap, cozystack-packages OCIRepository, tenant-root HelmRelease) via a label-scoped ValidatingAdmissionPolicy. No webhook DaemonSet, Service, TLS, or image — the API server evaluates the CEL expression in-process. Requires Kubernetes 1.30+. Bypass for legitimate deletes is two steps: kubectl label <kind> <name> platform.cozystack.io/no-delete- kubectl delete <kind> <name> CRDs installed dynamically by the operator get the label stamped in internal/crdinstall/install.go before the apply call. Release note: Added deletion-protection guardrail (ValidatingAdmissionPolicy) blocking DELETE on cozystack platform objects labeled platform.cozystack.io/no-delete=true. Bypass by removing the label first. Assisted-By: Claude <[email protected]> Signed-off-by: Myasnikov Daniil <[email protected]>
1 parent 680803a commit 8d7fa9a

9 files changed

Lines changed: 76 additions & 6 deletions

File tree

‎internal/crdinstall/install.go‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest
8989
}
9090
}
9191

92+
// Stamp the deletion-protection label so the platform VAP guards every
93+
// Cozystack CRD against accidental kubectl delete.
94+
for _, obj := range objects {
95+
labels := obj.GetLabels()
96+
if labels == nil {
97+
labels = make(map[string]string)
98+
}
99+
labels["platform.cozystack.io/no-delete"] = "true"
100+
obj.SetLabels(labels)
101+
}
102+
92103
logger.Info("Applying Cozystack CRDs", "count", len(objects))
93104
for _, obj := range objects {
94105
patchOptions := &client.PatchOptions{

‎packages/core/installer/templates/cozy-system-labels.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,4 +149,5 @@ spec:
149149
- namespace
150150
- cozy-system
151151
- cozystack.io/system=true
152+
- platform.cozystack.io/no-delete=true
152153
- pod-security.kubernetes.io/enforce=privileged

‎packages/core/installer/templates/cozy-system-namespace.yaml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ metadata:
1919
name: cozy-system
2020
labels:
2121
cozystack.io/system: "true"
22+
platform.cozystack.io/no-delete: "true"
2223
pod-security.kubernetes.io/enforce: privileged
2324
annotations:
2425
helm.sh/resource-policy: keep

‎packages/core/platform/templates/cozystack-version.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ kind: ConfigMap
66
metadata:
77
name: cozystack-version
88
namespace: {{ .Release.Namespace }}
9+
labels:
10+
platform.cozystack.io/no-delete: "true"
911
annotations:
1012
helm.sh/resource-policy: keep
1113
data:
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
{{/*
2+
ValidatingAdmissionPolicy that blocks DELETE on any object carrying the
3+
platform.cozystack.io/no-delete=true label. Native, in-process CEL eval —
4+
no webhook DaemonSet, no Service, no TLS, no image to maintain.
5+
6+
Requires Kubernetes 1.30+ (admissionregistration.k8s.io/v1 GA).
7+
8+
Bypass: remove the label, then delete.
9+
kubectl label <kind> <name> platform.cozystack.io/no-delete-
10+
*/}}
11+
---
12+
apiVersion: admissionregistration.k8s.io/v1
13+
kind: ValidatingAdmissionPolicy
14+
metadata:
15+
name: cozystack-no-delete-guardrail
16+
spec:
17+
failurePolicy: Fail
18+
matchConstraints:
19+
resourceRules:
20+
- apiGroups: ["*"]
21+
apiVersions: ["*"]
22+
operations: ["DELETE"]
23+
resources: ["*/*"]
24+
scope: "*"
25+
validations:
26+
- expression: "false"
27+
message: >-
28+
Deletion blocked: object carries platform.cozystack.io/no-delete=true.
29+
To bypass, first remove the label:
30+
kubectl label <kind> <name> platform.cozystack.io/no-delete-
31+
---
32+
apiVersion: admissionregistration.k8s.io/v1
33+
kind: ValidatingAdmissionPolicyBinding
34+
metadata:
35+
name: cozystack-no-delete-guardrail
36+
spec:
37+
policyName: cozystack-no-delete-guardrail
38+
validationActions: [Deny]
39+
matchResources:
40+
objectSelector:
41+
matchLabels:
42+
platform.cozystack.io/no-delete: "true"

‎packages/core/platform/templates/repository.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,7 @@ kind: {{ $kind }}
1313
metadata:
1414
name: cozystack-packages
1515
namespace: cozy-system
16+
labels:
17+
platform.cozystack.io/no-delete: "true"
1618
spec:
1719
{{- $sourceRepo.spec | toYaml | nindent 2 }}

‎packages/system/cert-manager-issuers/templates/cluster-issuers.yaml‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
apiVersion: cert-manager.io/v1
55
kind: ClusterIssuer
66
metadata:
7-
name: letsencrypt-prod
8-
spec:
7+
name: letsencrypt-prod
8+
labels:
9+
platform.cozystack.io/no-delete: "true"
10+
spec:
911
acme:
1012
privateKeySecretRef:
1113
name: letsencrypt-prod
@@ -28,8 +30,10 @@ spec:
2830
apiVersion: cert-manager.io/v1
2931
kind: ClusterIssuer
3032
metadata:
31-
name: letsencrypt-stage
32-
spec:
33+
name: letsencrypt-stage
34+
labels:
35+
platform.cozystack.io/no-delete: "true"
36+
spec:
3337
acme:
3438
privateKeySecretRef:
3539
name: letsencrypt-stage
@@ -52,6 +56,8 @@ spec:
5256
apiVersion: cert-manager.io/v1
5357
kind: ClusterIssuer
5458
metadata:
55-
name: selfsigned-cluster-issuer
56-
spec:
59+
name: selfsigned-cluster-issuer
60+
labels:
61+
platform.cozystack.io/no-delete: "true"
62+
spec:
5763
selfSigned: {}

‎packages/system/cozystack-basics/templates/tenant-root.yaml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ apiVersion: v1
33
kind: Namespace
44
metadata:
55
name: tenant-root
6+
labels:
7+
platform.cozystack.io/no-delete: "true"
68
---
79
apiVersion: helm.toolkit.fluxcd.io/v2
810
kind: HelmRelease
911
metadata:
1012
annotations:
1113
helm.sh/resource-policy: keep
1214
labels:
15+
platform.cozystack.io/no-delete: "true"
1316
sharding.fluxcd.io/key: tenants
1417
apps.cozystack.io/application.kind: Tenant
1518
apps.cozystack.io/application.group: apps.cozystack.io

‎packages/system/linstor/templates/cluster.yaml‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ apiVersion: piraeus.io/v1
22
kind: LinstorCluster
33
metadata:
44
name: linstorcluster
5+
labels:
6+
platform.cozystack.io/no-delete: "true"
57
spec:
68
#nodeSelector:
79
# node-role.kubernetes.io/linstor: ""

0 commit comments

Comments
 (0)