From 313faf9cd08d8aa47cb2b8118410852393b7597e Mon Sep 17 00:00:00 2001 From: Meredith Lancaster Date: Thu, 30 Jan 2025 07:43:13 -0700 Subject: [PATCH] add signer and source ref, commit options Signed-off-by: Meredith Lancaster --- pkg/cmd/attestation/verify/policy.go | 44 +++++++---------- pkg/cmd/attestation/verify/policy_test.go | 57 +++++++++++++++++++++++ pkg/cmd/attestation/verify/verify.go | 10 ++-- 3 files changed, 81 insertions(+), 30 deletions(-) diff --git a/pkg/cmd/attestation/verify/policy.go b/pkg/cmd/attestation/verify/policy.go index bb6ddb278..759867509 100644 --- a/pkg/cmd/attestation/verify/policy.go +++ b/pkg/cmd/attestation/verify/policy.go @@ -13,8 +13,7 @@ import ( "github.com/cli/cli/v2/pkg/cmd/attestation/verification" ) -const hostRegex = `^[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+.*$` -const workflowURIRegex = `^https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+.*\/.github\/workflows\/[a-zA-Z0-9-]+.(yml|yaml)$` +const workflowURIRegex = `^https:\/\/[a-zA-Z0-9-]+\.[a-zA-Z0-9-]+.*\/.github\/workflows\/.*\/[a-zA-Z0-9-]+.(yml|yaml)$` func expandToGitHubURL(tenant, ownerOrRepo string) string { if tenant == "" { @@ -57,11 +56,17 @@ func newEnforcementCriteria(opts *Options) (verification.EnforcementCriteria, er signedRepoRegex := expandToGitHubURLRegex(opts.Tenant, opts.SignerRepo) c.SANRegex = signedRepoRegex } else if opts.SignerWorkflow != "" { - validatedWorkflowRegex, err := validateSignerWorkflow(opts.Hostname, opts.SignerWorkflow) + validatedWorkflow, err := validateSignerWorkflow(opts.Hostname, opts.SignerWorkflow) if err != nil { return verification.EnforcementCriteria{}, err } - c.SANRegex = validatedWorkflowRegex + + workflowRegex := fmt.Sprintf("^%s", validatedWorkflow) + c.SANRegex = workflowRegex + + if opts.SignerRef != "" { + c.Certificate.BuildSignerURI = fmt.Sprintf("%s@%s", validatedWorkflow, opts.SignerRef) + } } else if opts.Repo != "" { // if the user has not provided the SAN, SANRegex, SignerRepo, or SignerWorkflow options // then we default to the repo option @@ -99,26 +104,11 @@ func newEnforcementCriteria(opts *Options) (verification.EnforcementCriteria, er c.Certificate.Issuer = opts.OIDCIssuer } - if opts.SignerDigest != "" { - c.Certificate.BuildSignerDigest = opts.SignerDigest - } - - if opts.SignerRef != "" { - // need to build the full URI value - uri, err := getFullWorkflowURI(c.SANRegex) - if err != nil { - return verification.EnforcementCriteria{}, err - } - c.Certificate.BuildSignerURI = uri - } - - if opts.SourceDigest != "" { - c.Certificate.SourceRepositoryDigest = opts.SourceDigest - } - - if opts.SourceRef != "" { - c.Certificate.SourceRepositoryRef = opts.SourceRef - } + // set the SourceRepositoryDigest, SourceRepositoryRef, and BuildSignerDigest + // extensions if the options are provided + c.Certificate.BuildSignerDigest = opts.SignerDigest + c.Certificate.SourceRepositoryDigest = opts.SourceDigest + c.Certificate.SourceRepositoryRef = opts.SourceRef return c, nil } @@ -179,13 +169,13 @@ func buildSigstoreVerifyPolicy(c verification.EnforcementCriteria, a artifact.Di func validateSignerWorkflow(hostname, signerWorkflow string) (string, error) { // we expect a provided workflow argument be in the format [HOST/]///path/to/workflow.yml // if the provided workflow does not contain a host, set the host - match, err := regexp.MatchString(hostRegex, signerWorkflow) + match, err := regexp.MatchString(workflowURIRegex, signerWorkflow) if err != nil { return "", err } if match { - return fmt.Sprintf("^https://%s", signerWorkflow), nil + return fmt.Sprintf("https://%s", signerWorkflow), nil } // if the provided workflow did not match the expect format @@ -194,5 +184,5 @@ func validateSignerWorkflow(hostname, signerWorkflow string) (string, error) { return "", errors.New("unknown host") } - return fmt.Sprintf("^https://%s/%s", hostname, signerWorkflow), nil + return fmt.Sprintf("https://%s/%s", hostname, signerWorkflow), nil } diff --git a/pkg/cmd/attestation/verify/policy_test.go b/pkg/cmd/attestation/verify/policy_test.go index ecaf74ec3..a05e3377d 100644 --- a/pkg/cmd/attestation/verify/policy_test.go +++ b/pkg/cmd/attestation/verify/policy_test.go @@ -217,6 +217,63 @@ func TestNewEnforcementCriteria(t *testing.T) { require.NoError(t, err) require.Equal(t, "https://foo.com", c.Certificate.Issuer) }) + + t.Run("sets Certificate.BuildSignerURI using SignerWorkflow and SignerRef", func(t *testing.T) { + opts := &Options{ + ArtifactPath: artifactPath, + Owner: "wrong", + Repo: "wrong/value", + SignerWorkflow: "foo/bar/.github/workflows/attest.yml", + SignerRef: "refs/heads/main", + Hostname: "github.com", + } + + c, err := newEnforcementCriteria(opts) + require.NoError(t, err) + require.Equal(t, "https://github.com/foo/bar/.github/workflows/attest.yml@refs/heads/main", c.Certificate.BuildSignerURI) + }) + + t.Run("sets Certificate.BuildSignerDigest using opts.SignerDigest", func(t *testing.T) { + opts := &Options{ + ArtifactPath: artifactPath, + Owner: "wrong", + Repo: "wrong/value", + SignerDigest: "foo", + Hostname: "github.com", + } + + c, err := newEnforcementCriteria(opts) + require.NoError(t, err) + require.Equal(t, "foo", c.Certificate.BuildSignerDigest) + }) + + t.Run("sets Certificate.SourceRepositoryDigest using opts.SourceDigest", func(t *testing.T) { + opts := &Options{ + ArtifactPath: artifactPath, + Owner: "wrong", + Repo: "wrong/value", + SourceDigest: "foo", + Hostname: "github.com", + } + + c, err := newEnforcementCriteria(opts) + require.NoError(t, err) + require.Equal(t, "foo", c.Certificate.SourceRepositoryDigest) + }) + + t.Run("sets Certificate.SourceRepositoryRef using opts.SourceRef", func(t *testing.T) { + opts := &Options{ + ArtifactPath: artifactPath, + Owner: "wrong", + Repo: "wrong/value", + SourceRef: "refs/heads/main", + Hostname: "github.com", + } + + c, err := newEnforcementCriteria(opts) + require.NoError(t, err) + require.Equal(t, "refs/heads/main", c.Certificate.SourceRepositoryRef) + }) } func TestValidateSignerWorkflow(t *testing.T) { diff --git a/pkg/cmd/attestation/verify/verify.go b/pkg/cmd/attestation/verify/verify.go index ea7502f00..c1ec63bbb 100644 --- a/pkg/cmd/attestation/verify/verify.go +++ b/pkg/cmd/attestation/verify/verify.go @@ -187,14 +187,18 @@ func NewVerifyCmd(f *cmdutil.Factory, runF func(*Options) error) *cobra.Command verifyCmd.Flags().IntVarP(&opts.Limit, "limit", "L", api.DefaultLimit, "Maximum number of attestations to fetch") cmdutil.AddFormatFlags(verifyCmd, &opts.exporter) // policy enforcement flags - verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners") verifyCmd.Flags().StringVarP(&opts.SAN, "cert-identity", "", "", "Enforce that the certificate's subject alternative name matches the provided value exactly") verifyCmd.Flags().StringVarP(&opts.SANRegex, "cert-identity-regex", "i", "", "Enforce that the certificate's subject alternative name matches the provided regex") - verifyCmd.Flags().StringVarP(&opts.SignerRepo, "signer-repo", "", "", "Repository of reusable workflow that signed attestation in the format /") - verifyCmd.Flags().StringVarP(&opts.SignerWorkflow, "signer-workflow", "", "", "Workflow that signed attestation in the format [host/]////") verifyCmd.MarkFlagsMutuallyExclusive("cert-identity", "cert-identity-regex", "signer-repo", "signer-workflow") verifyCmd.Flags().StringVarP(&opts.OIDCIssuer, "cert-oidc-issuer", "", verification.GitHubOIDCIssuer, "Issuer of the OIDC token") + verifyCmd.Flags().BoolVarP(&opts.DenySelfHostedRunner, "deny-self-hosted-runners", "", false, "Fail verification for attestations generated on self-hosted runners") verifyCmd.Flags().StringVarP(&opts.Hostname, "hostname", "", "", "Configure host to use") + verifyCmd.Flags().StringVarP(&opts.SignerDigest, "signer-digest", "", "", "Digest associated with the signer workflow") + verifyCmd.Flags().StringVarP(&opts.SignerRepo, "signer-repo", "", "", "Repository of reusable workflow that signed attestation in the format /") + verifyCmd.Flags().StringVarP(&opts.SignerWorkflow, "signer-workflow", "", "", "Workflow that signed attestation in the format [host/]////") + verifyCmd.Flags().StringVarP(&opts.SignerRef, "signer-ref", "", "", "Ref associated with the signer workflow. signer-workflow must be provided to use this") + verifyCmd.Flags().StringVarP(&opts.SourceRef, "source-ref", "", "", "Ref associated with the source workflow") + verifyCmd.Flags().StringVarP(&opts.SourceDigest, "source-digest", "", "", "Digest associated with the source workflow") return verifyCmd }