From 6ca8ac8469ed93cc90b04ccae9ec0465d3e53b4d Mon Sep 17 00:00:00 2001 From: ochenstarik-ui Date: Tue, 11 Aug 2026 01:28:50 +0700 Subject: [PATCH] Add release verification workflow --- .github/workflows/release-verification.yml | 69 ++++++++++++++ .../run-negative-tests.sh | 66 ++++++++++++++ .../run-positive-installation.sh | 89 +++++++++++++++++++ tests/release-verification/verify-assets.sh | 51 +++++++++++ 4 files changed, 275 insertions(+) create mode 100644 .github/workflows/release-verification.yml create mode 100644 tests/release-verification/run-negative-tests.sh create mode 100644 tests/release-verification/run-positive-installation.sh create mode 100644 tests/release-verification/verify-assets.sh diff --git a/.github/workflows/release-verification.yml b/.github/workflows/release-verification.yml new file mode 100644 index 0000000..11fc1ef --- /dev/null +++ b/.github/workflows/release-verification.yml @@ -0,0 +1,69 @@ +name: Release Verification + +on: + release: + types: [published] + workflow_dispatch: + inputs: + tag: + description: 'Release tag to verify' + required: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - name: Checkout tests + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + sparse-checkout: | + tests/release-verification + tests/contracts/monitor-snapshot-v1.txt + sparse-checkout-cone-mode: false + + - name: Setup cosign + uses: sigstore/cosign-installer@v3.5.0 + + - name: Determine Tag + id: tag + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + fi + + - name: Verify Assets List + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + bash tests/release-verification/verify-assets.sh "${{ steps.tag.outputs.tag }}" + + - name: Isolate Workspace + run: | + # Move tests to a safe location and clean the workspace + # This ensures the installation does not accidentally use source files + cp -r tests /tmp/isolated_tests + rm -rf * .git + + - name: Run Positive Installation + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p workspace + cd workspace + # Bring contract back for verification script + mkdir -p tests/contracts + cp /tmp/isolated_tests/contracts/monitor-snapshot-v1.txt tests/contracts/ + bash /tmp/isolated_tests/release-verification/run-positive-installation.sh "${{ steps.tag.outputs.tag }}" + + - name: Run Negative Tests + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + mkdir -p workspace-neg + cd workspace-neg + bash /tmp/isolated_tests/release-verification/run-negative-tests.sh "${{ steps.tag.outputs.tag }}" diff --git a/tests/release-verification/run-negative-tests.sh b/tests/release-verification/run-negative-tests.sh new file mode 100644 index 0000000..64128f3 --- /dev/null +++ b/tests/release-verification/run-negative-tests.sh @@ -0,0 +1,66 @@ +#!/bin/bash +set -euo pipefail + +TAG="${1:-}" + +if [[ -z "$TAG" ]]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Running negative tests against release $TAG..." + +# We will need smm-setup.sh or ochenstarik-server-monitor-manager.sh +# We'll download ochenstarik-server-monitor-manager.sh directly to test verify-release +gh release download "$TAG" -p 'ochenstarik-server-monitor-manager.sh' +chmod +x ochenstarik-server-monitor-manager.sh + +ARCHIVE="server-monitor-manager-linux-$(uname -m | sed -e 's/x86_64/x64/' -e 's/aarch64/arm64/').tar.gz" +gh release download "$TAG" -p "$ARCHIVE" +gh release download "$TAG" -p "server-monitor-manager-manifest.json" +gh release download "$TAG" -p "server-monitor-manager-manifest.sig" + +echo "Test 1: Altered byte in archive" +cp "$ARCHIVE" "corrupted-$ARCHIVE" +echo "corrupted" >> "corrupted-$ARCHIVE" +if ./ochenstarik-server-monitor-manager.sh verify-release "corrupted-$ARCHIVE" >/dev/null 2>&1; then + echo "FAIL: Altered archive was accepted!" + exit 1 +fi +echo "PASS: Altered archive rejected." +rm "corrupted-$ARCHIVE" + +echo "Test 2: Substituted hash in manifest without resigning" +cp server-monitor-manager-manifest.json corrupted-manifest.json +# Replace all hashes with zeros +sed -i 's/"[a-f0-9]\{64\}"/"0000000000000000000000000000000000000000000000000000000000000000"/g' corrupted-manifest.json +# Test verify-manifest directly +if ./ochenstarik-server-monitor-manager.sh verify-manifest corrupted-manifest.json server-monitor-manager-manifest.sig >/dev/null 2>&1; then + echo "FAIL: Manifest with substituted hash accepted!" + exit 1 +fi +echo "PASS: Substituted hash rejected." +rm corrupted-manifest.json + +echo "Test 3: Manifest without signature" +# We just pass an empty string for the signature file argument +if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json "" >/dev/null 2>&1; then + echo "FAIL: Manifest without signature accepted!" + exit 1 +fi +echo "PASS: Missing signature rejected." + +echo "Test 4: Signature made by another identity" +# Generate a local keypair and sign the manifest +export COSIGN_PASSWORD="" +cosign generate-key-pair +cosign sign-blob --yes --key cosign.key --output-signature fake.sig server-monitor-manager-manifest.json +# Verification must fail because ochenstarik-server-monitor-manager.sh enforces keyless OIDC identity! +if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json fake.sig >/dev/null 2>&1; then + echo "FAIL: Signature from wrong identity accepted!" + exit 1 +fi +echo "PASS: Wrong identity signature rejected." +rm cosign.key cosign.pub fake.sig + +echo "All negative tests passed!" diff --git a/tests/release-verification/run-positive-installation.sh b/tests/release-verification/run-positive-installation.sh new file mode 100644 index 0000000..1796fb6 --- /dev/null +++ b/tests/release-verification/run-positive-installation.sh @@ -0,0 +1,89 @@ +#!/bin/bash +set -euo pipefail + +TAG="${1:-}" + +if [[ -z "$TAG" ]]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Running positive installation test for $TAG..." + +# Fetch smm-setup.sh +gh release download "$TAG" -p 'smm-setup.sh*' + +# Verify checksum +sha256sum -c smm-setup.sh.sha256 + +# The archive is downloaded by verify-release or we must download it? +# In smm-setup.sh, the owner manually downloads the archive? +# Wait, let's look at docs: "загрузка bootstrap и архива из релиза, проверка контрольных сумм, проверка подписи manifest" +# Actually, the user does: +ARCHIVE="server-monitor-manager-linux-$(uname -m | sed -e 's/x86_64/x64/' -e 's/aarch64/arm64/').tar.gz" +gh release download "$TAG" -p "$ARCHIVE*" +gh release download "$TAG" -p "server-monitor-manager-manifest.*" + +sha256sum -c "$ARCHIVE.sha256" + +# Run setup steps through smm-setup.sh +# "preflight, verify-release, установка Control, mesh-init" +sudo bash smm-setup.sh preflight +sudo bash smm-setup.sh verify-manifest server-monitor-manager-manifest.json server-monitor-manager-manifest.sig +sudo bash smm-setup.sh verify-release "$ARCHIVE" +sudo bash smm-setup.sh install-control "$ARCHIVE" 127.0.0.1 17443 +sudo bash smm-setup.sh mesh-init 127.0.0.1 51820 + +echo "Checking Control healthz..." +for _ in {1..30}; do + if sudo curl --fail --silent \ + --cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \ + "https://127.0.0.1:17443/healthz" >/dev/null; then + break + fi + sleep 1 +done +sudo curl --fail --silent --show-error \ + --cacert /etc/ochenstarik-server-monitor-manager/control-ca.crt \ + "https://127.0.0.1:17443/healthz" + +echo "Extracting node code and installing agent..." +NODE_CODE=$(sudo bash smm-setup.sh node-code test-node) +export SMM_ENROLL_CODE="$NODE_CODE" +export SMM_ACCEPT_CA_FINGERPRINT=1 +sudo --preserve-env=SMM_ENROLL_CODE,SMM_ACCEPT_CA_FINGERPRINT bash smm-setup.sh install-node "$ARCHIVE" + +sudo systemctl is-active --quiet ochenstarik-smm-agent.service +sudo systemctl is-active --quiet ochenstarik-smm-control.service + +# Verify install-monitor +echo "Installing monitor..." +# Generate a dummy SSH key for the test +ssh-keygen -t ed25519 -N "" -f /tmp/monitor_key +MONITOR_PUB=$(cat /tmp/monitor_key.pub) +sudo bash smm-setup.sh install-monitor "$MONITOR_PUB" + +echo "Verifying monitor user and forced command..." +# Run SSH locally as the monitor user (assuming ssh is configured, but actually we can just su into the user or run the forced command directly) +# The forced command is likely defined in ~smm-monitor/.ssh/authorized_keys +MONITOR_CMD=$(sudo cat /var/lib/ochenstarik-server-monitor-manager/monitor/.ssh/authorized_keys | grep -o 'command="[^"]*"' | cut -d'"' -f2) +SNAPSHOT=$(sudo -u ochenstarik-smm-monitor $MONITOR_CMD) + +# Simple validation of snapshot fields (since actual values vary, we just check keys) +EXPECTED_KEYS=$(cat tests/contracts/monitor-snapshot-v1.txt | cut -d'=' -f1 | sort) +ACTUAL_KEYS=$(echo "$SNAPSHOT" | cut -d'=' -f1 | sort) + +if [[ "$EXPECTED_KEYS" == "$ACTUAL_KEYS" ]]; then + echo "Monitor snapshot keys match contract." +else + echo "Monitor snapshot keys mismatch!" + diff <(echo "$EXPECTED_KEYS") <(echo "$ACTUAL_KEYS") || true + exit 1 +fi + +# Verify uninstall +sudo bash smm-setup.sh uninstall-monitor +sudo bash smm-setup.sh uninstall-agent --purge +sudo bash smm-setup.sh uninstall-control --confirm-destroy-control + +echo "Positive installation test passed!" diff --git a/tests/release-verification/verify-assets.sh b/tests/release-verification/verify-assets.sh new file mode 100644 index 0000000..719f619 --- /dev/null +++ b/tests/release-verification/verify-assets.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -euo pipefail + +TAG="${1:-}" + +if [[ -z "$TAG" ]]; then + echo "Usage: $0 " + exit 1 +fi + +echo "Verifying assets for release $TAG..." + +# Fetch the list of assets from the release +ACTUAL_ASSETS=$(gh release view "$TAG" --json assets -q '.assets[].name' | sort) + +EXPECTED_ASSETS=$(cat <