mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Complete release and helper download safeguards
This commit is contained in:
@@ -74,9 +74,11 @@ Normal stable publication and stable dry runs select `signpath` directly.
|
||||
- Release checksums and detached signatures are published alongside artifacts
|
||||
and verified independently after publication.
|
||||
- Release activation requires GitHub CLI 2.97.0 or newer, which includes the
|
||||
literal signer-identity matcher fix. The published checksum manifest must
|
||||
carry build provenance from the exact `create-release.yml` workflow and
|
||||
release source commit; repository-level provenance is not sufficient.
|
||||
literal signer-identity matcher fix. The shared
|
||||
`scripts/require-safe-gh-attestation.sh` guard enforces this floor. The
|
||||
published checksum manifest must carry build provenance from the exact
|
||||
`create-release.yml` workflow and release source commit; repository-level
|
||||
provenance is not sufficient.
|
||||
- Every new release is assembled and validated as a draft. Its activation
|
||||
marker is uploaded and digest-checked before publication; GitHub must then
|
||||
report the published release as immutable, protecting its tag and complete
|
||||
|
||||
@@ -4522,3 +4522,10 @@ the customer-promotion lease or mutate a floating image tag, Helm index,
|
||||
paid-runtime pointer, or live environment until the check passes. Repository
|
||||
release immutability must therefore be enabled before merging or running this
|
||||
activation path.
|
||||
|
||||
Attestation policy decisions require GitHub CLI 2.97.0 or newer so signer
|
||||
repository and workflow names are matched literally. The shared verifier must
|
||||
also bind the downloaded `checksums.txt` bytes to the immutable release and to
|
||||
SLSA v1 provenance from the exact `create-release.yml` workflow and expected
|
||||
source SHA. Multi-asset download retries clear both the activation marker and
|
||||
checksum manifest first so a partial attempt cannot poison every later retry.
|
||||
|
||||
@@ -10253,6 +10253,23 @@ func TestContract_AgentHelperDownloadIsLinuxOnlyAndSeparatelySigned(t *testing.T
|
||||
if response.Code != http.StatusBadRequest {
|
||||
t.Fatalf("non-Linux helper status = %d, want %d", response.Code, http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// The installer fetches this artifact before it has an API credential, so
|
||||
// the full router must preserve the documented public download boundary.
|
||||
cfg := newTestConfigWithTokens(t)
|
||||
cfg.AuthUser = "admin"
|
||||
cfg.AuthPass = "hashed"
|
||||
publicRouter := NewRouter(cfg, nil, nil, nil, nil, "v6.0.0")
|
||||
request = httptest.NewRequest(
|
||||
http.MethodPost,
|
||||
"/download/pulse-agent-helper?arch=linux-amd64",
|
||||
nil,
|
||||
)
|
||||
response = httptest.NewRecorder()
|
||||
publicRouter.Handler().ServeHTTP(response, request)
|
||||
if response.Code != http.StatusMethodNotAllowed {
|
||||
t.Fatalf("public helper download status = %d, want %d", response.Code, http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContract_AgentRunnerDownloadIsLinuxOnlyAndSeparatelySigned(t *testing.T) {
|
||||
|
||||
@@ -4742,6 +4742,7 @@ func TestPublicDownloadEndpointsBypassAuth(t *testing.T) {
|
||||
"/install.sh",
|
||||
"/install.ps1",
|
||||
"/download/pulse-agent",
|
||||
"/download/pulse-agent-helper?arch=linux-amd64",
|
||||
}
|
||||
|
||||
for idx, path := range paths {
|
||||
|
||||
@@ -25,6 +25,7 @@ class VerifyGitHubReleaseIntegrityTest(unittest.TestCase):
|
||||
asset_verification_succeeds: bool = True,
|
||||
provenance_verification_succeeds: bool = True,
|
||||
gh_version: str = "2.97.0",
|
||||
partial_download_once: bool = False,
|
||||
):
|
||||
with tempfile.TemporaryDirectory() as directory:
|
||||
root = Path(directory)
|
||||
@@ -54,6 +55,14 @@ class VerifyGitHubReleaseIntegrityTest(unittest.TestCase):
|
||||
while [ "$#" -gt 0 ]; do
|
||||
if [ "$1" = --dir ]; then
|
||||
mkdir -p "$2"
|
||||
if [ "$PARTIAL_DOWNLOAD_ONCE" = true ] && [ ! -e "$DOWNLOAD_STATE" ]; then
|
||||
touch "$DOWNLOAD_STATE"
|
||||
printf '%s\\n' 'abc pulse-v6.5.0-linux-amd64.tar.gz' > "$2/checksums.txt"
|
||||
exit 1
|
||||
fi
|
||||
if [ -e "$2/release-activation.json" ] || [ -e "$2/checksums.txt" ]; then
|
||||
exit 66
|
||||
fi
|
||||
printf '%s\\n' '{{"schema_version": 1}}' > "$2/release-activation.json"
|
||||
printf '%s\\n' 'abc pulse-v6.5.0-linux-amd64.tar.gz' > "$2/checksums.txt"
|
||||
exit 0
|
||||
@@ -79,9 +88,13 @@ class VerifyGitHubReleaseIntegrityTest(unittest.TestCase):
|
||||
env.update(
|
||||
{
|
||||
"PATH": f"{root}:{env['PATH']}",
|
||||
"PULSE_RELEASE_ATTESTATION_ATTEMPTS": "1",
|
||||
"PULSE_RELEASE_ATTESTATION_ATTEMPTS": (
|
||||
"2" if partial_download_once else "1"
|
||||
),
|
||||
"PULSE_RELEASE_ATTESTATION_RETRY_DELAY": "0",
|
||||
"GH_VERSION": gh_version,
|
||||
"PARTIAL_DOWNLOAD_ONCE": str(partial_download_once).lower(),
|
||||
"DOWNLOAD_STATE": str(root / "download-state"),
|
||||
}
|
||||
)
|
||||
result = subprocess.run(
|
||||
@@ -166,6 +179,13 @@ class VerifyGitHubReleaseIntegrityTest(unittest.TestCase):
|
||||
self.assertIn("checksum manifest build provenance verification failed", result.stderr)
|
||||
self.assertIn("attestation verify", calls)
|
||||
|
||||
def test_recovers_from_a_partial_multi_asset_download(self) -> None:
|
||||
result, calls = self.run_verifier(
|
||||
self.release(), partial_download_once=True
|
||||
)
|
||||
self.assertEqual(result.returncode, 0, result.stderr)
|
||||
self.assertEqual(calls.count("release download"), 2)
|
||||
|
||||
def test_rejects_an_unsafe_github_cli_before_release_lookup(self) -> None:
|
||||
result, calls = self.run_verifier(self.release(), gh_version="2.96.1")
|
||||
self.assertNotEqual(result.returncode, 0)
|
||||
|
||||
@@ -124,7 +124,10 @@ fi
|
||||
# release attestation rather than trusting filename and JSON identity alone.
|
||||
downloaded=false
|
||||
for attempt in $(seq 1 "$ATTESTATION_ATTEMPTS"); do
|
||||
rm -f "$activation_asset"
|
||||
# A previous attempt can leave either asset behind after a partial
|
||||
# download. Clear both because gh release download refuses to overwrite
|
||||
# existing files unless explicitly told to do so.
|
||||
rm -f "$activation_asset" "$checksums_asset"
|
||||
if gh release download "$TAG" \
|
||||
--repo "$REPO" \
|
||||
--pattern release-activation.json \
|
||||
|
||||
Reference in New Issue
Block a user