Skip to content

Commit

Permalink
Fix attestation verify source repository check bug (#9053)
Browse files Browse the repository at this point in the history
* add build source repo URI extension when repo is provided, add integration tests for this change

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add initial docs on specifying cert identity

Signed-off-by: Meredith Lancaster <malancas@github.com>

* wording

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add reusable workflow example

Signed-off-by: Meredith Lancaster <malancas@github.com>

* add more test cases

Signed-off-by: Meredith Lancaster <malancas@github.com>

* tweak to verify docs

---------

Signed-off-by: Meredith Lancaster <malancas@github.com>
Co-authored-by: Phill MV <phillmv@github.com>
  • Loading branch information
malancas and phillmv committed May 8, 2024
1 parent 08e8ee6 commit c9e8fd6
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 5 deletions.
21 changes: 16 additions & 5 deletions pkg/cmd/attestation/verify/policy.go
Expand Up @@ -28,17 +28,28 @@ func buildSANMatcher(san, sanRegex string) (verify.SubjectAlternativeNameMatcher
return sanMatcher, nil
}

func buildCertExtensions(opts *Options, runnerEnv string) certificate.Extensions {
extensions := certificate.Extensions{
Issuer: opts.OIDCIssuer,
SourceRepositoryOwnerURI: fmt.Sprintf("https://github.com/%s", opts.Owner),
RunnerEnvironment: runnerEnv,
}

// if opts.Repo is set, set the SourceRepositoryURI field before returning the extensions
if opts.Repo != "" {
extensions.SourceRepositoryURI = fmt.Sprintf("https://github.com/%s", opts.Repo)
}
return extensions
}

func buildCertificateIdentityOption(opts *Options, runnerEnv string) (verify.PolicyOption, error) {
sanMatcher, err := buildSANMatcher(opts.SAN, opts.SANRegex)
if err != nil {
return nil, err
}

extensions := certificate.Extensions{
Issuer: opts.OIDCIssuer,
SourceRepositoryOwnerURI: fmt.Sprintf("https://github.com/%s", opts.Owner),
RunnerEnvironment: runnerEnv,
}
extensions := buildCertExtensions(opts, runnerEnv)

certId, err := verify.NewCertificateIdentity(sanMatcher, extensions)
if err != nil {
return nil, err
Expand Down
10 changes: 10 additions & 0 deletions pkg/cmd/attestation/verify/verify.go
Expand Up @@ -54,6 +54,16 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command
To see the full results that are generated upon successful verification, i.e.
for use with a policy engine, provide the %[1]s--json-result%[1]s flag.
The attestation's certificate's Subject Alternative Name (SAN) identifies the entity
responsible for creating the attestation, which most of the time will be a GitHub
Actions workflow file located inside your repository. By default, this command uses
either the %[1]s--repo%[1]s or the %[1]s--owner%[1]s flag value to validate the SAN.
However, if you generate attestations with a reusable workflow then the SAN will
identify the reusable workflow – which may or may not be located inside your %[1]s--repo%[1]s
or %[1]s--owner%[1]s. In these situations, you can use the %[1]s--cert-identity%[1]s or
%[1]s--cert-identity-regex%[1]s flags to specify the reusable workflow's URI.
For more policy verification options, see the other available flags.
`, "`"),
Example: heredoc.Doc(`
Expand Down
82 changes: 82 additions & 0 deletions pkg/cmd/attestation/verify/verify_integration_test.go
@@ -0,0 +1,82 @@
//go:build integration

package verify

import (
"testing"

"github.com/cli/cli/v2/pkg/cmd/attestation/api"
"github.com/cli/cli/v2/pkg/cmd/attestation/artifact/oci"
"github.com/cli/cli/v2/pkg/cmd/attestation/io"
"github.com/cli/cli/v2/pkg/cmd/attestation/verification"
"github.com/cli/cli/v2/pkg/cmd/factory"
"github.com/stretchr/testify/require"
)

func TestVerifyIntegration(t *testing.T) {
logger := io.NewTestHandler()

sigstoreConfig := verification.SigstoreConfig{
Logger: logger,
}

cmdFactory := factory.New("test")

hc, err := cmdFactory.HttpClient()
if err != nil {
t.Fatal(err)
}

publicGoodOpts := Options{
APIClient: api.NewLiveClient(hc, logger),
ArtifactPath: artifactPath,
BundlePath: bundlePath,
DigestAlgorithm: "sha512",
Logger: logger,
OCIClient: oci.NewLiveClient(),
OIDCIssuer: GitHubOIDCIssuer,
Owner: "sigstore",
SANRegex: "^https://github.com/sigstore/",
SigstoreVerifier: verification.NewLiveSigstoreVerifier(sigstoreConfig),
}

t.Run("with valid owner", func(t *testing.T) {
err := runVerify(&publicGoodOpts)
require.NoError(t, err)
})

t.Run("with valid repo", func(t *testing.T) {
opts := publicGoodOpts
opts.Repo = "sigstore/sigstore-js"

err := runVerify(&opts)
require.NoError(t, err)
})

t.Run("with valid owner and invalid repo", func(t *testing.T) {
opts := publicGoodOpts
opts.Repo = "sigstore/fakerepo"

err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "verifying with issuer \"sigstore.dev\": failed to verify certificate identity: no matching certificate identity found")
})

t.Run("with invalid owner", func(t *testing.T) {
opts := publicGoodOpts
opts.Owner = "fakeowner"

err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "verifying with issuer \"sigstore.dev\": failed to verify certificate identity: no matching certificate identity found")
})

t.Run("with invalid owner and invalid repo", func(t *testing.T) {
opts := publicGoodOpts
opts.Repo = "fakeowner/fakerepo"

err := runVerify(&opts)
require.Error(t, err)
require.ErrorContains(t, err, "verifying with issuer \"sigstore.dev\": failed to verify certificate identity: no matching certificate identity found")
})
}

0 comments on commit c9e8fd6

Please sign in to comment.