Files
home-network-k3s-impl/conformance/tests/l3-fault-injection/vpn-killswitch/fixtures/vpn-and-internet-pods.yaml
T
jakman85andClaude Opus 4.8 0e69c63b55 conformance: complete vpn-killswitch L3 test + ADR-0015 RBAC/fixtures
Turn the vpn-killswitch scaffold into a runnable SAFETY-CRITICAL L3 test for
R-NET-06 under ADR-0015 (node-level host OpenVPN tunnel steered by Cilium Egress
Gateway with a host nftables kill-switch), replacing the obsolete in-cluster
gluetun design.

- fixtures/vpn-and-internet-pods.yaml: two egress-class probe pods + documented
  RBAC/PodSecurity/namespace requirements for node-level fault injection.
- vpn-killswitch/chainsaw-test.yaml: GIVEN healthy tunnel (vpn egress != WAN);
  WHEN the host `vpn-egress-tunnel` systemd unit is STOPPED via a privileged,
  hostPID, node-pinned nsenter Job THEN vpn-class egress is blackholed (curl
  fails, never returns the WAN IP = no leak) while internet-class is unaffected;
  WHEN restarted THEN vpn egress recovers. spec-level `finally` ALWAYS runs an
  idempotent start Job (self-healing) so a mid-test failure never leaves the
  gateway tunnel down.
- incluster/rbac.yaml: grant the runner least-privilege for the delivered tests
  (namespaces create/delete/patch for the PSA relabel, pods/services/configmaps/
  pods-exec/pods-log, batch/jobs) and replace the gluetun-scale rule with the
  ADR-0015 host-systemd mechanism note (privilege via PodSecurity, not RBAC).

Offline validation: python yaml parse (all docs), `run.sh --lint` (structural),
`run.sh --coverage-only --strict-coverage` (no tag/annotation drift; R-NET-05/06/
07 covered, R-NET-06 has its L3 test), yamllint. No live apply, no cluster run.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-08-24 13:58:40 -05:00

69 lines
3.2 KiB
YAML

# =============================================================================
# Fixture for the vpn-killswitch L3 fault-injection test (R-NET-06, ADR-0015).
#
# Two probe workloads — identical to the egress-class fixture — plus the two
# node-pinned PRIVILEGED fault Jobs that STOP / START the host `vpn-egress-tunnel`
# systemd unit on the egress-gateway node. Under ADR-0015 the VPN tunnel is HOST
# infrastructure (OpenVPN split-tunnel on the node), NOT an in-cluster gluetun pod,
# so "force the tunnel down" means stopping a host systemd service — which requires
# entering the host namespaces (nsenter -t 1) from a privileged, hostPID pod pinned
# to that node.
#
# The Jobs are authored here as fixtures (not applied by this file directly) so the
# test can `apply` the stop Job at the fault point and the start Job in `finally`.
# They are pinned to the gateway node via nodeSelector; hostPID + privileged are
# required so nsenter can reach PID 1's mount/uts/ipc/net/pid namespaces and run the
# host `systemctl`.
#
# ---------------------------------------------------------------------------------
# NAMESPACE / PodSecurity / RBAC REQUIREMENTS (see incluster/rbac.yaml):
# * The namespace these Jobs run in MUST permit privileged + hostPID pods. Chainsaw
# runs each test in an ephemeral namespace that inherits the platform default
# (pod-security.kubernetes.io/enforce=restricted), which FORBIDS both. The test
# therefore relabels its OWN ephemeral namespace to `enforce=privileged` BEFORE
# applying these Jobs (and it is torn down with the namespace). This is scoped to
# the single per-test namespace — the platform default stays restricted.
# * If a cluster-wide Kyverno "disallow-privileged"/"disallow-host-namespaces"
# policy is in force, the conformance test namespace (prefix `chainsaw-conf-`)
# MUST be in that policy's exclusion set, or these Jobs are the documented, sole
# sanctioned exception for node-level fault injection (R-SEC-02 audit item).
# * The runner ServiceAccount (conformance-runner) needs: namespaces patch (to set
# the PSA label), and jobs create/delete/get/list/watch (to drive the fault).
# * The Job PODS run under the ephemeral namespace's `default` ServiceAccount and
# need NO Kubernetes API permissions — they act on the HOST via nsenter, not the
# API. Their power comes from PodSecurity (privileged) + hostPID, not RBAC.
# =============================================================================
apiVersion: v1
kind: Pod
metadata:
name: egress-internet
labels:
app: egress-test
homelab.keithjackson.dev/egress-class: internet
spec:
containers:
- name: curl
image: curlimages/curl:8.11.1
command: ["sleep", "3600"]
resources:
requests: { cpu: 10m, memory: 16Mi }
limits: { memory: 32Mi }
restartPolicy: Never
---
apiVersion: v1
kind: Pod
metadata:
name: egress-vpn
labels:
app: egress-test
homelab.keithjackson.dev/egress-class: vpn
spec:
containers:
- name: curl
image: curlimages/curl:8.11.1
command: ["sleep", "3600"]
resources:
requests: { cpu: 10m, memory: 16Mi }
limits: { memory: 32Mi }
restartPolicy: Never