mirror of
https://github.com/freedomofpress/dangerzone.git
synced 2025-04-28 09:52:37 +02:00
Publish and attest multi-architecture container images
A new `dangerzone-image attest-provenance` script is now available, making it possible to verify the attestations of an image published on the github container registry. Container images are now build nightly and uploaded to the container registry.
This commit is contained in:
parent
a9fec44837
commit
27a91f9a0e
2 changed files with 139 additions and 0 deletions
90
dangerzone/updater/attestations.py
Normal file
90
dangerzone/updater/attestations.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import subprocess
|
||||
from tempfile import NamedTemporaryFile
|
||||
|
||||
from . import cosign
|
||||
|
||||
# NOTE: You can grab the SLSA attestation for an image/tag pair with the following
|
||||
# commands:
|
||||
#
|
||||
# IMAGE=ghcr.io/apyrgio/dangerzone/dangerzone
|
||||
# TAG=20250129-0.8.0-149-gbf2f5ac
|
||||
# DIGEST=$(crane digest ${IMAGE?}:${TAG?})
|
||||
# ATT_MANIFEST=${IMAGE?}:${DIGEST/:/-}.att
|
||||
# ATT_BLOB=${IMAGE?}@$(crane manifest ${ATT_MANIFEST?} | jq -r '.layers[0].digest')
|
||||
# crane blob ${ATT_BLOB?} | jq -r '.payload' | base64 -d | jq
|
||||
CUE_POLICY = r"""
|
||||
// The predicateType field must match this string
|
||||
predicateType: "https://slsa.dev/provenance/v0.2"
|
||||
|
||||
predicate: {{
|
||||
// This condition verifies that the builder is the builder we
|
||||
// expect and trust. The following condition can be used
|
||||
// unmodified. It verifies that the builder is the container
|
||||
// workflow.
|
||||
builder: {{
|
||||
id: =~"^https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v[0-9]+.[0-9]+.[0-9]+$"
|
||||
}}
|
||||
invocation: {{
|
||||
configSource: {{
|
||||
// This condition verifies the entrypoint of the workflow.
|
||||
// Replace with the relative path to your workflow in your
|
||||
// repository.
|
||||
entryPoint: "{workflow}"
|
||||
|
||||
// This condition verifies that the image was generated from
|
||||
// the source repository we expect. Replace this with your
|
||||
// repository.
|
||||
uri: =~"^git\\+https://github.com/{repository}@refs/heads/{branch}"
|
||||
// Add a condition to check for a specific commit hash
|
||||
digest: {{
|
||||
sha1: "{commit}"
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
}}
|
||||
"""
|
||||
|
||||
|
||||
def verify(
|
||||
image_name: str,
|
||||
branch: str,
|
||||
commit: str,
|
||||
repository: str,
|
||||
workflow: str,
|
||||
) -> bool:
|
||||
"""
|
||||
Look up the image attestation to see if the image has been built
|
||||
on Github runners, and from a given repository.
|
||||
"""
|
||||
cosign.ensure_installed()
|
||||
policy = CUE_POLICY.format(
|
||||
repository=repository, workflow=workflow, commit=commit, branch=branch
|
||||
)
|
||||
|
||||
# Put the value in files and verify with cosign
|
||||
with (
|
||||
NamedTemporaryFile(mode="w", suffix=".cue") as policy_f,
|
||||
):
|
||||
policy_f.write(policy)
|
||||
policy_f.flush()
|
||||
|
||||
# Call cosign with the temporary file paths
|
||||
cmd = [
|
||||
"cosign",
|
||||
"verify-attestation",
|
||||
"--type",
|
||||
"slsaprovenance",
|
||||
"--policy",
|
||||
policy_f.name,
|
||||
"--certificate-oidc-issuer",
|
||||
"https://token.actions.githubusercontent.com",
|
||||
"--certificate-identity-regexp",
|
||||
"^https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v[0-9]+.[0-9]+.[0-9]+$",
|
||||
image_name,
|
||||
]
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True)
|
||||
if result.returncode != 0:
|
||||
error = result.stderr.decode()
|
||||
raise Exception(f"Attestation cannot be verified. {error}")
|
||||
return True
|
|
@ -103,5 +103,54 @@ def get_manifest(image: str) -> None:
|
|||
click.echo(registry.get_manifest(image).content)
|
||||
|
||||
|
||||
@main.command()
|
||||
@click.argument("image_name")
|
||||
# XXX: Do we really want to check against this?
|
||||
@click.option(
|
||||
"--branch",
|
||||
default=DEFAULT_BRANCH,
|
||||
help="The Git branch that the image was built from",
|
||||
)
|
||||
@click.option(
|
||||
"--commit",
|
||||
required=True,
|
||||
help="The Git commit the image was built from",
|
||||
)
|
||||
@click.option(
|
||||
"--repository",
|
||||
default=DEFAULT_REPOSITORY,
|
||||
help="The github repository to check the attestation for",
|
||||
)
|
||||
@click.option(
|
||||
"--workflow",
|
||||
default=".github/workflows/release-container-image.yml",
|
||||
help="The path of the GitHub actions workflow this image was created from",
|
||||
)
|
||||
def attest_provenance(
|
||||
image_name: str,
|
||||
branch: str,
|
||||
commit: str,
|
||||
repository: str,
|
||||
workflow: str,
|
||||
) -> None:
|
||||
"""
|
||||
Look up the image attestation to see if the image has been built
|
||||
on Github runners, and from a given repository.
|
||||
"""
|
||||
# TODO: Parse image and make sure it has a tag. Might even check for a digest.
|
||||
# parsed = registry.parse_image_location(image)
|
||||
|
||||
verified = attestations.verify(image_name, branch, commit, repository, workflow)
|
||||
if verified:
|
||||
click.echo(
|
||||
f"🎉 Successfully verified image '{image_name}' and its associated claims:"
|
||||
)
|
||||
click.echo(f"- ✅ SLSA Level 3 provenance")
|
||||
click.echo(f"- ✅ GitHub repo: {repository}")
|
||||
click.echo(f"- ✅ GitHub actions workflow: {workflow}")
|
||||
click.echo(f"- ✅ Git branch: {branch}")
|
||||
click.echo(f"- ✅ Git commit: {commit}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
Loading…
Reference in a new issue