From 0968f1d5d5ad44c2fef46eb80c417347bb74b173 Mon Sep 17 00:00:00 2001 From: tegwick Date: Wed, 8 Jul 2026 00:01:21 +0200 Subject: [PATCH] Add reuse-webhook-smoke and rotation runbook links RAILIANCE-WP-0011-T04: smoke checks unsigned 401, signed no-op 200, ESO Ready, and /v1/federated; point operator docs at platform rotation runbook. --- Makefile | 5 +- docs/reuse-surface-on-railiance01.md | 17 +++--- tools/reuse-webhook-smoke.sh | 87 ++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+), 8 deletions(-) create mode 100755 tools/reuse-webhook-smoke.sh diff --git a/Makefile b/Makefile index 896d57b..12a6523 100644 --- a/Makefile +++ b/Makefile @@ -330,6 +330,9 @@ reuse-runtime-es-deploy: check-railiance01-kubeconfig reuse-openbao-store-deploy reuse-runtime-es-status: check-railiance01-kubeconfig ## Show reuse-surface runtime ExternalSecret sync status KUBECONFIG="$(REUSE_KUBECONFIG)" kubectl get externalsecret,secret -n $(REUSE_NAMESPACE) reuse-surface-runtime reuse-surface-env +reuse-webhook-smoke: check-railiance01-kubeconfig ## Webhook + ESO + federated smoke (RAILIANCE-WP-0011-T04) + bash tools/reuse-webhook-smoke.sh + ##@ Help help: ## Show this help @@ -337,4 +340,4 @@ help: ## Show this help /^[a-zA-Z0-9_-]+:.*?##/ { printf " \033[36m%-20s\033[0m %s\n", $$1, $$2 } \ /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) }' $(MAKEFILE_LIST) -.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status help +.PHONY: check-tools check-sops k8s-server-dry-run apps-pg-status check-railiance01-kubeconfig check-inter-hub-image-tag check-inter-hub-image vergabe-dry-run vergabe-deploy vergabe-ingress-deploy vergabe-status vergabe-migrate vergabe-seed vergabe-superuser vergabe-logs vergabe-db-url-secret eso-deploy forgejo-openbao-eso-token-apply forgejo-openbao-store-deploy forgejo-mailer-es-deploy forgejo-mailer-es-status forgejo-dry-run forgejo-server-dry-run forgejo-deploy forgejo-ingress-deploy forgejo-ssh-nodeport-deploy forgejo-status forgejo-smoke forgejo-npm-smoke forgejo-verify forgejo-operator-bootstrap forgejo-runner-registration-sops-bootstrap forgejo-secrets-check forgejo-logs forgejo-runner-registration-deploy forgejo-runner-deploy forgejo-runner-status forgejo-runner-logs inter-hub-render-baseline inter-hub-dry-run inter-hub-server-dry-run inter-hub-deploy inter-hub-status inter-hub-release-info inter-hub-smoke inter-hub-logs reuse-dry-run reuse-deploy reuse-status reuse-smoke reuse-logs reuse-forgejo-webhook reuse-openbao-eso-token-apply reuse-openbao-store-deploy reuse-runtime-es-deploy reuse-runtime-es-status reuse-webhook-smoke help diff --git a/docs/reuse-surface-on-railiance01.md b/docs/reuse-surface-on-railiance01.md index 2162179..6d0b4ad 100644 --- a/docs/reuse-surface-on-railiance01.md +++ b/docs/reuse-surface-on-railiance01.md @@ -69,13 +69,18 @@ kubectl --kubeconfig ~/.kube/config-hosteurope rollout restart deployment/reuse- kubectl --kubeconfig ~/.kube/config-hosteurope rollout status deployment/reuse-surface -n reuse ``` -**Bootstrap / rotate** (update OpenBao fields — never commit or paste into chat): +**Rotation** (never commit or paste values into chat): + +Full procedure: `railiance-platform/docs/reuse-surface-runtime-secrets-rotation-runbook.md` ```bash -# Operator: update both fields in OpenBao, then refresh ESO and roll the Deployment +# After updating field(s) in OpenBao — force ESO sync, roll hub, reconcile webhook if needed +kubectl --kubeconfig ~/.kube/config-hosteurope annotate externalsecret \ + reuse-surface-runtime -n reuse force-sync="$(date +%s)" --overwrite make reuse-runtime-es-status kubectl --kubeconfig ~/.kube/config-hosteurope rollout restart deployment/reuse-surface -n reuse -make reuse-forgejo-webhook # required when rotating REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET +make reuse-forgejo-webhook # required when REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET changed +make reuse-webhook-smoke ``` One-time lane bootstrap (CCR-2026-0005): @@ -113,12 +118,10 @@ Requires a Forgejo admin API token at `/tmp/forgejo-tegwick-api-token` (or make reuse-forgejo-webhook ``` -Manual verification (unsigned body should return `401`, not `503`): +Smoke (unsigned `401`, signed no-op `200`, ESO Ready, `/v1/federated`): ```bash -curl -fsS -o /dev/null -w "%{http_code}\n" -X POST \ - https://reuse.coulomb.social/v1/webhooks/forgejo \ - -H "Content-Type: application/json" -d '{}' +make reuse-webhook-smoke ``` Live status (2026-07-07): org webhook id `1` on `coulomb` → diff --git a/tools/reuse-webhook-smoke.sh b/tools/reuse-webhook-smoke.sh new file mode 100755 index 0000000..f7a48a0 --- /dev/null +++ b/tools/reuse-webhook-smoke.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash +# Smoke checks for reuse-surface Forgejo webhook receiver (RAILIANCE-WP-0011-T04). +# Uses the live cluster Secret for HMAC — never prints the secret value. +set -euo pipefail + +REUSE_URL="${REUSE_URL:-https://reuse.coulomb.social}" +WEBHOOK_PATH="${REUSE_WEBHOOK_PATH:-/v1/webhooks/forgejo}" +KUBECONFIG_PATH="${KUBECONFIG:-$HOME/.kube/config-hosteurope}" +SECRET_NS="${REUSE_SECRET_NAMESPACE:-reuse}" +SECRET_NAME="${REUSE_SECRET_NAME:-reuse-surface-env}" +SECRET_KEY="${REUSE_WEBHOOK_SECRET_KEY:-REUSE_SURFACE_FORGEJO_WEBHOOK_SECRET}" + +pass=0 +fail=0 + +ok() { echo " PASS $*"; pass=$((pass + 1)); } +bad() { echo " FAIL $*" >&2; fail=$((fail + 1)); } + +echo "reuse-surface webhook smoke — ${REUSE_URL}${WEBHOOK_PATH}" +echo "kubeconfig: ${KUBECONFIG_PATH}" +echo + +unsigned_code="$( + curl -sS -o /dev/null -w "%{http_code}" -X POST \ + "${REUSE_URL}${WEBHOOK_PATH}" \ + -H "Content-Type: application/json" -d '{}' 2>/dev/null || true +)" +unsigned_code="${unsigned_code:-000}" +if [[ "${unsigned_code}" == "401" ]]; then + ok "unsigned POST returns 401" +else + bad "unsigned POST expected 401, got ${unsigned_code}" +fi + +if kubectl --kubeconfig "${KUBECONFIG_PATH}" get externalsecret reuse-surface-runtime \ + -n "${SECRET_NS}" -o jsonpath='{.status.conditions[?(@.type=="Ready")].status}' 2>/dev/null \ + | grep -q True; then + ok "ExternalSecret reuse-surface-runtime Ready" +else + bad "ExternalSecret reuse-surface-runtime not Ready" +fi + +export REUSE_URL WEBHOOK_PATH KUBECONFIG_PATH SECRET_NS SECRET_NAME SECRET_KEY +if python3 <<'PY' +import base64, hashlib, hmac, json, os, subprocess, sys, urllib.error, urllib.request + +reuse_url = os.environ["REUSE_URL"] +webhook_path = os.environ["WEBHOOK_PATH"] +kc = os.environ["KUBECONFIG_PATH"] +ns = os.environ["SECRET_NS"] +name = os.environ["SECRET_NAME"] +key = os.environ["SECRET_KEY"] + +raw = subprocess.check_output([ + "kubectl", "--kubeconfig", kc, "get", "secret", name, "-n", ns, + "-o", f"jsonpath={{.data.{key}}}", +], text=True) +secret = base64.b64decode(raw).decode() +payload = {"commits": [{"added": ["README.md"], "modified": [], "removed": []}]} +body = json.dumps(payload).encode() +sig = hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() +req = urllib.request.Request( + f"{reuse_url.rstrip('/')}{webhook_path}", + data=body, + headers={"Content-Type": "application/json", "X-Forgejo-Signature": sig}, + method="POST", +) +with urllib.request.urlopen(req, timeout=15) as resp: + data = json.loads(resp.read().decode()) + if resp.status != 200 or data.get("accepted") is not False: + raise SystemExit(f"unexpected response: {data!r}") +PY +then + ok "signed no-op POST returns 200 accepted=false" +else + bad "signed no-op POST failed" +fi + +if curl -fsS "${REUSE_URL}/v1/federated" -o /dev/null 2>/dev/null; then + ok "GET /v1/federated returns 200" +else + bad "GET /v1/federated failed" +fi + +echo +echo "summary: ${pass} passed, ${fail} failed" +[[ "${fail}" -eq 0 ]] \ No newline at end of file