|
282 | 282 | -o jsonpath='{range .items[*].spec.limits[*]}{.default.cpu}{" "}{.default.memory}{" "}{.defaultRequest.cpu}{" "}{.defaultRequest.memory}{"\n"}{end}' \ |
283 | 283 | | grep -qx '250m 128Mi 25m 128Mi' |
284 | 284 | } |
| 285 | + |
| 286 | +@test "Deletion-protection VAP denies delete on labeled cozystack-version ConfigMap" { |
| 287 | + # Locks down the contract delivered by packages/core/platform/templates/ |
| 288 | + # deletion-protection.yaml: a DELETE on any object carrying |
| 289 | + # platform.cozystack.io/no-delete=true is rejected by the |
| 290 | + # ValidatingAdmissionPolicy with the documented message, and the bypass |
| 291 | + # path (remove the label, then delete) succeeds. |
| 292 | + # |
| 293 | + # This covers every regression the PR is meant to prevent in a single |
| 294 | + # pass: capability gate inverted, binding objectSelector mistyped, |
| 295 | + # validationActions flipped Deny→Warn, expression flipped false→true, |
| 296 | + # label-key drift between the binding and the manifests. |
| 297 | + |
| 298 | + # Preflight: VAP requires Kubernetes 1.30+. Skip on older clusters so |
| 299 | + # the suite stays green where the capability gate intentionally elides |
| 300 | + # the policy. Detect by attempting to fetch the policy by name; if the |
| 301 | + # API is present, the resource will be retrievable, otherwise kubectl |
| 302 | + # exits non-zero on an unknown resource type. |
| 303 | + if ! kubectl api-resources --api-group=admissionregistration.k8s.io \ |
| 304 | + 2>/dev/null | grep -qw validatingadmissionpolicies; then |
| 305 | + skip "ValidatingAdmissionPolicy API not available on this cluster" |
| 306 | + fi |
| 307 | + kubectl get validatingadmissionpolicy cozystack-no-delete-guardrail |
| 308 | + |
| 309 | + # The cozystack-version ConfigMap is created with the no-delete label |
| 310 | + # baked in by the chart (templates/cozystack-version.yaml) and is |
| 311 | + # backfilled by the migration on upgrades. Asserting the label is the |
| 312 | + # precondition for the deny check below: if the label is gone the deny |
| 313 | + # will not fire and the test would misreport as a pass on a regressed |
| 314 | + # binding. |
| 315 | + kubectl get configmap cozystack-version -n cozy-system \ |
| 316 | + -o jsonpath='{.metadata.labels.platform\.cozystack\.io/no-delete}' \ |
| 317 | + | grep -qx 'true' |
| 318 | + |
| 319 | + # The actual deny check. Capture both stdout+stderr and exit code so a |
| 320 | + # network/auth failure does not silently look like a deny success. |
| 321 | + local output rc |
| 322 | + output=$(kubectl delete configmap cozystack-version -n cozy-system 2>&1) \ |
| 323 | + && rc=0 || rc=$? |
| 324 | + echo "kubectl delete exit=$rc, output=$output" |
| 325 | + # Delete MUST have failed: success means the VAP regressed. |
| 326 | + [ "$rc" -ne 0 ] |
| 327 | + # Assert the user-facing deny message is the one this PR ships — guards |
| 328 | + # against expression flipped to "true" or message reworded away from the |
| 329 | + # documented bypass. The CEL message is on one line in the api-server |
| 330 | + # response, so grep for the literal substring with the --namespace flag. |
| 331 | + echo "$output" | grep -q 'Deletion blocked: object carries platform.cozystack.io/no-delete=true' |
| 332 | + echo "$output" | grep -q -- '--namespace' |
| 333 | + |
| 334 | + # And confirm the ConfigMap is still there — a partial deny that races |
| 335 | + # tombstone creation would also be a regression. |
| 336 | + kubectl get configmap cozystack-version -n cozy-system >/dev/null |
| 337 | + |
| 338 | + # Bypass path: remove the label, delete must succeed. Re-stamp the label |
| 339 | + # afterward so the cluster ends the test in the same state it started. |
| 340 | + kubectl label configmap cozystack-version -n cozy-system \ |
| 341 | + platform.cozystack.io/no-delete- --overwrite |
| 342 | + # Stash the data so we can reconstruct after delete. |
| 343 | + local version |
| 344 | + version=$(kubectl get configmap cozystack-version -n cozy-system \ |
| 345 | + -o jsonpath='{.data.version}') |
| 346 | + kubectl delete configmap cozystack-version -n cozy-system |
| 347 | + ! kubectl get configmap cozystack-version -n cozy-system 2>/dev/null |
| 348 | + # Reconstruct: declarative apply matches the chart template at |
| 349 | + # packages/core/platform/templates/cozystack-version.yaml — same label set |
| 350 | + # AND the helm.sh/resource-policy: keep annotation that pins the ConfigMap |
| 351 | + # across helm uninstall. |
| 352 | + cat <<EOF | kubectl apply -f - |
| 353 | +apiVersion: v1 |
| 354 | +kind: ConfigMap |
| 355 | +metadata: |
| 356 | + name: cozystack-version |
| 357 | + namespace: cozy-system |
| 358 | + labels: |
| 359 | + platform.cozystack.io/no-delete: "true" |
| 360 | + annotations: |
| 361 | + helm.sh/resource-policy: keep |
| 362 | +data: |
| 363 | + version: "${version}" |
| 364 | +EOF |
| 365 | +} |
0 commit comments