- infra/scripts/simulate-two-host/: real two-Docker-network simulation (Gateway genuinely isolated from any Lab bridge network) proving the Lab Host Router fix - 13/13 real assertions (HTTP+WebSocket through the Router, Alice/Bob isolation, garbage/destroyed tokens, mTLS-only Router access). - infra/scripts/drills/: Lab Host loss/rebuild (9/9), deployment rollback, CI-fails-closed (6 required failure modes), fake cohort dress rehearsal (14/14, one continuous narrative through the real app). - infra/ci/gitea-workflows/build-and-test.yml: new security-scan job (npm audit, gitleaks, hadolint, shellcheck, trivy - all free/open-source), severity policy in docs/SECURITY_SCANNING.md (CRITICAL/HIGH blocks, MEDIUM/LOW reported only). Gates build-images alongside regression. Found and fixed two real pre-existing bugs via the CI-fails-closed drill: staging-smoke-test.sh and production-smoke-test.sh both had a classic set -e gotcha (curl ... && echo "OK" as one statement - a failing command that isn't last in an && chain doesn't trigger -e) that let an unreachable/broken endpoint fall through silently instead of failing the smoke test. platform/test/e2e-phase3a.js updated for the new paginated /api/admin/audit-events response shape (Phase 4.1 pagination work).
163 lines
6.9 KiB
YAML
163 lines
6.9 KiB
YAML
# Gitea Actions workflow — build/test on every push to main. Gitea Actions uses the same
|
|
# workflow syntax as GitHub Actions (a compatible runner), so this is portable if the canonical
|
|
# remote ever needs to move CI providers, without being GitHub Actions itself or requiring a
|
|
# paid CI provider. Runs against the canonical Gitea repo only — see docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md
|
|
# §18 for why this never touches the GitHub kubekosh fork.
|
|
#
|
|
# Deploy this file to .gitea/workflows/build-and-test.yml at the repo root to activate it (kept
|
|
# under infra/ci/ in this PR so it's reviewed alongside the rest of the infra package first,
|
|
# per the same "build now, activate deliberately" posture as everything else server-dependent
|
|
# in this phase — enabling Gitea Actions on the instance is itself a one-time admin step that
|
|
# may not be done yet).
|
|
|
|
name: build-and-test
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
pull_request:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
regression:
|
|
runs-on: ubuntu-latest
|
|
services:
|
|
postgres:
|
|
image: postgres:16-alpine
|
|
env:
|
|
POSTGRES_PASSWORD: postgres
|
|
ports: ["5432:5432"]
|
|
options: >-
|
|
--health-cmd "pg_isready -U postgres"
|
|
--health-interval 10s --health-timeout 5s --health-retries 10
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
- name: Set up Node
|
|
uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: Install dependencies (all first-party services)
|
|
run: |
|
|
for d in backend frontend platform/api platform/gateway platform/provisioner platform/lab-router; do
|
|
(cd "$d" && npm ci)
|
|
done
|
|
|
|
- name: Syntax-check every service
|
|
run: |
|
|
for d in backend frontend/src platform/api platform/gateway platform/provisioner platform/lab-router; do
|
|
find "$d" -name '*.js' -not -path '*/node_modules/*' -exec node --check {} \;
|
|
done
|
|
|
|
- name: Lab Host Router unit tests (Phase 4.1 — fast, no Docker daemon needed)
|
|
run: (cd platform/lab-router && npm test)
|
|
|
|
- name: Build frontend
|
|
run: (cd frontend && npm run build)
|
|
|
|
- name: Provisioner migrations against the service Postgres
|
|
run: |
|
|
cd platform/provisioner
|
|
PLATFORM_DB_URL=postgres://postgres:postgres@localhost:5432/postgres \
|
|
node -e "require('./src/db').runMigrations().then(m=>console.log('Applied:',m.join(', ')))"
|
|
|
|
- name: Platform API migrations (depends on Provisioner's, same order as production)
|
|
run: |
|
|
cd platform/api
|
|
PLATFORM_DB_URL=postgres://postgres:postgres@localhost:5432/postgres \
|
|
node -e "require('./src/db').runMigrations().then(m=>console.log('Applied:',m.join(', ')))"
|
|
|
|
- name: Validate infra configs (Phase 4 local tests — no hardware needed)
|
|
run: |
|
|
./infra/scripts/validate-compose.sh
|
|
./infra/firewall/validate-firewall.sh
|
|
|
|
# The real k3s-boot-dependent e2e suites (platform/test/e2e-phase*.js) need a full Docker
|
|
# Compose stack with --privileged Lab containers, which a shared CI runner typically
|
|
# can't grant safely — they stay a LOCAL/staging pre-promotion step
|
|
# (infra/scripts/staging-smoke-test.sh), not this job. See docs/PHASE4_PRODUCTION_INFRASTRUCTURE.md §18.
|
|
|
|
# Phase 4.1 §24 — free/open-source security scanning, no paid tooling, no signup required for
|
|
# any of these. Severity policy (docs/SECURITY_SCANNING.md): CRITICAL/HIGH findings fail the
|
|
# build; MEDIUM/LOW are reported but never block a merge — a build that's permanently red over
|
|
# an unrelated low-severity transitive dependency finding trains everyone to ignore red builds.
|
|
security-scan:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- uses: actions/setup-node@v4
|
|
with:
|
|
node-version: "20"
|
|
|
|
- name: npm audit (dependency vulnerabilities) — fails only on high/critical
|
|
run: |
|
|
FAIL=0
|
|
for d in backend frontend platform/api platform/gateway platform/provisioner platform/lab-router; do
|
|
echo "== $d =="
|
|
(cd "$d" && npm audit --audit-level=high) || FAIL=1
|
|
done
|
|
exit $FAIL
|
|
|
|
- name: Secret scanning (gitleaks — free, no account needed)
|
|
run: docker run --rm -v "$PWD:/repo" zricethezav/gitleaks:latest detect --source=/repo --no-git -v
|
|
|
|
- name: Dockerfile linting (hadolint — free)
|
|
run: |
|
|
FAIL=0
|
|
for f in Dockerfile platform/api/Dockerfile platform/gateway/Dockerfile platform/provisioner/Dockerfile platform/egress-proxy/Dockerfile platform/lab-router/Dockerfile; do
|
|
echo "== $f =="
|
|
docker run --rm -i hadolint/hadolint < "$f" || FAIL=1
|
|
done
|
|
exit $FAIL
|
|
|
|
# -S error, not warning — matches docs/SECURITY_SCANNING.md's severity policy. Real run
|
|
# against this tree at -S warning surfaces only style nits (unused loop vars, a missing
|
|
# `cd ... || exit` guard) on scripts that are otherwise correct; failing the build on
|
|
# those trains everyone to ignore the check.
|
|
- name: Shell linting (shellcheck — free, standard)
|
|
run: |
|
|
sudo apt-get update -qq && sudo apt-get install -y -qq shellcheck
|
|
find infra -name '*.sh' -exec shellcheck -S error {} \+
|
|
|
|
- name: Container image scanning (trivy — free, no account needed)
|
|
run: |
|
|
docker build -q -t rootnode-scan/platform-api -f platform/api/Dockerfile .
|
|
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock aquasec/trivy:latest image \
|
|
--severity HIGH,CRITICAL --exit-code 1 rootnode-scan/platform-api
|
|
|
|
build-images:
|
|
needs: [regression, security-scan]
|
|
if: github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Build and tag images
|
|
run: ./infra/scripts/ci/tag-and-push.sh
|
|
env:
|
|
REGISTRY: ${{ vars.REGISTRY }}
|
|
REGISTRY_USER: ${{ secrets.REGISTRY_USER }}
|
|
REGISTRY_PASSWORD: ${{ secrets.REGISTRY_PASSWORD }}
|
|
|
|
deploy-staging:
|
|
needs: build-images
|
|
if: github.ref == 'refs/heads/main'
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
- name: Deploy to staging (automatic)
|
|
run: |
|
|
cd infra/ansible
|
|
ansible-playbook site.yml -i inventory/staging.ini \
|
|
-e "image_tag=staging-${GITHUB_SHA::12}" \
|
|
--vault-password-file <(echo "$VAULT_PASSWORD")
|
|
env:
|
|
VAULT_PASSWORD: ${{ secrets.ANSIBLE_VAULT_PASSWORD }}
|
|
|
|
- name: Staging smoke test
|
|
run: ./infra/scripts/staging-smoke-test.sh
|
|
|
|
# Production is NEVER automatic — a human runs infra/scripts/deploy-production.sh after
|
|
# reviewing the staging smoke test result, per the explicit "manual approval step" preference.
|
|
# No job here does that; see docs/RUNBOOK_NORMAL_DEPLOY.md for the actual command.
|