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>
94 lines
3.5 KiB
Bash
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!"
|