server-monitor-manager/tests/release-verification/run-negative-tests.sh
Ochenstarik c1e47684e6 fix(verification): verify releases the way an operator does
PR #40 correctly publishes the keyless signing certificate and makes
verify_archive require manifest, signature and certificate together. Three
things around it were inconsistent.

The release-verification scripts fetched assets with `gh release download`.
The workspace-isolation step deliberately removes `.git` so that the install
comes from the release rather than the source tree, and `gh` resolves the
repository from that git context — the tool fought the isolation it runs
inside, which is what broke the first runs. An operator has neither `gh` nor a
token, so the scripts now use public `curl`, `sha256sum` and `cosign` only.
`verify-assets.sh` keeps `gh`: it runs before isolation and only lists assets.

The monitor check addressed a user and a home directory that do not exist.
The bootstrap creates `ochenstarik-monitor` with `/var/lib/ochenstarik-monitor`;
the script used `ochenstarik-smm-monitor` under the Control state directory, so
that section could never have passed. It now also asserts that the forced
command is pinned in `authorized_keys` before running it, and quotes the
command instead of splitting it on whitespace.

The expected asset list did not include the new certificate, so a correct
release would have been reported as unexpected.

Also: a negative case for an archive published without its certificate, removal
of drafting comments that quoted the task text, and documentation of the three
signature files, since `verify-release` now requires them beside the archive and
the documented download list stopped being sufficient.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 13:16:27 +07:00

94 lines
3.5 KiB
Bash

#!/bin/bash
# Every tampering scenario an operator could hit must be rejected by the release
# artefacts themselves. Downloads use public curl only: gh needs git context that
# the isolated workspace removes, and the operator has neither gh nor a token.
set -euo pipefail
TAG="${1:-}"
REPOSITORY="${SMM_REPOSITORY:-ochenstarik-ui/server-monitor-manager}"
if [[ -z "$TAG" ]]; then
echo "Usage: $0 <tag>" >&2
exit 1
fi
BASE_URL="https://github.com/${REPOSITORY}/releases/download/${TAG}"
echo "Running negative tests against release $TAG..."
download() {
local name="$1"
curl --fail --silent --show-error --location --retry 3 \
-o "$name" "${BASE_URL}/${name}" \
|| { echo "FAIL: asset is not downloadable: $name" >&2; exit 1; }
}
case "$(uname -m)" in
x86_64) RUNTIME="linux-x64" ;;
aarch64|arm64) RUNTIME="linux-arm64" ;;
*) echo "FAIL: unsupported architecture $(uname -m)" >&2; exit 1 ;;
esac
ARCHIVE="server-monitor-manager-${RUNTIME}.tar.gz"
download ochenstarik-server-monitor-manager.sh
chmod +x ochenstarik-server-monitor-manager.sh
download "$ARCHIVE"
download "$ARCHIVE.sha256"
download server-monitor-manager-manifest.json
download server-monitor-manager-manifest.sig
download server-monitor-manager-manifest.pem
echo "Test 1: Altered byte in archive"
cp "$ARCHIVE" "corrupted-$ARCHIVE"
cp "$ARCHIVE.sha256" "corrupted-$ARCHIVE.sha256"
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!" >&2
exit 1
fi
echo "PASS: Altered archive rejected."
rm -f "corrupted-$ARCHIVE" "corrupted-$ARCHIVE.sha256"
echo "Test 2: Substituted hash in manifest without resigning"
cp server-monitor-manager-manifest.json corrupted-manifest.json
sed -i 's/"[a-f0-9]\{64\}"/"0000000000000000000000000000000000000000000000000000000000000000"/g' corrupted-manifest.json
if ./ochenstarik-server-monitor-manager.sh verify-manifest corrupted-manifest.json \
server-monitor-manager-manifest.sig server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Manifest with substituted hash accepted!" >&2
exit 1
fi
echo "PASS: Substituted hash rejected."
rm -f corrupted-manifest.json
echo "Test 3: Manifest without signature"
if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json \
"" server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Manifest without signature accepted!" >&2
exit 1
fi
echo "PASS: Missing signature rejected."
echo "Test 4: Signature made by another identity"
export COSIGN_PASSWORD=""
cosign generate-key-pair >/dev/null
cosign sign-blob --yes --key cosign.key \
--output-signature fake.sig server-monitor-manager-manifest.json >/dev/null
if ./ochenstarik-server-monitor-manager.sh verify-manifest server-monitor-manager-manifest.json \
fake.sig server-monitor-manager-manifest.pem >/dev/null 2>&1; then
echo "FAIL: Signature from wrong identity accepted!" >&2
exit 1
fi
echo "PASS: Wrong identity signature rejected."
rm -f cosign.key cosign.pub fake.sig
echo "Test 5: Missing certificate beside the archive"
mkdir -p no-cert && cp "$ARCHIVE" "$ARCHIVE.sha256" \
server-monitor-manager-manifest.json server-monitor-manager-manifest.sig no-cert/
if ./ochenstarik-server-monitor-manager.sh verify-release "no-cert/$ARCHIVE" >/dev/null 2>&1; then
echo "FAIL: Archive accepted without the signing certificate!" >&2
exit 1
fi
echo "PASS: Missing certificate rejected."
rm -rf no-cert
echo "All negative tests passed!"