diff --git a/pkg/cmd/attestation/artifact/artifact.go b/pkg/cmd/attestation/artifact/artifact.go index 53f8d8aad..9d8125450 100644 --- a/pkg/cmd/attestation/artifact/artifact.go +++ b/pkg/cmd/attestation/artifact/artifact.go @@ -54,9 +54,8 @@ func normalizeReference(reference string, pathSeparator rune) (normalized string return filepath.Clean(reference), fileArtifactType, nil } -func NewDigestedArtifactForRelease(URL string, digest string, digestAlg string) (artifact *DigestedArtifact) { +func NewDigestedArtifactForRelease(digest string, digestAlg string) (artifact *DigestedArtifact) { return &DigestedArtifact{ - URL: URL, digest: digest, digestAlg: digestAlg, } diff --git a/pkg/cmd/attestation/verification/sigstore.go b/pkg/cmd/attestation/verification/sigstore.go index 14c8875d9..190ea5c0f 100644 --- a/pkg/cmd/attestation/verification/sigstore.go +++ b/pkg/cmd/attestation/verification/sigstore.go @@ -239,9 +239,6 @@ func (v *LiveSigstoreVerifier) verify(attestation *api.Attestation, policy verif result, err := verifier.Verify(attestation.Bundle, policy) // if verification fails, create the error and exit verification early if err != nil { - v.Logger.VerbosePrint(v.Logger.ColorScheme.Redf( - "Error is \"%s\"\n", err.Error(), - )) v.Logger.VerbosePrint(v.Logger.ColorScheme.Redf( "Failed to verify against issuer \"%s\" \n\n", issuer, )) diff --git a/pkg/cmd/release/attestation/attestation.go b/pkg/cmd/release/attestation/attestation.go index 70760f8c6..08e1398b8 100644 --- a/pkg/cmd/release/attestation/attestation.go +++ b/pkg/cmd/release/attestation/attestation.go @@ -9,7 +9,6 @@ import ( "github.com/cli/cli/v2/pkg/cmd/attestation/artifact" "github.com/cli/cli/v2/pkg/cmd/attestation/verification" - att_io "github.com/cli/cli/v2/pkg/cmd/attestation/io" v1 "github.com/in-toto/attestation/go/v1" "google.golang.org/protobuf/encoding/protojson" ) @@ -61,30 +60,25 @@ func VerifyAttestations(art artifact.DigestedArtifact, att []*api.Attestation, s return sigstoreVerified, "", nil } -func FilterAttestationsByPURL(attestations []*api.Attestation, repo, tagName string, logger *att_io.Handler) []*api.Attestation { +func FilterAttestationsByTag(attestations []*api.Attestation, tagName string) ([]*api.Attestation, error) { var filtered []*api.Attestation - expectedPURL := "pkg:github/" + repo + "@" + tagName for _, att := range attestations { statement := att.Bundle.Bundle.GetDsseEnvelope().Payload var statementData v1.Statement err := protojson.Unmarshal([]byte(statement), &statementData) if err != nil { - logger.Println(logger.ColorScheme.Red("✗ Failed to unmarshal statement")) - continue + return nil, fmt.Errorf("failed to unmarshal statement: %w", err) } - purlValue := statementData.Predicate.GetFields()["purl"] - var purl string - if purlValue != nil { - purl = purlValue.GetStringValue() - } - if purl == expectedPURL { + tagValue := statementData.Predicate.GetFields()["tag"].GetStringValue() + + if tagValue == tagName { filtered = append(filtered, att) } } - return filtered + return filtered, nil } -func FilterAttestationsByFileDigest(attestations []*api.Attestation, repo, tagName, fileDigest string, logger *att_io.Handler) []*api.Attestation { +func FilterAttestationsByFileDigest(attestations []*api.Attestation, repo, tagName, fileDigest string) ([]*api.Attestation, error) { var filtered []*api.Attestation for _, att := range attestations { statement := att.Bundle.Bundle.GetDsseEnvelope().Payload @@ -92,8 +86,7 @@ func FilterAttestationsByFileDigest(attestations []*api.Attestation, repo, tagNa err := protojson.Unmarshal([]byte(statement), &statementData) if err != nil { - logger.Println(logger.ColorScheme.Red("✗ Failed to unmarshal statement")) - continue + return nil, fmt.Errorf("failed to unmarshal statement: %w", err) } subjects := statementData.Subject for _, subject := range subjects { @@ -107,5 +100,5 @@ func FilterAttestationsByFileDigest(attestations []*api.Attestation, repo, tagNa } } - return filtered + return filtered, nil } diff --git a/pkg/cmd/release/verify-asset/verify-asset.go b/pkg/cmd/release/verify-asset/verify-asset.go index 8df2f2a11..666ad3f45 100644 --- a/pkg/cmd/release/verify-asset/verify-asset.go +++ b/pkg/cmd/release/verify-asset/verify-asset.go @@ -19,91 +19,76 @@ import ( "github.com/spf13/cobra" ) -func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.VerifyAssetOptions) error) *cobra.Command { - opts := &attestation.VerifyAssetOptions{ - IO: f.IOStreams, - HttpClient: f.HttpClient, - } +func NewCmdVerifyAsset(f *cmdutil.Factory, runF func(*attestation.AttestOptions) error) *cobra.Command { + opts := &attestation.AttestOptions{} cmd := &cobra.Command{ Use: "verify-asset ", Short: "Verify that a given asset originated from a specific GitHub Release.", Args: cobra.ExactArgs(2), PreRunE: func(cmd *cobra.Command, args []string) error { - return nil - }, - RunE: func(cmd *cobra.Command, args []string) error { - // support `-R, --repo` override - opts.BaseRepo = f.BaseRepo + opts.TagName = args[0] + opts.FilePath = args[1] - if len(args) > 0 { - opts.TagName = args[0] - } - if len(args) > 1 { - opts.FilePath = args[1] - } - - if runF != nil { - return runF(opts) - } - - httpClient, err := opts.HttpClient() + httpClient, err := f.HttpClient() if err != nil { return err } - - baseRepo, err := opts.BaseRepo() + baseRepo, err := f.BaseRepo() if err != nil { return err } - - logger := att_io.NewHandler(opts.IO) + logger := att_io.NewHandler(f.IOStreams) hostname, _ := ghauth.DefaultHost() - option := attestation.AttestOptions{ + + *opts = attestation.AttestOptions{ + TagName: opts.TagName, + FilePath: opts.FilePath, Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(), APIClient: api.NewLiveClient(httpClient, hostname, logger), Limit: 10, Owner: baseRepo.RepoOwner(), PredicateType: "https://in-toto.io/attestation/release/v0.1", Logger: logger, + HttpClient: httpClient, + BaseRepo: baseRepo, + IO: f.IOStreams, + Exporter: opts.Exporter, + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error { + if runF != nil { + return runF(opts) } - option.HttpClient = httpClient - option.BaseRepo = baseRepo - option.IO = opts.IO - option.TagName = opts.TagName - option.Exporter = opts.Exporter - option.FilePath = opts.FilePath - - td, err := option.APIClient.GetTrustDomain() + td, err := opts.APIClient.GetTrustDomain() if err != nil { - logger.Println(logger.ColorScheme.Red("✗ Failed to get trust domain")) + opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to get trust domain")) return err } - ec, err := attestation.NewEnforcementCriteria(&option, logger) + ec, err := attestation.NewEnforcementCriteria(opts, opts.Logger) if err != nil { - logger.Println(logger.ColorScheme.Red("✗ Failed to build policy information")) + opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to build policy information")) return err } config := verification.SigstoreConfig{ - TrustedRoot: "", - Logger: logger, + Logger: opts.Logger, NoPublicGood: true, TrustDomain: td, } sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(config) if err != nil { - logger.Println(logger.ColorScheme.Red("✗ Failed to create Sigstore verifier")) + opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to create Sigstore verifier")) return err } - option.SigstoreVerifier = sigstoreVerifier - option.EC = ec + opts.SigstoreVerifier = sigstoreVerifier + opts.EC = ec - // output ec - return verifyAssetRun(&option) + return verifyAssetRun(opts) }, } @@ -124,50 +109,56 @@ func verifyAssetRun(opts *attestation.AttestOptions) error { opts.Logger.Printf("Loaded digest %s for %s\n", fileDigest.DigestWithAlg(), fileName) - sha, err := shared.FetchRefSHA(ctx, opts.HttpClient, opts.BaseRepo, opts.TagName) + ref, err := shared.FetchRefSHA(ctx, opts.HttpClient, opts.BaseRepo, opts.TagName) if err != nil { return err } - releaseArtifact := artifact.NewDigestedArtifactForRelease(opts.TagName, sha, "sha1") - opts.Logger.Printf("Resolved %s to %s\n", opts.TagName, releaseArtifact.DigestWithAlg()) + releaseRefDigest := artifact.NewDigestedArtifactForRelease(ref, "sha1") + opts.Logger.Printf("Resolved %s to %s\n", opts.TagName, releaseRefDigest.DigestWithAlg()) // Attestation fetching - attestations, logMsg, err := attestation.GetAttestations(opts, releaseArtifact.DigestWithAlg()) + attestations, logMsg, err := attestation.GetAttestations(opts, releaseRefDigest.DigestWithAlg()) if err != nil { if errors.Is(err, api.ErrNoAttestationsFound) { - opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found for subject %s\n"), releaseArtifact.DigestWithAlg()) + opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found for subject %s\n"), releaseRefDigest.DigestWithAlg()) return err } opts.Logger.Println(opts.Logger.ColorScheme.Red(logMsg)) return err } - // Filter attestations by predicate PURL - filteredAttestations := attestation.FilterAttestationsByPURL(attestations, opts.Repo, opts.TagName, opts.Logger) - filteredAttestations = attestation.FilterAttestationsByFileDigest(filteredAttestations, opts.Repo, opts.TagName, fileDigest.Digest(), opts.Logger) + // Filter attestations by tag + filteredAttestations, err := attestation.FilterAttestationsByTag(attestations, opts.TagName) + if err != nil { + opts.Logger.Println(opts.Logger.ColorScheme.Red(err.Error())) + return err + } + + filteredAttestations, err = attestation.FilterAttestationsByFileDigest(filteredAttestations, opts.Repo, opts.TagName, fileDigest.Digest()) + if err != nil { + opts.Logger.Println(opts.Logger.ColorScheme.Red(err.Error())) + return err + } + + if len(filteredAttestations) == 0 { + opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found for %s\n"), fileName) + return nil + } opts.Logger.Printf("Loaded %s from GitHub API\n", text.Pluralize(len(filteredAttestations), "attestation")) // Verify attestations - verified, errMsg, err := attestation.VerifyAttestations(*releaseArtifact, filteredAttestations, opts.SigstoreVerifier, opts.EC) + verified, errMsg, err := attestation.VerifyAttestations(*releaseRefDigest, filteredAttestations, opts.SigstoreVerifier, opts.EC) if err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Red(errMsg)) - - opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed")) - - // Release v1.0.0 does not contain bin-linux.tgz (sha256:0c2524c2b002fda89f8b766c7d3dd8e6ac1de183556728a83182c6137f19643d) - opts.Logger.Printf(opts.Logger.ColorScheme.Red("Release %s does not contain %s (%s)\n"), opts.TagName, opts.FilePath, fileDigest.DigestWithAlg()) return err } opts.Logger.Printf("The following %s matched the policy criteria\n\n", text.Pluralize(len(verified), "attestation")) opts.Logger.Println(opts.Logger.ColorScheme.Green("✓ Verification succeeded!\n")) - - opts.Logger.Printf("Attestation found matching release %s (%s)\n", opts.TagName, releaseArtifact.DigestWithAlg()) - - // bin-linux.tgz is present in release v1.0.0 + opts.Logger.Printf("Attestation found matching release %s (%s)\n", opts.TagName, releaseRefDigest.DigestWithAlg()) opts.Logger.Printf("%s is present in release %s\n", fileName, opts.TagName) return nil diff --git a/pkg/cmd/release/verify/verify.go b/pkg/cmd/release/verify/verify.go index 4232cfca1..149125dc6 100644 --- a/pkg/cmd/release/verify/verify.go +++ b/pkg/cmd/release/verify/verify.go @@ -28,10 +28,12 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro Short: "Verify the attestation for a GitHub Release.", Args: cobra.ExactArgs(1), PreRunE: func(cmd *cobra.Command, args []string) error { - if len(args) > 0 { - opts.TagName = args[0] + if len(args) < 1 { + return cmdutil.FlagErrorf("You must specify a tag") } + opts.TagName = args[0] + httpClient, err := f.HttpClient() if err != nil { return err @@ -41,29 +43,26 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro if err != nil { return err } - logger := att_io.NewHandler(f.IOStreams) hostname, _ := ghauth.DefaultHost() - opts.Repo = baseRepo.RepoOwner() + "/" + baseRepo.RepoName() - opts.APIClient = api.NewLiveClient(httpClient, hostname, logger) - opts.Limit = 10 - opts.Owner = baseRepo.RepoOwner() - opts.PredicateType = "https://in-toto.io/attestation/release/v0.1" - opts.Logger = logger - - opts.HttpClient = httpClient - opts.BaseRepo = baseRepo - - opts.HttpClient = httpClient - + *opts = attestation.AttestOptions{ + TagName: opts.TagName, + Repo: baseRepo.RepoOwner() + "/" + baseRepo.RepoName(), + APIClient: api.NewLiveClient(httpClient, hostname, logger), + Limit: 10, + Owner: baseRepo.RepoOwner(), + PredicateType: "https://in-toto.io/attestation/release/v0.1", + Logger: logger, + HttpClient: httpClient, + BaseRepo: baseRepo, + } return nil }, RunE: func(cmd *cobra.Command, args []string) error { if runF != nil { return runF(opts) } - // td, err := opts.APIClient.GetTrustDomain() if err != nil { @@ -78,11 +77,11 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro } config := verification.SigstoreConfig{ - TrustedRoot: "", Logger: opts.Logger, NoPublicGood: true, TrustDomain: td, } + sigstoreVerifier, err := verification.NewLiveSigstoreVerifier(config) if err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Failed to create Sigstore verifier")) @@ -92,7 +91,6 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro opts.SigstoreVerifier = sigstoreVerifier opts.EC = ec - // output ec return verifyRun(opts) }, } @@ -105,38 +103,39 @@ func NewCmdVerify(f *cmdutil.Factory, runF func(*attestation.AttestOptions) erro func verifyRun(opts *attestation.AttestOptions) error { ctx := context.Background() - sha, err := shared.FetchRefSHA(ctx, opts.HttpClient, opts.BaseRepo, opts.TagName) + ref, err := shared.FetchRefSHA(ctx, opts.HttpClient, opts.BaseRepo, opts.TagName) if err != nil { return err } - artifact := artifact.NewDigestedArtifactForRelease(opts.TagName, sha, "sha1") - opts.Logger.Printf("Resolved %s to %s\n", opts.TagName, artifact.DigestWithAlg()) + releaseRefDigest := artifact.NewDigestedArtifactForRelease(ref, "sha1") + opts.Logger.Printf("Resolved %s to %s\n", opts.TagName, releaseRefDigest.DigestWithAlg()) // Attestation fetching - attestations, logMsg, err := attestation.GetAttestations(opts, artifact.DigestWithAlg()) + attestations, logMsg, err := attestation.GetAttestations(opts, releaseRefDigest.DigestWithAlg()) if err != nil { if errors.Is(err, api.ErrNoAttestationsFound) { - opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found for subject %s\n"), artifact.DigestWithAlg()) + opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ No attestations found for subject %s\n"), releaseRefDigest.DigestWithAlg()) return err } opts.Logger.Println(opts.Logger.ColorScheme.Red(logMsg)) return err } - // Filter attestations by predicate PURL - filteredAttestations := attestation.FilterAttestationsByPURL(attestations, opts.Repo, opts.TagName, opts.Logger) + // Filter attestations by predicate tag + filteredAttestations, err := attestation.FilterAttestationsByTag(attestations, opts.TagName) + if err != nil { + opts.Logger.Println(opts.Logger.ColorScheme.Red(err.Error())) + return err + } opts.Logger.Printf("Loaded %s from GitHub API\n", text.Pluralize(len(filteredAttestations), "attestation")) // Verify attestations - verified, errMsg, err := attestation.VerifyAttestations(*artifact, filteredAttestations, opts.SigstoreVerifier, opts.EC) + verified, errMsg, err := attestation.VerifyAttestations(*releaseRefDigest, filteredAttestations, opts.SigstoreVerifier, opts.EC) if err != nil { opts.Logger.Println(opts.Logger.ColorScheme.Red(errMsg)) - - opts.Logger.Println(opts.Logger.ColorScheme.Red("✗ Verification failed")) - opts.Logger.Printf(opts.Logger.ColorScheme.Red("✗ Failed to find an attestation for release %s in %s\n"), opts.TagName, opts.Repo) return err } @@ -144,7 +143,7 @@ func verifyRun(opts *attestation.AttestOptions) error { opts.Logger.Printf("The following %s matched the policy criteria\n\n", text.Pluralize(len(verified), "attestation")) opts.Logger.Println(opts.Logger.ColorScheme.Green("✓ Verification succeeded!\n")) - opts.Logger.Printf("Attestation found matching release %s (%s)\n", opts.TagName, artifact.Digest()) + opts.Logger.Printf("Attestation found matching release %s (%s)\n", opts.TagName, releaseRefDigest.Digest()) printVerifiedSubjects(verified, opts.Logger) return nil @@ -164,14 +163,10 @@ func printVerifiedSubjects(verified []*verification.AttestationProcessingResult, digest := s.Digest if name != "" { - // digest is map[string]string and i want to be key:value - // so i need to iterate over the map and print key:value digestStr := "" for key, value := range digest { digestStr += key + ":" + value } - // output should like this - // bin-linux.tgz sha256:0c2524c2b002fda89f8b766c7d3dd8e6ac1de183556728a83182c6137f19643d logger.Println(" " + name + " " + digestStr) } }