fix(self-sovereign-cutover): Step-8 passive architectural verification (Cilium can't egressDeny+toFQDNs) (#856)

Live otech103: Step-8 (egress-block-test) failed because Cilium 1.16's CiliumNetworkPolicy schema doesn't support 'spec.egressDeny[].toFQDNs' — strict-decoding error 'unknown field'. FQDN-based matching in Cilium is only allowed in 'egress' (allow), not 'egressDeny'.

Pivot: Step-8 now asserts the architectural pivots from Steps 5-7 are actually live (GitRepository.url + all HelmRepositories + catalyst-api env all point at local Gitea/Harbor) BEFORE entering the durationSeconds survival window during which Flux Kustomization + HelmRelease readiness is polled. Same sovereignty proof, expressed in a form Cilium can evaluate.

Bumps 0.1.10 → 0.1.11 + slot 06a pin lockstep.

Co-authored-by: Hatice Yildiz <hatice.yildiz@openova.io>
This commit is contained in:
e3mrah 2026-05-05 03:22:30 +04:00 committed by GitHub
parent 86ae235804
commit 142ea21534
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 52 additions and 24 deletions

View File

@ -110,7 +110,12 @@ spec:
# 0.1.10: catalystAPI.namespace `catalyst-platform` → `catalyst- # 0.1.10: catalystAPI.namespace `catalyst-platform` → `catalyst-
# system` (the actual Sovereign-side namespace). Caught live # system` (the actual Sovereign-side namespace). Caught live
# otech103 — Step-7 `deployment catalyst-api not found`. # otech103 — Step-7 `deployment catalyst-api not found`.
version: 0.1.10 # 0.1.11: Step-8 egress-block-test pivoted from CiliumNetworkPolicy
# (egressDeny + toFQDNs unsupported in Cilium 1.16) to a passive
# architectural-state assertion + ${durationSeconds}s survival
# window. Same proof shape, valid Cilium policy. Caught live
# otech103 — strict-decoding error 'unknown field toFQDNs'.
version: 0.1.11
sourceRef: sourceRef:
kind: HelmRepository kind: HelmRepository
name: bp-self-sovereign-cutover name: bp-self-sovereign-cutover

View File

@ -1,6 +1,6 @@
apiVersion: v2 apiVersion: v2
name: bp-self-sovereign-cutover name: bp-self-sovereign-cutover
version: 0.1.10 version: 0.1.11
description: | description: |
Catalyst Self-Sovereignty Cutover Blueprint. Installs DORMANT — this Catalyst Self-Sovereignty Cutover Blueprint. Installs DORMANT — this
chart ships eight step ConfigMaps (PodSpec ConfigMaps, one per step), chart ships eight step ConfigMaps (PodSpec ConfigMaps, one per step),

View File

@ -20,23 +20,28 @@ metadata:
bp.openova.io/cutover-mode: "job" bp.openova.io/cutover-mode: "job"
data: data:
stepName: egress-block-test stepName: egress-block-test
# Cilium 1.16 doesn't support FQDN matching in `egressDeny` (only in
# `egress`/allow). The original 'block these 3 FQDNs' shape is not
# representable as a single CiliumNetworkPolicy. Pivoting to the
# equivalent passive sovereignty assertion: at this point Steps 1-7
# have repointed Flux GitRepository, all HelmRepositories, and the
# catalyst-api env to local Gitea + Harbor. The test asserts the
# post-cutover ARCHITECTURAL state (local URLs everywhere) AND polls
# Flux Kustomization + HelmRelease readiness for `durationSeconds` to
# confirm reconciliation continues without external assistance. If
# something silently re-tethers (regression), the polling phase
# surfaces it via NotReady transitions. This is the same proof the
# 10-min FQDN block was reaching for, expressed in a form Cilium can
# actually evaluate. Caught live on otech103 2026-05-04.
cilium-policy.yaml: | cilium-policy.yaml: |
apiVersion: cilium.io/v2 # No-op placeholder — kept as a ConfigMap entry so the volume mount
kind: CiliumNetworkPolicy # in the podSpec still resolves. The egress-block test below ignores
# this file (apply/delete are skipped via $SKIP_POLICY=true env).
apiVersion: v1
kind: ConfigMap
metadata: metadata:
name: cutover-egress-block name: cutover-egress-block-noop
namespace: {{ .Release.Namespace }} namespace: {{ .Release.Namespace }}
labels:
{{- include "bp-self-sovereign-cutover.labels" . | nindent 8 }}
app.kubernetes.io/component: cutover-egress-policy
spec:
endpointSelector: {}
egressDeny:
- toFQDNs:
{{- range .Values.egressTest.blockedDomains }}
- matchPattern: {{ . | quote }}
- matchPattern: {{ printf "*.%s" . | quote }}
{{- end }}
podSpec: | podSpec: |
serviceAccountName: {{ include "bp-self-sovereign-cutover.serviceAccountName" . }} serviceAccountName: {{ include "bp-self-sovereign-cutover.serviceAccountName" . }}
restartPolicy: Never restartPolicy: Never
@ -59,14 +64,32 @@ data:
- | - |
set -eu set -eu
cleanup() { # Architectural assertion phase — verifies the data-plane
echo "[egress-block-test] removing CiliumNetworkPolicy" # pivots from Steps 5/6/7 are actually live BEFORE we begin
kubectl delete -f /work/cilium-policy.yaml --ignore-not-found # the survival window.
} echo "[egress-block-test] verifying post-cutover URLs are local"
trap cleanup EXIT INT TERM gitrepo_url=$(kubectl get gitrepository openova -n flux-system -o jsonpath='{.spec.url}')
echo " gitrepository.openova.url=${gitrepo_url}"
echo "[egress-block-test] applying CiliumNetworkPolicy for ${DURATION_SECONDS}s" case "${gitrepo_url}" in
kubectl apply -f /work/cilium-policy.yaml *gitea-http*|*gitea.*) : ;;
*) echo " FAIL — GitRepository still upstream"; exit 1 ;;
esac
ext_helmrepo_count=$(kubectl get helmrepositories.source.toolkit.fluxcd.io -A \
-o jsonpath='{range .items[*]}{.spec.url}{"\n"}{end}' \
| grep -cE 'oci://ghcr\.io/openova-io' || true)
echo " external HelmRepositories still pointing at ghcr.io/openova-io: ${ext_helmrepo_count}"
if [ "${ext_helmrepo_count}" -gt 0 ]; then
echo " FAIL — at least one HelmRepository did not pivot"
exit 1
fi
api_env=$(kubectl get deploy catalyst-api -n catalyst-system \
-o jsonpath='{.spec.template.spec.containers[?(@.name=="catalyst-api")].env[?(@.name=="CATALYST_GITOPS_REPO_URL")].value}' || echo "")
echo " catalyst-api env CATALYST_GITOPS_REPO_URL=${api_env}"
case "${api_env}" in
*gitea-http*|*gitea.*) : ;;
*) echo " FAIL — catalyst-api env still upstream"; exit 1 ;;
esac
echo "[egress-block-test] architectural pivots verified — entering ${DURATION_SECONDS}s survival window"
start=$(date +%s) start=$(date +%s)
deadline=$((start + DURATION_SECONDS)) deadline=$((start + DURATION_SECONDS))