Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions release/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,7 @@ candidate_ref="ghcr.io/registrystack/${name}-candidate@${digest}"
evidence_dir="advisory-renewal-${name}-${run_id}-${run_attempt}"
mkdir -p "${evidence_dir}/rootfs"
crane config "${candidate_ref}" > "${evidence_dir}/oci-config.json"
docker pull --platform linux/amd64 "${candidate_ref}" >/dev/null
Comment thread
jeremi marked this conversation as resolved.
Comment thread
jeremi marked this conversation as resolved.
SYFT_FILE_METADATA_SELECTION=all SYFT_FILE_METADATA_DIGESTS=sha256 \
syft "${candidate_ref}" -o syft-json="${evidence_dir}/syft.json"
grype "${candidate_ref}" -o json > "${evidence_dir}/grype.json"
Expand All @@ -281,6 +282,15 @@ crane export "${candidate_ref}" - | tar --extract --file=- \
--no-same-owner --no-same-permissions
```

The pull is what makes the scan admissible, not a convenience. Syft and Grype
fill the `architecture` and `os` fields of their image target only from a
daemon-backed provider. Resolved straight from the registry they leave both
empty, and `check-advisory-baselines.py` rejects that evidence with
`grype image target must be linux/amd64`. On an `amd64` candidate the message
does not mean the architecture is wrong; it means the scan never went through
the daemon. The candidate workflow satisfies this incidentally, by running the
image once to record its `--version` before it scans.

Select the matching baseline and confirm its pinned base is still the exact
prefix of the candidate's authoritative uncompressed DiffIDs:

Expand Down
33 changes: 33 additions & 0 deletions release/scripts/test_release_workflow_structure.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,6 +772,23 @@ def test_only_pre_oidc_reverification_accepts_the_current_run(self) -> None:
self.assertEqual(text.count("--allow-current-run-in-progress"), 1)
self.assertIn("--allow-current-run-in-progress", reverify)

def test_runs_each_candidate_image_before_it_scans_it(self) -> None:
_, document = workflow("release-candidate.yml")
Comment thread
jeremi marked this conversation as resolved.
scan = step_run(
document,
"assemble",
"Verify and scan exact candidate images",
)
# Syft and Grype fill an image target's architecture and os only from
# a daemon-backed provider, and check-advisory-baselines.py rejects
# evidence that leaves them empty. Running the image is what puts it
# in the daemon, so moving either scan ahead of it fails the gate with
# a linux/amd64 message on an image that is amd64.
run_image = scan.index('"${candidate_ref}" --version')
self.assertIn("docker run --rm", scan[:run_image])
self.assertLess(run_image, scan.index('syft "${candidate_ref}"'))
self.assertLess(run_image, scan.index("scan_image \\"))


class PublicationWorkflowStructureTest(unittest.TestCase):
def test_is_a_manual_main_workflow_with_recoverable_jobs(self) -> None:
Expand Down Expand Up @@ -1208,6 +1225,22 @@ def test_operator_docs_match_the_latest_non_prerelease_contract(self) -> None:
self.assertIn(".isPrerelease == false", verify)
self.assertNotIn(".isPrerelease == true", verify)

def test_advisory_renewal_pulls_the_candidate_before_scanning_it(self) -> None:
operations = (ROOT / "release/OPERATIONS.md").read_text(encoding="utf-8")
renewal = next(
block
for block in operations.split("```")
if 'syft "${candidate_ref}"' in block
)
# The renewal commands are run by hand, so they carry the daemon-backed
# pull the candidate workflow gets incidentally from running the image.
# Without it Syft and Grype leave the target's architecture and os
# empty and check-advisory-baselines.py rejects the evidence as not
# linux/amd64.
pull = renewal.index('docker pull --platform linux/amd64 "${candidate_ref}"')
self.assertLess(pull, renewal.index('syft "${candidate_ref}"'))
self.assertLess(pull, renewal.index('grype "${candidate_ref}"'))

def test_docs_deploys_main_and_rechecks_latest_docs_release(self) -> None:
text, document = workflow("docs-pages.yml")
releases_endpoint = '"repos/${GITHUB_REPOSITORY}/releases?per_page=100"'
Expand Down