mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-19 02:28:59 +00:00
Authenticate every published installer
This commit is contained in:
parent
a190703fe5
commit
240adaa5e4
6 changed files with 79 additions and 2 deletions
|
|
@ -3429,6 +3429,12 @@ installers consume. macOS notarization is intentionally skipped
|
|||
for v1: the README documents the Gatekeeper bypass and the
|
||||
install-script flow downloads the same unsigned binary, with the
|
||||
audit trail of signed-manifest and SHA256 verification preserved.
|
||||
Because the shell installer is itself an executable bootstrap trust boundary,
|
||||
release assembly must include `install-mcp.sh` in the authenticated checksum
|
||||
manifest and emit its checksum and detached-signature sidecars before upload;
|
||||
validating only the binary that an unauthenticated installer chooses is not a
|
||||
complete install chain. Local and post-publication validation require every
|
||||
published installer entry exactly once in that signed manifest.
|
||||
The adapter's complete request/response tool-list projection, manifest
|
||||
projection, capability and governance metadata formatting, request/response
|
||||
tool filtering, typed input-schema projection, and API route/body call
|
||||
|
|
@ -4416,7 +4422,8 @@ release packet, and make post-publication validation authenticate
|
|||
`checksums.txt` and every listed artifact's `.sshsig` against the configured
|
||||
`PULSE_UPDATE_SIGNING_PUBLIC_KEY`, not merely test that sidecars are present.
|
||||
Validation must fail if the trust root is unavailable, if any signature is
|
||||
invalid, if any published artifact or `checksums.txt` is missing its
|
||||
invalid, if any published installer is absent from the authenticated checksum
|
||||
manifest, if any published artifact or `checksums.txt` is missing its
|
||||
`.sshsig` sidecar, if the authenticated checksum manifest contains duplicate
|
||||
asset filenames or trailing fields, or if the canonical
|
||||
release-packet SBOM is absent so published RC/stable downloads can keep the
|
||||
|
|
|
|||
|
|
@ -1363,6 +1363,9 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||
}
|
||||
localValidator := string(localValidatorBytes)
|
||||
localRequired := []string{
|
||||
`"install-mcp.sh"`,
|
||||
`for installer in install.sh install-docker.sh install-mcp.sh install-mcp.ps1 install.ps1 pulse-auto-update.sh; do`,
|
||||
`checksums.txt must contain exactly one entry for release installer ${installer}`,
|
||||
`"pulse-v${PULSE_VERSION}-release.sbom.spdx.json"`,
|
||||
`release_sbom="pulse-${PULSE_TAG}-release.sbom.spdx.json"`,
|
||||
`error "checksums.txt is missing ${release_sbom}"`,
|
||||
|
|
@ -1460,6 +1463,9 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||
}
|
||||
publishedValidator := string(publishedValidatorBytes)
|
||||
publishedRequired := []string{
|
||||
`REQUIRED_SIGNED_INSTALLERS=(`,
|
||||
`install-mcp.sh`,
|
||||
`Authenticated checksums.txt must contain exactly one valid entry for published installer ${installer}.`,
|
||||
`RELEASE_SBOM="pulse-${TAG}-release.sbom.spdx.json"`,
|
||||
`PULSE_UPDATE_SIGNING_PUBLIC_KEY is required to authenticate published release assets.`,
|
||||
`go -C "$REPO_ROOT" run ./scripts/release_update_key.go public-key-ssh`,
|
||||
|
|
@ -1503,6 +1509,7 @@ func TestReleaseValidationRequiresSignedSidecars(t *testing.T) {
|
|||
"make post-publication validation authenticate",
|
||||
"every listed artifact's `.sshsig` against the configured",
|
||||
"Validation must fail if the trust root is unavailable",
|
||||
"any published installer is absent from the authenticated checksum",
|
||||
"release-packet SBOM is absent",
|
||||
"download endpoints must return checksum and signature headers",
|
||||
"must disable Go's automatic VCS stamping",
|
||||
|
|
@ -2805,6 +2812,7 @@ func TestBuildReleasePackagesPulseMcpForAllPlatforms(t *testing.T) {
|
|||
`checksum_files+=( pulse-mcp-linux-* )`,
|
||||
`checksum_files+=( pulse-mcp-darwin-* )`,
|
||||
`checksum_files+=( pulse-mcp-freebsd-* )`,
|
||||
`checksum_files+=( install-mcp.sh )`,
|
||||
} {
|
||||
if !strings.Contains(string(commonContent), needle) {
|
||||
t.Fatalf("release checksum collection missing bare MCP assets: %s", needle)
|
||||
|
|
@ -2816,6 +2824,7 @@ func TestBuildReleasePackagesPulseMcpForAllPlatforms(t *testing.T) {
|
|||
"pulse-mcp-linux-amd64",
|
||||
"pulse-mcp-darwin-arm64",
|
||||
"pulse-mcp-freebsd-amd64",
|
||||
"install-mcp.sh",
|
||||
} {
|
||||
if err := os.WriteFile(filepath.Join(releaseDir, asset), []byte(asset), 0o755); err != nil {
|
||||
t.Fatalf("write MCP checksum fixture: %v", err)
|
||||
|
|
@ -2826,7 +2835,7 @@ func TestBuildReleasePackagesPulseMcpForAllPlatforms(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatalf("collect MCP release checksum files: %v\n%s", err, checksumOutput)
|
||||
}
|
||||
for _, asset := range []string{"pulse-mcp-linux-amd64", "pulse-mcp-darwin-arm64", "pulse-mcp-freebsd-amd64"} {
|
||||
for _, asset := range []string{"pulse-mcp-linux-amd64", "pulse-mcp-darwin-arm64", "pulse-mcp-freebsd-amd64", "install-mcp.sh"} {
|
||||
if !strings.Contains(string(checksumOutput), asset) {
|
||||
t.Fatalf("bare MCP release asset %s missing from checksum/signature input:\n%s", asset, checksumOutput)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -287,6 +287,9 @@ pulse_release_collect_checksum_files() {
|
|||
if [[ -f "install-docker.sh" ]]; then
|
||||
checksum_files+=( install-docker.sh )
|
||||
fi
|
||||
if [[ -f "install-mcp.sh" ]]; then
|
||||
checksum_files+=( install-mcp.sh )
|
||||
fi
|
||||
if [[ -f "pulse-auto-update.sh" ]]; then
|
||||
checksum_files+=( pulse-auto-update.sh )
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -47,7 +47,12 @@ class ValidatePublishedReleaseTest(unittest.TestCase):
|
|||
|
||||
payloads = {
|
||||
SBOM: b'{"spdxVersion":"SPDX-2.3"}\n',
|
||||
"install-docker.sh": b"#!/bin/sh\necho docker installer\n",
|
||||
"install-mcp.sh": b"#!/bin/sh\necho mcp installer\n",
|
||||
"install-mcp.ps1": b"Write-Output 'mcp installer'\r\n",
|
||||
"install.ps1": b"Write-Output 'agent installer'\r\n",
|
||||
"install.sh": b"#!/bin/sh\necho pulse\n",
|
||||
"pulse-auto-update.sh": b"#!/bin/sh\necho update\n",
|
||||
}
|
||||
checksum_lines: list[str] = []
|
||||
for name, content in payloads.items():
|
||||
|
|
@ -154,6 +159,23 @@ printf '%s\n' "${FAKE_SSH_PUBLIC_KEY}"
|
|||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn("SSH signature verification failed for install.sh", result.stderr)
|
||||
|
||||
def test_rejects_unsigned_published_mcp_installer(self) -> None:
|
||||
checksums = (self.assets / "checksums.txt").read_text(encoding="utf-8")
|
||||
filtered = "".join(
|
||||
line
|
||||
for line in checksums.splitlines(keepends=True)
|
||||
if not line.endswith(" install-mcp.sh\n")
|
||||
)
|
||||
self.replace_signed_checksums(filtered)
|
||||
|
||||
result = self.run_validator()
|
||||
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
self.assertIn(
|
||||
"Authenticated checksums.txt must contain exactly one valid entry for published installer install-mcp.sh.",
|
||||
result.stderr,
|
||||
)
|
||||
|
||||
def test_rejects_forged_checksum_manifest_before_using_it(self) -> None:
|
||||
with (self.assets / "checksums.txt").open("a", encoding="utf-8") as handle:
|
||||
handle.write(f"{'0' * 64} attacker-payload\n")
|
||||
|
|
|
|||
|
|
@ -85,6 +85,21 @@ if ! verify_signature "$CHECKSUMS_PATH" "$CHECKSUMS_SIG_PATH" "checksums.txt for
|
|||
exit 1
|
||||
fi
|
||||
|
||||
REQUIRED_SIGNED_INSTALLERS=(
|
||||
install.sh
|
||||
install-docker.sh
|
||||
install-mcp.sh
|
||||
install-mcp.ps1
|
||||
install.ps1
|
||||
pulse-auto-update.sh
|
||||
)
|
||||
for installer in "${REQUIRED_SIGNED_INSTALLERS[@]}"; do
|
||||
if ! awk -v name="$installer" '$2 == name { found = 1 } END { exit !found }' "$CHECKSUMS_PATH"; then
|
||||
echo "Authenticated checksums.txt must contain exactly one valid entry for published installer ${installer}." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
RELEASE_SBOM="pulse-${TAG}-release.sbom.spdx.json"
|
||||
if ! awk '{print $2}' "$CHECKSUMS_PATH" | grep -Fx "$RELEASE_SBOM" >/dev/null 2>&1; then
|
||||
echo "checksums.txt does not list ${RELEASE_SBOM} for ${TAG}" >&2
|
||||
|
|
@ -191,4 +206,11 @@ if [[ "$status" -ne 0 ]]; then
|
|||
exit 1
|
||||
fi
|
||||
|
||||
for installer in "${REQUIRED_SIGNED_INSTALLERS[@]}"; do
|
||||
if ! grep -Fxq -- "$installer" "$SEEN_FILENAMES_PATH"; then
|
||||
echo "Authenticated checksums.txt must contain exactly one valid entry for published installer ${installer}." >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
echo "Published release assets for ${TAG} match authenticated checksums.txt, *.sha256 files, and verified *.sshsig sidecars."
|
||||
|
|
|
|||
|
|
@ -449,6 +449,11 @@ info "Checking required release assets..."
|
|||
# They are only included in Docker images for /download/ endpoints
|
||||
required_assets=(
|
||||
"install.sh"
|
||||
"install-docker.sh"
|
||||
"install-mcp.sh"
|
||||
"install-mcp.ps1"
|
||||
"install.ps1"
|
||||
"pulse-auto-update.sh"
|
||||
"checksums.txt"
|
||||
"pulse-v${PULSE_VERSION}-release.sbom.spdx.json"
|
||||
"pulse-v${PULSE_VERSION}.tar.gz"
|
||||
|
|
@ -490,6 +495,15 @@ if [ $missing_count -gt 0 ]; then
|
|||
fi
|
||||
success "All ${#required_assets[@]} required release assets present"
|
||||
|
||||
for installer in install.sh install-docker.sh install-mcp.sh install-mcp.ps1 install.ps1 pulse-auto-update.sh; do
|
||||
manifest_matches="$(awk -v name="${installer}" '$2 == name && NF == 2 { count++ } END { print count + 0 }' checksums.txt)"
|
||||
if [ "${manifest_matches}" -ne 1 ]; then
|
||||
error "checksums.txt must contain exactly one entry for release installer ${installer}"
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
success "All published release installers are uniquely listed in checksums.txt"
|
||||
|
||||
# Validate published install.sh is the Pulse SERVER installer.
|
||||
# Across v6 rc.1 → rc.5 the rendered AGENT installer was published here by
|
||||
# mistake, silently breaking the LXC quickstart, the in-product Update Pulse
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue