172 lines
7.0 KiB
YAML
172 lines
7.0 KiB
YAML
name: quality-gate
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- codex/**
|
|
pull_request:
|
|
workflow_dispatch:
|
|
|
|
jobs:
|
|
quality:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install .NET SDK
|
|
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
|
with:
|
|
dotnet-version: 10.0.301
|
|
|
|
- name: Restore locked dependencies
|
|
run: dotnet restore Rendezvous.slnx --locked-mode
|
|
|
|
- name: Verify dependency licenses and reviewed transport pin
|
|
run: python3 eng/release_artifacts.py policy --root .
|
|
|
|
- name: Reject vulnerable direct or transitive packages
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
dotnet package list --project Rendezvous.slnx \
|
|
--vulnerable --include-transitive --no-restore --format json \
|
|
>"${RUNNER_TEMP}/nuget-vulnerabilities.json"
|
|
python3 eng/release_artifacts.py audit \
|
|
--input "${RUNNER_TEMP}/nuget-vulnerabilities.json"
|
|
|
|
- name: Enforce compatibility version bumps
|
|
run: ./scripts/check-compatibility.sh origin/main
|
|
|
|
- name: Verify formatting and analyzers
|
|
run: dotnet format Rendezvous.slnx --verify-no-changes --no-restore
|
|
|
|
- name: Build
|
|
run: dotnet build Rendezvous.slnx --configuration Release --no-restore
|
|
|
|
- name: Verify generated API contract
|
|
run: git diff --exit-code -- docs/api
|
|
|
|
- name: Test
|
|
run: dotnet test Rendezvous.slnx --configuration Release --no-build
|
|
|
|
- name: Run quick capacity and resilience gate
|
|
run: ./scripts/run-capacity-gate.sh
|
|
|
|
- name: Test privileged Linux namespace topology when available
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
probe="rendezvous-probe-$$"
|
|
suffix="$(( $$ % 100000 ))"
|
|
bridge="rvb${suffix}"
|
|
veth_root="rvr${suffix}"
|
|
veth_peer="rvp${suffix}"
|
|
cleanup_probe() {
|
|
if [[ -n "$veth_root" ]]; then
|
|
ip link delete "$veth_root" >/dev/null 2>&1 || true
|
|
fi
|
|
if [[ -n "$bridge" ]]; then
|
|
ip link delete "$bridge" >/dev/null 2>&1 || true
|
|
fi
|
|
if [[ -n "$probe" ]]; then
|
|
ip netns delete "$probe" >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup_probe EXIT
|
|
if command -v ip >/dev/null 2>&1 \
|
|
&& command -v iptables >/dev/null 2>&1 \
|
|
&& command -v sysctl >/dev/null 2>&1 \
|
|
&& ip netns add "$probe" 2>/dev/null \
|
|
&& ip link add "$bridge" type bridge \
|
|
&& ip link add "$veth_root" type veth peer name "$veth_peer" \
|
|
&& ip link set "$veth_root" master "$bridge" \
|
|
&& ip link set "$veth_peer" netns "$probe" \
|
|
&& ip netns exec "$probe" sysctl -q -w net.ipv4.ip_forward=1 \
|
|
&& ip netns exec "$probe" iptables -t nat -A POSTROUTING -o "$veth_peer" -j MASQUERADE \
|
|
&& ip netns exec "$probe" iptables -A FORWARD -i "$veth_peer" -o lo \
|
|
-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT; then
|
|
ip link delete "$veth_root"
|
|
veth_root=""
|
|
ip link delete "$bridge"
|
|
bridge=""
|
|
ip netns delete "$probe"
|
|
probe=""
|
|
results="${RUNNER_TEMP:-/tmp}/rendezvous-netns-results"
|
|
mkdir -p "$results"
|
|
RENDEZVOUS_RUN_NETNS_TESTS=1 dotnet test Rendezvous.slnx \
|
|
--configuration Release \
|
|
--no-build \
|
|
--filter FullyQualifiedName~PrivilegedLinuxNatNamespacesCompleteDirectTrafficAcrossSeparateObservedEndpoints \
|
|
--logger "trx;LogFileName=netns.trx" \
|
|
--results-directory "$results"
|
|
grep -q 'testName="[^"]*\.PrivilegedLinuxNatNamespacesCompleteDirectTrafficAcrossSeparateObservedEndpoints"' \
|
|
"$results/netns.trx"
|
|
else
|
|
echo "Network namespaces/NAT tooling unavailable; deterministic loopback topology remains the required gate."
|
|
fi
|
|
|
|
container:
|
|
needs: quality
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
steps:
|
|
- name: Check out repository
|
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
|
|
- name: Install .NET SDK
|
|
uses: actions/setup-dotnet@67a3573c9a986a3f9c594539f4ab511d57bb3ce9 # v4.3.1
|
|
with:
|
|
dotnet-version: 10.0.301
|
|
|
|
- name: Build deployment diagnostic
|
|
run: |
|
|
dotnet restore src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj --locked-mode
|
|
dotnet build src/FinalFactory.Rendezvous.TestClient/FinalFactory.Rendezvous.TestClient.csproj --configuration Release --no-restore
|
|
|
|
- name: Build and exercise hardened container
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
compose_file="deploy/compose/compose.yaml"
|
|
secret="deploy/compose/secrets/signing-key"
|
|
cleanup() {
|
|
RENDEZVOUS_UID=1654 RENDEZVOUS_GID=1654 \
|
|
docker compose -f "$compose_file" down --volumes >/dev/null 2>&1 || true
|
|
rm -f "$secret"
|
|
}
|
|
trap cleanup EXIT
|
|
install -d -m 0700 deploy/compose/secrets
|
|
openssl rand -out "$secret" 32
|
|
chmod 0444 "$secret"
|
|
export RENDEZVOUS_UID=1654
|
|
export RENDEZVOUS_GID=1654
|
|
export SOURCE_REVISION_ID="$GITHUB_SHA"
|
|
docker compose -f "$compose_file" build \
|
|
--build-arg SOURCE_REVISION_ID="$SOURCE_REVISION_ID"
|
|
docker compose -f "$compose_file" up --no-build --detach
|
|
container_id="$(docker compose -f "$compose_file" ps -q rendezvous)"
|
|
test -n "$container_id"
|
|
test "$(docker inspect --format '{{.Config.User}}' "$container_id")" = "1654:1654"
|
|
test "$(docker inspect --format '{{.HostConfig.ReadonlyRootfs}}' "$container_id")" = "true"
|
|
test "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination \"/app/appsettings.Production.json\"}}{{.RW}}{{end}}{{end}}' "$container_id")" = "false"
|
|
test "$(docker inspect --format '{{range .Mounts}}{{if eq .Destination \"/run/secrets/rendezvous-signing-key\"}}{{.RW}}{{end}}{{end}}' "$container_id")" = "false"
|
|
for attempt in {1..100}; do
|
|
if curl --fail --silent http://127.0.0.1:8080/health/ready >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
if (( attempt == 100 )); then
|
|
docker compose -f "$compose_file" logs rendezvous
|
|
exit 1
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
./scripts/smoke-deployment.sh
|
|
docker compose -f "$compose_file" stop --timeout 40 rendezvous
|
|
test "$(docker inspect --format '{{.State.Running}}' "$container_id")" = "false"
|
|
test "$(docker inspect --format '{{.State.ExitCode}}' "$container_id")" = "0"
|