Files
home-network-k3s-impl/conformance/tests/l3-fault-injection/default-deny-failclosed/chainsaw-test.yaml
T
jakman85andClaude Opus 4.8 1bf8e3bfc4 feat(conformance): implement L3 default-deny-failclosed (R-NET-01/02, fault injection)
Was a scaffold (fixture TODO, fault-injection a no-op echo). Implemented against the
live datapath: fixtures/allowed-pair.yaml = default-deny baseline + source + dest(:8080)
+ the sole explicit allow. The test verifies source->dest works (ALLOWED_OK), then
REVOKES both halves of the allow (real fault: kubectl delete networkpolicy), and asserts
connectivity fails CLOSED (FAIL_CLOSED_OK) while DNS still resolves (DNS_OK). Proves
default-deny is datapath-enforced and fails closed on revocation. DNS check asserts the
resolved answer, since busybox nslookup exits non-zero on a successful forward lookup.
PASSES (74.8s).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-08-17 17:42:34 -05:00

97 lines
4.2 KiB
YAML

# =============================================================================
# L3 FAULT-INJECTION — Default-deny fail-closed (R-NET-01, R-NET-02) *SAFETY-CRITICAL* [RUNNABLE]
# Catalog/spec: docs/14 R-TEST-05 lists default-deny among the behaviors that MUST
# be proven by FAULT INJECTION (not happy-path). L3 partner of tests/l2-behavioral/default-deny.
#
# GIVEN a source that can reach dest:8080 ONLY because of an explicit allow rule,
# WHEN the allow rule is REVOKED (fault: delete both halves of the NetworkPolicy),
# THEN connectivity drops to fail-closed within a bounded window (the baseline denies
# once the allow is gone), while DNS still resolves.
#
# Proves the deny is enforced by the live datapath, not merely declared, and that
# removing access fails CLOSED rather than lingering open.
# =============================================================================
apiVersion: chainsaw.kyverno.io/v1alpha1
kind: Test
metadata:
name: default-deny-failclosed
labels:
conformance.home.k3s/R-NET-01: "true"
conformance.home.k3s/R-NET-02: "true"
annotations:
conformance.home.k3s/requirements: "R-NET-01, R-NET-02"
conformance.home.k3s/layer: "L3"
conformance.home.k3s/safety-critical: "true"
conformance.home.k3s/executable: "true"
conformance.home.k3s/intent: >-
Revoking an allow rule drops connectivity to fail-closed immediately; DNS
still works — the datapath enforces default-deny, not just declares it.
spec:
timeouts:
apply: 90s
assert: 120s
exec: 150s
steps:
- name: given-source-allowed-to-dest
try:
- apply:
file: fixtures/allowed-pair.yaml
# No readinessProbe (default-deny drops the kubelet probe) → Ready once Running.
- assert:
resource:
apiVersion: v1
kind: Pod
metadata: { name: source }
status: { phase: Running }
- assert:
resource:
apiVersion: v1
kind: Pod
metadata: { name: dest }
status: { phase: Running }
- script:
# The explicit allow is the SOLE reason this succeeds (baseline denies all else).
content: |
set -eu
kubectl -n "$NAMESPACE" exec source -- nc -z -w 5 dest 8080 && echo ALLOWED_OK
check:
(contains($stdout, 'ALLOWED_OK')): true
- name: when-revoke-allow-then-fail-closed
try:
# -- FAULT INJECTION: revoke the allow (both halves); baseline default-deny remains. --
- script:
content: |
set -eu
kubectl -n "$NAMESPACE" delete networkpolicy \
allow-source-to-dest-egress allow-source-to-dest-ingress --wait=true
echo REVOKED
- script:
# Connectivity must fail CLOSED. Poll briefly for the datapath to converge; a
# persistent success is a LEAK (fails the test).
content: |
set -u
for i in $(seq 1 10); do
if kubectl -n "$NAMESPACE" exec source -- nc -z -w 5 dest 8080 2>/dev/null; then
echo "attempt $i: still reachable, waiting for revocation to converge..."; sleep 3
else
echo FAIL_CLOSED_OK; exit 0
fi
done
echo "LEAK: dest still reachable after allow revoked" >&2; exit 1
check:
(contains($stdout, 'FAIL_CLOSED_OK')): true
- script:
# DNS must STILL resolve (baseline permits DNS egress) — the deny is scoped, not total.
# busybox nslookup can exit non-zero on a successful forward lookup (server-PTR quirk),
# so assert on the RESOLVED ANSWER in the output, not the exit code.
content: |
set -u
out="$(kubectl -n "$NAMESPACE" exec source -- nslookup kubernetes.default 2>&1 || true)"
echo "$out"
echo "$out" | grep -qiE 'kubernetes\.default|10\.43\.' && echo DNS_OK || { echo "DNS_BLOCKED" >&2; exit 1; }
check:
(contains($stdout, 'DNS_OK')): true
finally:
- script:
content: 'echo "cleanup via chainsaw namespace teardown"'