Phase 5: KubeVirt + CDI IaaS proof (R-PLAT-03/04, R-STATE-01, R-NET-01)
- platform/kubevirt: pinned KubeVirt v1.9.0 + CDI v1.66.0 operators (remote bases); KubeVirt/CDI CRs tuned for Longhorn; useEmulation=true (prefer real KVM, emulate fallback). - Controllers/configs split (F-MON1): CRs + ServiceMonitors + alerts via platform-config. - Observability: ServiceMonitors, KubeVirt platform-health alerts (R-OBS-04), VM dashboard. - catalog/vm-demo.yaml (kind: vm) + rendered VirtualMachine/DataVolume fixture. - Conformance: vm-lifecycle (R-PLAT-03/04), vm-data-survival (R-STATE-01, L3), vm-default-deny (R-NET-01) — self-skipping until KubeVirt+longhorn exist. - ci/SPEC_PIN: adopt accepted spec b633b7f (ADR-0015 egress work already merged). - conformance/run.sh: document R-ENV-02/R-GITOPS-01 coverage gaps (WS-GITOPS/INFRA-owned). Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
254d50a20c
commit
dbffb8fab8
16 files changed
+905
-61
No files matched your search
@@ -0,0 +1,189 @@
|
||||
# =============================================================================
|
||||
# L2 BEHAVIORAL — Default-deny applies to a VM (R-NET-01) [RUNNABLE / self-skips]
|
||||
# Catalog: docs/14 (default-deny) — the IaaS partner of the container `default-deny`
|
||||
# test. Spec: docs/03 "Default-deny baseline"; docs/06 ("default-deny +
|
||||
# egress classes apply to VMs too"); ADR-0005/0015.
|
||||
#
|
||||
# GIVEN a VM in a namespace carrying the default-deny baseline (deny all east-west +
|
||||
# egress, allow only DNS to the in-cluster resolver),
|
||||
# WHEN the guest probes the network from INSIDE the VM,
|
||||
# THEN DNS resolves (the one standing allow) but an undeclared public TCP dial is
|
||||
# BLOCKED — the same default-deny fabric that governs pods governs the VM's
|
||||
# own datapath (its traffic egresses via the virt-launcher pod netns, so a
|
||||
# podSelector:{} NetworkPolicy covers it). R-NET-01 for IaaS.
|
||||
#
|
||||
# METHOD (virtctl-free): cloud-init runs the two probes and writes a PASS/FAIL
|
||||
# verdict to a raw Longhorn block disk, then powers off (RerunOnFailure => stays
|
||||
# down). A busybox reader pod reads the verdict back. The container default-deny
|
||||
# test proves the datapath fail-closed (default-deny-failclosed, L3); this asserts
|
||||
# the SAME baseline is enforced for a VM.
|
||||
#
|
||||
# RUNNABLE / SELF-SKIPPING: skips cleanly (green) until KubeVirt + longhorn exist.
|
||||
#
|
||||
# NOTE (bounded live-iteration, per the WS-VM report): the in-guest probe details
|
||||
# (guest block-device ordering /dev/vdb, cluster-DNS injection into the guest, sh=bash
|
||||
# for /dev/tcp on the Fedora containerDisk) are authored to the KubeVirt defaults and
|
||||
# confirmed on the first live Phase-5 run; the test STRUCTURE and assertion are final.
|
||||
# =============================================================================
|
||||
apiVersion: chainsaw.kyverno.io/v1alpha1
|
||||
kind: Test
|
||||
metadata:
|
||||
name: vm-default-deny
|
||||
labels:
|
||||
conformance.home.k3s/R-NET-01: "true"
|
||||
annotations:
|
||||
conformance.home.k3s/requirements: "R-NET-01"
|
||||
conformance.home.k3s/layer: "L2"
|
||||
conformance.home.k3s/safety-critical: "false"
|
||||
conformance.home.k3s/executable: "true"
|
||||
conformance.home.k3s/intent: >-
|
||||
A VM under the default-deny baseline reaches only DNS from inside the guest;
|
||||
an undeclared public TCP dial is blocked. Skips cleanly until KubeVirt + longhorn exist.
|
||||
spec:
|
||||
timeouts:
|
||||
exec: 20m
|
||||
steps:
|
||||
- name: default-deny-applies-to-the-vm-datapath
|
||||
try:
|
||||
- script:
|
||||
content: |
|
||||
set -euo pipefail
|
||||
NS="$NAMESPACE"
|
||||
|
||||
# -- Gate: skip cleanly (green) until KubeVirt + longhorn exist. --
|
||||
if ! kubectl get crd virtualmachineinstances.kubevirt.io >/dev/null 2>&1; then
|
||||
echo "SKIP: KubeVirt CRDs absent"; echo VM_DEFAULT_DENY_SKIPPED_OK; exit 0
|
||||
fi
|
||||
if ! kubectl get storageclass longhorn >/dev/null 2>&1; then
|
||||
echo "SKIP: 'longhorn' StorageClass absent"; echo VM_DEFAULT_DENY_SKIPPED_OK; exit 0
|
||||
fi
|
||||
|
||||
CANARY="vm-deny-$(date +%s%N)"
|
||||
|
||||
# -- GIVEN: the default-deny baseline in this namespace (deny all, allow only DNS to
|
||||
# kube-system) — the exact shape Kyverno generates for app namespaces (docs/03). --
|
||||
kubectl -n "$NS" apply -f - <<'EOF'
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: NetworkPolicy
|
||||
metadata:
|
||||
name: default-deny
|
||||
spec:
|
||||
podSelector: {}
|
||||
policyTypes: [Ingress, Egress]
|
||||
ingress: []
|
||||
egress:
|
||||
- to:
|
||||
- namespaceSelector:
|
||||
matchLabels:
|
||||
kubernetes.io/metadata.name: kube-system
|
||||
ports:
|
||||
- { protocol: UDP, port: 53 }
|
||||
- { protocol: TCP, port: 53 }
|
||||
EOF
|
||||
|
||||
# -- The verdict block disk (Longhorn). --
|
||||
kubectl -n "$NS" apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: PersistentVolumeClaim
|
||||
metadata:
|
||||
name: vm-verdict
|
||||
spec:
|
||||
accessModes: [ReadWriteOnce]
|
||||
volumeMode: Block
|
||||
storageClassName: longhorn
|
||||
resources:
|
||||
requests:
|
||||
storage: 1Gi
|
||||
EOF
|
||||
|
||||
# -- The VM + the in-guest probe (quoted heredoc: many '$' in the probe; __CANARY__ sed'd). --
|
||||
cat > /tmp/vm-deny.yaml <<'EOF'
|
||||
apiVersion: kubevirt.io/v1
|
||||
kind: VirtualMachine
|
||||
metadata:
|
||||
name: prober
|
||||
spec:
|
||||
runStrategy: RerunOnFailure
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
kubevirt.io/vm: prober
|
||||
homelab.keithjackson.dev/egress-class: internet
|
||||
spec:
|
||||
domain:
|
||||
cpu: { cores: 1 }
|
||||
memory: { guest: 1024Mi }
|
||||
devices:
|
||||
disks:
|
||||
- name: rootdisk
|
||||
disk: { bus: virtio }
|
||||
- name: verdict
|
||||
disk: { bus: virtio }
|
||||
- name: cloudinit
|
||||
disk: { bus: virtio }
|
||||
resources:
|
||||
requests:
|
||||
memory: 1024Mi
|
||||
volumes:
|
||||
- name: rootdisk
|
||||
containerDisk:
|
||||
image: quay.io/containerdisks/fedora:41
|
||||
- name: verdict
|
||||
persistentVolumeClaim:
|
||||
claimName: vm-verdict
|
||||
- name: cloudinit
|
||||
cloudInitNoCloud:
|
||||
userData: |-
|
||||
#cloud-config
|
||||
runcmd:
|
||||
- [ sh, -c, "getent hosts quay.io >/dev/null 2>&1 && DNS=1 || DNS=0; timeout 5 sh -c 'echo > /dev/tcp/1.1.1.1/443' >/dev/null 2>&1 && DENY=0 || DENY=1; V=FAIL; [ \"$DNS\" = 1 ] && [ \"$DENY\" = 1 ] && V=PASS; printf '%s' \"$V:__CANARY__\" | dd of=/dev/vdb bs=1 seek=0 conv=notrunc; sync; sleep 3; poweroff" ]
|
||||
EOF
|
||||
sed -i "s/__CANARY__/$CANARY/" /tmp/vm-deny.yaml
|
||||
kubectl -n "$NS" apply -f /tmp/vm-deny.yaml
|
||||
|
||||
# -- WHEN: wait until the guest has run the probes and powered off. --
|
||||
ok=""
|
||||
for i in $(seq 1 90); do
|
||||
phase="$(kubectl -n "$NS" get vmi prober -o jsonpath='{.status.phase}' 2>/dev/null || true)"
|
||||
if [ "$phase" = "Succeeded" ] || { [ -z "$phase" ] && [ "$i" -gt 3 ]; }; then ok=1; break; fi
|
||||
sleep 10
|
||||
done
|
||||
[ -n "$ok" ] || { echo "FAIL: guest never completed the probes+poweroff (last='$phase')" >&2; exit 1; }
|
||||
|
||||
# -- THEN: read the verdict from the Longhorn disk via a throwaway reader. --
|
||||
kubectl -n "$NS" delete vm prober --wait=true --timeout=180s
|
||||
kubectl -n "$NS" apply -f - <<'EOF'
|
||||
apiVersion: v1
|
||||
kind: Pod
|
||||
metadata:
|
||||
name: verdict-reader
|
||||
spec:
|
||||
restartPolicy: Never
|
||||
containers:
|
||||
- name: app
|
||||
image: busybox:1.36.1
|
||||
command: ["sh","-c","sleep 3600"]
|
||||
volumeDevices:
|
||||
- name: v
|
||||
devicePath: /dev/verdict
|
||||
volumes:
|
||||
- name: v
|
||||
persistentVolumeClaim:
|
||||
claimName: vm-verdict
|
||||
EOF
|
||||
kubectl -n "$NS" wait --for=condition=Ready pod/verdict-reader --timeout=240s
|
||||
GOT="$(kubectl -n "$NS" exec verdict-reader -- sh -c "dd if=/dev/verdict bs=1 count=$((5 + ${#CANARY})) 2>/dev/null")"
|
||||
echo "verdict='$GOT' (expected PASS:$CANARY)"
|
||||
case "$GOT" in
|
||||
PASS:"$CANARY") echo VM_DEFAULT_DENY_OK ;;
|
||||
*) echo "FAIL: default-deny not enforced for the VM (verdict='$GOT')" >&2; exit 1 ;;
|
||||
esac
|
||||
check:
|
||||
(contains($stdout, 'VM_DEFAULT_DENY_OK') || contains($stdout, 'VM_DEFAULT_DENY_SKIPPED_OK')): true
|
||||
finally:
|
||||
- script:
|
||||
content: |
|
||||
kubectl -n "$NAMESPACE" delete vm prober --ignore-not-found --wait=false || true
|
||||
kubectl -n "$NAMESPACE" delete pod verdict-reader --ignore-not-found --wait=false || true
|
||||
kubectl -n "$NAMESPACE" delete pvc vm-verdict --ignore-not-found --wait=false || true
|
||||
echo "cleanup best-effort; chainsaw namespace teardown finalizes"
|
||||
Reference in new issue
Block a user