cli/cli

gh attestation verify fails with a reusable workflow.

thepwagner opened this issue · 6 comments

Describe the bug

The attestation verify feature matches the certificate SAN against the repository name. When the workflow uses a workflow_call/reusable worfklow, the SAN is the workflow name, which is not necessarily the same as the repository name.

This causes verifying artifacts produced by the target repository to fail.

Steps to reproduce the behavior

Example artifacts: https://github.com/thepwagner/ghcr-reaper/releases/tag/v0.0.1 from this workflow https://github.com/thepwagner/ghcr-reaper/blob/v0.0.1/.github/workflows/release.yaml

Expected vs actual behavior / Logs

I expect the provided examples to work:

$ gh attestation verify ghcr-reaper_0.0.1_linux_amd64.deb --repo thepwagner/ghcr-reaper
Loaded digest sha256:f1250d410cb32b3a5203885791c571fde4d239e1b2cc7f44f0ff552502a02aaa for file://ghcr-reaper_0.0.1_linux_amd64.deb
Loaded 1 attestation from GitHub API
✗ Verification failed

Error: verifying with issuer "sigstore.dev": failed to verify certificate identity: no matching certificate identity found

I can workaround by passing the workflow reference as an additional argument, but that's not expected:

$ gh attestation verify ghcr-reaper_0.0.1_linux_amd64.deb --repo thepwagner/ghcr-reaper --cert-identity 'https://github.com/thepwagner-org/actions/.github/workflows/golang-release-attest.yaml@refs/heads/github-attestations'
Loaded digest sha256:f1250d410cb32b3a5203885791c571fde4d239e1b2cc7f44f0ff552502a02aaa for file://ghcr-reaper_0.0.1_linux_amd64.deb
Loaded 1 attestation from GitHub API
✓ Verification succeeded!

sha256:f1250d410cb32b3a5203885791c571fde4d239e1b2cc7f44f0ff552502a02aaa was attested by:
REPO                    PREDICATE_TYPE                  WORKFLOW
thepwagner-org/actions  https://slsa.dev/provenance/v1  .github/workflows/golang-release-attest.yaml@refs/heads/github-attestations

Related

Thanks for filing this issue ❤

Actually I think it works as expected, but there are a some improvements we can do here from our side, both on the documentation and the error messages.

The reason it fails now is because the artifact comes from one repo, but it's signed with a (reusable) workflow from another repo (which is also in another org, but it doesn't really matter). This is because the reusable workflow is where the build provenance attestation is created.

So even if we can assure that the artifact comes from the repo you specified, the identity of the signer is not known/trusted. That is why the extra parameter is needed. The regex version can also be used here:

gh attestation verify ghcr-reaper_0.0.1_linux_amd64.deb \
    --repo thepwagner/ghcr-reaper \
    --cert-identity-regex 'https://github.com/thepwagner-org/actions/.github/workflows/golang-release-attest.yaml.*'

If the repo the artifact originates from and the signer is identical, we trust the signer and so no extra parameter is needed.

This is because the reusable workflow is where the build provenance attestation is created.

Understood, a different error message will help.

FWIW, that model surprised me. My assumption was the --repo flag would verify the artifact was produced by a workflow execution in the given repository. I did not think it would care if that workflow used a definition entirely within the repository or delegated via reusable workflow.

Specifically I expected gh would be looking at the 1.3.6.1.4.1.57264.1.18 extension (in my case https://github.com/thepwagner/ghcr-reaper/.github/workflows/release.yaml@refs/tags/v0.0.1), not the SAN.
I expected 1.3.6.1.4.1.57264.1.9 to be the kind of thing I'd need a rego policy to match against: an extra guarantee (and a worthwhile one!), but not something that I need a workaround to use.

(Bias: everything I've written prefers verifying the extensions to the SAN, if there's a good reason to prefer the SAN I'd like to learn!)

Yes, there are some options to chose from (different extensions). One of the reason to not trust the value in the extension in your example is that the identity of the issuer (SAN) is not known. So even if the extension's value matches your, how can you trust it when the signer is unknown?

The reusable workflow may perform arbitrary actions prior or during the build.

So the answer is sort of yes to both. The extension's values are where the data should be sourced from, but only if we trust the signer. Makes sense?

oh, hey @thepwagner! This turned out to be a good test case. Thanks for kicking the tires.