diff --git a/README.md b/README.md index 1d654341a..aea423fd0 100644 --- a/README.md +++ b/README.md @@ -262,6 +262,28 @@ The only requirement is that the provenance file covers all artifacts passed as To verify a container image, you need to pass a container image name that is _immutable_ by providing its digest, in order to avoid [TOCTOU attacks](#toctou-attacks). +#### The verify-image command + +```bash +$ slsa-verifier verify-image --help +Verifies SLSA provenance for an image + +Usage: + slsa-verifier verify-image [flags] tarball + +Flags: + --build-workflow-input map[] [optional] a workflow input provided by a user at trigger time in the format 'key=value'. (Only for 'workflow_dispatch' events on GitHub Actions). (default map[]) + --builder-id string [optional] the unique builder ID who created the provenance + -h, --help help for verify-npm-package + --print-provenance [optional] print the verified provenance to stdout + --provenance-path string path to a provenance file + --provenance-repository string [optional] provenance repository when stored different from image repository. When set, overrides COSIGN_REPOSITORY environment variable + --source-branch string [optional] expected branch the binary was compiled from + --source-tag string [optional] expected tag the binary was compiled from + --source-uri string expected source repository that should have produced the binary, e.g. github.com/some/repo + --source-versioned-tag string [optional] expected version the binary was compiled from. Uses semantic version to match the tag +``` + First set the image name: ```shell diff --git a/cli/slsa-verifier/verify.go b/cli/slsa-verifier/verify.go index 7d5a95be5..97ce36e70 100644 --- a/cli/slsa-verifier/verify.go +++ b/cli/slsa-verifier/verify.go @@ -76,7 +76,7 @@ func verifyArtifactCmd() *cobra.Command { } func verifyImageCmd() *cobra.Command { - o := &verify.VerifyOptions{} + o := &verify.VerifyImageOptions{} cmd := &cobra.Command{ Use: "verify-image [flags] image", @@ -96,6 +96,9 @@ func verifyImageCmd() *cobra.Command { if cmd.Flags().Changed("provenance-path") { v.ProvenancePath = &o.ProvenancePath } + if cmd.Flags().Changed("provenance-repository") { + v.ProvenanceRepository = &o.ProvenanceRepository + } if cmd.Flags().Changed("source-branch") { v.SourceBranch = &o.SourceBranch } diff --git a/cli/slsa-verifier/verify/options.go b/cli/slsa-verifier/verify/options.go index c21e3fcc4..4e217701b 100644 --- a/cli/slsa-verifier/verify/options.go +++ b/cli/slsa-verifier/verify/options.go @@ -74,6 +74,48 @@ func (o *VerifyOptions) AddFlags(cmd *cobra.Command) { cmd.MarkFlagsMutuallyExclusive("source-versioned-tag", "source-tag") } +// VerifyImageOptions is the top-level options for the `verifyImage` command + +type VerifyImageOptions struct { + VerifyOptions + /* Other */ + ProvenanceRepository string +} + +var _ Interface = (*VerifyImageOptions)(nil) + +// AddFlags implements Interface. +func (o *VerifyImageOptions) AddFlags(cmd *cobra.Command) { + cmd.Flags().Var(&o.BuildWorkflowInputs, "build-workflow-input", + "[optional] a workflow input provided by a user at trigger time in the format 'key=value'. (Only for 'workflow_dispatch' events on GitHub Actions).") + + cmd.Flags().StringVar(&o.BuilderID, "builder-id", "", "[optional] the unique builder ID who created the provenance") + + /* Source options */ + cmd.Flags().StringVar(&o.SourceURI, "source-uri", "", + "expected source repository that should have produced the binary, e.g. github.com/some/repo") + + cmd.Flags().StringVar(&o.SourceBranch, "source-branch", "", "[optional] expected branch the binary was compiled from") + + cmd.Flags().StringVar(&o.SourceTag, "source-tag", "", "[optional] expected tag the binary was compiled from") + + cmd.Flags().StringVar(&o.SourceVersionTag, "source-versioned-tag", "", + "[optional] expected version the binary was compiled from. Uses semantic version to match the tag") + + /* Other options */ + cmd.Flags().StringVar(&o.ProvenancePath, "provenance-path", "", + "path to a provenance file") + + cmd.Flags().StringVar(&o.ProvenanceRepository, "provenance-repository", "", + "image repository for provenance with format: /. When set, overrides COSIGN_REPOSITORY environment variable") + + cmd.Flags().BoolVar(&o.PrintProvenance, "print-provenance", false, + "[optional] print the verified provenance to stdout") + + cmd.MarkFlagRequired("source-uri") + cmd.MarkFlagsMutuallyExclusive("source-versioned-tag", "source-tag") +} + // VerifyNpmOptions is the top-level options for the `verifyNpmPackage` command. type VerifyNpmOptions struct { VerifyOptions diff --git a/cli/slsa-verifier/verify/verify_image.go b/cli/slsa-verifier/verify/verify_image.go index 9bf8c50e3..8785edeb0 100644 --- a/cli/slsa-verifier/verify/verify_image.go +++ b/cli/slsa-verifier/verify/verify_image.go @@ -30,14 +30,15 @@ type ComputeDigestFn func(string) (string, error) // Note: nil branch, tag, version-tag and builder-id means we ignore them during verification. type VerifyImageCommand struct { // May be nil if supplied alongside in the registry - ProvenancePath *string - BuilderID *string - SourceURI string - SourceBranch *string - SourceTag *string - SourceVersionTag *string - BuildWorkflowInputs map[string]string - PrintProvenance bool + ProvenancePath *string + ProvenanceRepository *string + BuilderID *string + SourceURI string + SourceBranch *string + SourceTag *string + SourceVersionTag *string + BuildWorkflowInputs map[string]string + PrintProvenance bool } func (c *VerifyImageCommand) Exec(ctx context.Context, artifacts []string) (*utils.TrustedBuilderID, error) { @@ -70,7 +71,12 @@ func (c *VerifyImageCommand) Exec(ctx context.Context, artifacts []string) (*uti } } - verifiedProvenance, outBuilderID, err := verifiers.VerifyImage(ctx, artifacts[0], provenance, provenanceOpts, builderOpts) + var provenanceRepository string + if c.ProvenanceRepository != nil { + provenanceRepository = *c.ProvenanceRepository + } + + verifiedProvenance, outBuilderID, err := verifiers.VerifyImage(ctx, artifacts[0], provenance, provenanceRepository, provenanceOpts, builderOpts) if err != nil { return nil, err } diff --git a/register/register.go b/register/register.go index 787bb1f3c..adbd8599f 100644 --- a/register/register.go +++ b/register/register.go @@ -24,8 +24,8 @@ type SLSAVerifier interface { // VerifyImage verifies a provenance for a supplied OCI image. VerifyImage(ctx context.Context, - provenance []byte, artifactImage string, - provenanceOpts *options.ProvenanceOpts, + provenance []byte, provenanceRepository string, + artifactImage string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) diff --git a/verifiers/internal/gcb/verifier.go b/verifiers/internal/gcb/verifier.go index 511065009..abff5cea0 100644 --- a/verifiers/internal/gcb/verifier.go +++ b/verifiers/internal/gcb/verifier.go @@ -50,8 +50,8 @@ func (v *GCBVerifier) VerifyNpmPackage(ctx context.Context, // VerifyImage verifies provenance for an OCI image. func (v *GCBVerifier) VerifyImage(ctx context.Context, - provenance []byte, artifactImage string, - provenanceOpts *options.ProvenanceOpts, + provenance []byte, provenanceRepository string, + artifactImage string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) { prov, err := ProvenanceFromBytes(provenance) diff --git a/verifiers/internal/gha/verifier.go b/verifiers/internal/gha/verifier.go index 62b0cb1cf..ed66d9809 100644 --- a/verifiers/internal/gha/verifier.go +++ b/verifiers/internal/gha/verifier.go @@ -9,6 +9,7 @@ import ( "os" "strings" + "github.com/google/go-containerregistry/pkg/name" "github.com/secure-systems-lab/go-securesystemslib/dsse" "github.com/sigstore/cosign/v2/pkg/cosign" "github.com/sigstore/rekor/pkg/client" @@ -245,8 +246,8 @@ func (v *GHAVerifier) VerifyArtifact(ctx context.Context, // VerifyImage verifies provenance for an OCI image. func (v *GHAVerifier) VerifyImage(ctx context.Context, - provenance []byte, artifactImage string, - provenanceOpts *options.ProvenanceOpts, + provenance []byte, provenanceRepository string, + artifactImage string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) { /* Retrieve any valid signed attestations that chain up to Fulcio root CA. */ @@ -255,10 +256,19 @@ func (v *GHAVerifier) VerifyImage(ctx context.Context, return nil, nil, err } - // Parse any provenance target repository set using environment variable COSIGN_REPOSITORY - provenanceTargetRepository, err := ociremote.GetEnvTargetRepository() - if err != nil { - return nil, nil, err + var provenanceTargetRepository name.Repository + // Consume input for --provenance-repository when set + if provenanceRepository != "" { + provenanceTargetRepository, err = name.NewRepository(provenanceRepository) + if err != nil { + return nil, nil, err + } + } else { + // If user input --provenance-repository is empty, look for COSIGN_REPOSITORY environment + provenanceTargetRepository, err = ociremote.GetEnvTargetRepository() + if err != nil { + return nil, nil, err + } } registryClientOpts := []ociremote.Option{} diff --git a/verifiers/verifier.go b/verifiers/verifier.go index 745523d09..2256a5630 100644 --- a/verifiers/verifier.go +++ b/verifiers/verifier.go @@ -37,6 +37,7 @@ func getVerifier(builderOpts *options.BuilderOpts) (register.SLSAVerifier, error func VerifyImage(ctx context.Context, artifactImage string, provenance []byte, + provenanceRepository string, provenanceOpts *options.ProvenanceOpts, builderOpts *options.BuilderOpts, ) ([]byte, *utils.TrustedBuilderID, error) { @@ -44,7 +45,7 @@ func VerifyImage(ctx context.Context, artifactImage string, if err != nil { return nil, nil, err } - return verifier.VerifyImage(ctx, provenance, artifactImage, provenanceOpts, builderOpts) + return verifier.VerifyImage(ctx, provenance, provenanceRepository, artifactImage, provenanceOpts, builderOpts) } func VerifyArtifact(ctx context.Context,