refactor: add subcommands and separate functionality from artifacts a… (#231)

* refactor: add subcommands and separate functionality from artifacts and images

Signed-off-by: Asra Ali <asraa@google.com>
This commit is contained in:
asraa
2022-09-06 17:10:58 -05:00
committed by GitHub
parent 0211941480
commit ff0ced42ef
13 changed files with 591 additions and 234 deletions
+4 -1
View File
@@ -11,4 +11,7 @@ flags:
goos: linux
goarch: amd64
binary: slsa-verifier-{{ .Os }}-{{ .Arch }}
dir: ./cli/slsa-verifier
dir: ./cli/slsa-verifier
ldflags:
- "-X version.Version={{ .Version }}"
+2 -2
View File
@@ -22,10 +22,10 @@ jobs:
go mod vendor
# Build cli
go build -mod=vendor -o slsa-verifier ./cli/slsa-verifier/main.go
go build -mod=vendor -o slsa-verifier ./cli/slsa-verifier/
# Builder service
go build -mod=vendor -o service ./cli/experimental/service/main.go
go build -mod=vendor -o service ./cli/experimental/service/
# Tests
go test -mod=vendor -v ./...
+33 -18
View File
@@ -49,34 +49,36 @@ $ sha256sum -c --strict SHA256SUM.md
## Verification of Provenance
We currently support artifact verification (for binary blobs) and container images.
### Available options
Below is a list of options currently supported. Note that signature verification is handled seamlessly without the need for developers to manipulate public keys.
Below is a list of options currently supported for binary blobs and container images. Note that signature verification is handled seamlessly without the need for developers to manipulate public keys. See [Available options](#available-options) for details on the options exposed to validate the provenance.
```bash
$ git clone git@github.com:slsa-framework/slsa-verifier.git
$ go run ./cli/slsa-verifier --help
Usage of ./slsa-verifier:
-artifact-path string
path to an artifact to verify
-branch string
expected branch the binary was compiled from (default "main")
-print-provenance
output the verified provenance
-provenance string
path to a provenance file
-source string
expected source repository that should have produced the binary, e.g. github.com/some/repo
-tag string
[optional] expected tag the binary was compiled from
-versioned-tag string
[optional] expected version the binary was compiled from. Uses semantic version to match the tag
$ go run ./cli/slsa-verifier/ verify-artifact --help
Verifies SLSA provenance on an artifact blob
Usage:
slsa-verifier verify-artifact [flags]
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). (default map[])
--builder-id string EXPERIMENTAL: the unique builder ID who created the provenance
-h, --help help for verify-artifact
--print-provenance print the verified provenance to stdout
--provenance-path string path to a provenance file
--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
```
### Example
```bash
$ go run ./cli/slsa-verifier -artifact-path ~/Downloads/slsa-verifier-linux-amd64 -provenance ~/Downloads/slsa-verifier-linux-amd64.intoto.jsonl -source github.com/slsa-framework/slsa-verifier -tag v1.3.0
$ go run ./cli/slsa-verifier -provenance-path ~/Downloads/slsa-verifier-linux-amd64.intoto.jsonl --source-uri github.com/slsa-framework/slsa-verifier --source-tag v1.3.0 ~/Downloads/slsa-verifier-linux-amd64
Verified signature against tlog entry index 3189970 at URL: https://rekor.sigstore.dev/api/v1/log/entries/206071d5ca7a2346e4db4dcb19a648c7f13b4957e655f4382b735894059bd199
Verified build using builder https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v1.2.0 at commit 5bb13ef508b2b8ded49f9264d7712f1316830d10
PASSED: Verified SLSA provenance
@@ -84,6 +86,19 @@ PASSED: Verified SLSA provenance
The verified in-toto statement may be written to stdout with the `--print-provenance` flag to pipe into policy engines.
### Options Details
The following options are supported for [SLSA GitHub builders and generators](https://github.com/slsa-framework/slsa-github-generator#generation-of-provenance):
| Option | Description |
| --- | ----------- |
| `source-uri` | Expects a source, for e.g. `github.com/org/repo`. |
| `source-branch` | Expects a `branch` like `main` or `dev`. Not supported for all GitHub Workflow triggers. |
| `source-tag` | Expects a `tag` like `v0.0.1`. Verifies exact tag used to create the binary. NSupported for new [tag](https://github.com/slsa-framework/example-package/blob/main/.github/workflows/e2e.go.tag.main.config-ldflags-assets-tag.slsa3.yml#L5) and [release](https://github.com/slsa-framework/example-package/blob/main/.github/workflows/e2e.go.release.main.config-ldflags-assets-tag.slsa3.yml) triggers. |
| `source-versioned-tag` | Like `tag`, but verifies using semantic versioning. |
| `build-workflow-input` | Expects key-value pairs like `key=value` to match against [inputs](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#onworkflow_dispatchinputs) for GitHub Actions `workflow_dispatch` triggers. |
## Technical design
### Blog post
+24 -184
View File
@@ -1,202 +1,42 @@
package main
import (
"context"
"crypto/sha256"
"encoding/hex"
"flag"
"errors"
"fmt"
"io"
"os"
"strings"
serrors "github.com/slsa-framework/slsa-verifier/errors"
"github.com/slsa-framework/slsa-verifier/options"
"github.com/slsa-framework/slsa-verifier/verifiers"
"github.com/slsa-framework/slsa-verifier/verifiers/container"
"github.com/spf13/cobra"
)
type workflowInputs struct {
kv map[string]string
func check(err error) {
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}
var (
provenancePath string
builderID string
artifactPath string
artifactImage string
source string
branch string
tag string
versiontag string
inputs workflowInputs
printProvenance bool
)
func experimentalEnabled() bool {
func ExperimentalEnabled() bool {
return os.Getenv("SLSA_VERIFIER_EXPERIMENTAL") == "1"
}
func (i *workflowInputs) String() string {
return fmt.Sprintf("%v", i.kv)
}
func (i *workflowInputs) Set(value string) error {
l := strings.Split(value, "=")
if len(l) != 2 {
return fmt.Errorf("%w: expected 'key=value' format, got '%s'", serrors.ErrorInvalidFormat, value)
func rootCmd() *cobra.Command {
c := &cobra.Command{
Use: "slsa-verifier",
Short: "Verify SLSA provenance for Github Actions",
Long: `Verify SLSA provenance for Github Actions.
For more information on SLSA, visit https://slsa.dev`,
RunE: func(cmd *cobra.Command, args []string) error {
return errors.New("expected command")
},
}
i.kv[l[0]] = l[1]
return nil
}
func (i *workflowInputs) AsMap() map[string]string {
return i.kv
c.AddCommand(versionCmd())
c.AddCommand(verifyArtifactCmd())
c.AddCommand(verifyImageCmd())
// We print our own errors and usage in the check function.
c.SilenceErrors = true
return c
}
func main() {
if experimentalEnabled() {
flag.StringVar(&builderID, "builder-id", "", "EXPERIMENTAL: the unique builder ID who created the provenance")
}
flag.StringVar(&provenancePath, "provenance", "", "path to a provenance file")
flag.StringVar(&artifactPath, "artifact-path", "", "path to an artifact to verify")
flag.StringVar(&artifactImage, "artifact-image", "", "name of the OCI image to verify")
flag.StringVar(&source, "source", "",
"expected source repository that should have produced the binary, e.g. github.com/some/repo")
flag.StringVar(&branch, "branch", "", "[optional] expected branch the binary was compiled from")
flag.StringVar(&tag, "tag", "", "[optional] expected tag the binary was compiled from")
flag.StringVar(&versiontag, "versioned-tag", "",
"[optional] expected version the binary was compiled from. Uses semantic version to match the tag")
flag.BoolVar(&printProvenance, "print-provenance", false,
"print the verified provenance to std out")
inputs.kv = make(map[string]string)
flag.Var(&inputs, "workflow-input",
"[optional] a workflow input provided by a user at trigger time in the format 'key=value'. (Only for 'workflow_dispatch' events).")
flag.Parse()
if artifactImage != "" && artifactPath != "" {
fmt.Fprintf(os.Stderr, "'artifact-image' and 'artifact-path' cannot be specified together\n")
flag.Usage()
os.Exit(1)
}
if source == "" {
flag.Usage()
os.Exit(1)
}
var pbuilderID, pbranch, ptag, pversiontag *string
// Note: nil tag, version-tag and builder-id means we ignore them during verification.
if isFlagPassed("tag") {
ptag = &tag
}
if isFlagPassed("versioned-tag") {
pversiontag = &versiontag
}
if experimentalEnabled() && isFlagPassed("builder-id") {
pbuilderID = &builderID
}
if isFlagPassed("branch") {
pbranch = &branch
}
if ptag != nil && pversiontag != nil {
fmt.Fprintf(os.Stderr, "'version' and 'tag' options cannot be used together\n")
os.Exit(1)
}
verifiedProvenance, _, err := runVerify(artifactImage, artifactPath, provenancePath, source,
pbranch, pbuilderID, ptag, pversiontag, inputs.AsMap(), nil)
if err != nil {
fmt.Fprintf(os.Stderr, "FAILED: SLSA verification failed: %v\n", err)
os.Exit(2)
}
fmt.Fprintf(os.Stderr, "PASSED: Verified SLSA provenance\n")
if printProvenance {
fmt.Fprintf(os.Stdout, "%s\n", string(verifiedProvenance))
}
}
func isFlagPassed(name string) bool {
found := false
flag.Visit(func(f *flag.Flag) {
if f.Name == name {
found = true
}
})
return found
}
type ComputeDigestFn func(string) (string, error)
func runVerify(artifactImage, artifactPath, provenancePath, source string,
branch, builderID, ptag, pversiontag *string, inputs map[string]string,
fn ComputeDigestFn,
) ([]byte, string, error) {
ctx := context.Background()
// Artifact hash retrieval depends on the artifact type.
artifactHash, err := getArtifactHash(artifactImage, artifactPath, fn)
if err != nil {
return nil, "", err
}
provenanceOpts := &options.ProvenanceOpts{
ExpectedSourceURI: source,
ExpectedBranch: branch,
ExpectedDigest: artifactHash,
ExpectedVersionedTag: pversiontag,
ExpectedTag: ptag,
ExpectedWorkflowInputs: inputs,
}
builderOpts := &options.BuilderOpts{
ExpectedID: builderID,
}
var provenance []byte
if provenancePath != "" {
provenance, err = os.ReadFile(provenancePath)
if err != nil {
return nil, "", err
}
}
return verifiers.Verify(ctx, artifactImage, provenance, artifactHash, provenanceOpts, builderOpts)
}
func getArtifactHash(artifactImage, artifactPath string,
// This function is used to handle unit tests and adapt
// digest computation to local images.
fn ComputeDigestFn,
) (string, error) {
if artifactPath != "" {
f, err := os.Open(artifactPath)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
// Retrieve the image digest.
if fn == nil {
fn = container.GetImageDigest
}
digest, err := fn(artifactImage)
if err != nil {
return "", err
}
// Verify that the reference is immutable.
if err := container.ValidateArtifactReference(artifactImage, digest); err != nil {
return "", err
}
return digest, nil
check(rootCmd().Execute())
}
+37 -12
View File
@@ -18,6 +18,7 @@ import (
"github.com/sigstore/cosign/pkg/oci"
"github.com/sigstore/cosign/pkg/oci/layout"
"github.com/slsa-framework/slsa-verifier/cli/slsa-verifier/verify"
serrors "github.com/slsa-framework/slsa-verifier/errors"
"github.com/slsa-framework/slsa-verifier/verifiers/container"
)
@@ -486,13 +487,20 @@ func Test_runVerifyArtifactPath(t *testing.T) {
}
for _, v := range checkVersions {
artifactPath := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifact))
provenancePath := fmt.Sprintf("%s.intoto.jsonl", artifactPath)
_, outBuilderID, err := runVerify("", artifactPath,
provenancePath,
tt.source, tt.pbranch, tt.pbuilderID,
tt.ptag, tt.pversiontag, tt.inputs, nil)
cmd := verify.VerifyArtifactCommand{
ProvenancePath: provenancePath,
SourceURI: tt.source,
SourceBranch: tt.pbranch,
BuilderID: tt.pbuilderID,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
BuildWorkflowInputs: tt.inputs,
}
outBuilderID, err := cmd.Exec(context.Background(), []string{artifactPath})
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
@@ -505,6 +513,7 @@ func Test_runVerifyArtifactPath(t *testing.T) {
if tt.outBuilderID != "" && outBuilderID != tt.outBuilderID {
t.Errorf(cmp.Diff(outBuilderID, tt.outBuilderID))
}
}
})
}
@@ -646,9 +655,16 @@ func Test_runVerifyGHAArtifactImage(t *testing.T) {
for _, v := range checkVersions {
image := filepath.Clean(filepath.Join(TEST_DIR, v, tt.artifact))
_, outBuilderID, err := runVerify(image, "", "",
tt.source, tt.pbranch, tt.pbuilderID,
tt.ptag, tt.pversiontag, nil, localDigestComputeFn)
cmd := verify.VerifyImageCommand{
SourceURI: tt.source,
SourceBranch: tt.pbranch,
BuilderID: tt.pbuilderID,
SourceTag: tt.ptag,
SourceVersionTag: tt.pversiontag,
DigestFn: localDigestComputeFn,
}
outBuilderID, err := cmd.Exec(context.Background(), []string{image})
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
@@ -840,15 +856,23 @@ func Test_runVerifyGCBArtifactImage(t *testing.T) {
for _, v := range checkVersions {
provenance := filepath.Clean(filepath.Join(TEST_DIR, v, tt.provenance))
image := tt.artifact
var fn ComputeDigestFn
var fn verify.ComputeDigestFn
if !tt.oci {
image = filepath.Clean(filepath.Join(TEST_DIR, v, image))
fn = localDigestComputeFn
}
_, outBuilderID, err := runVerify(image, "", provenance,
tt.source, nil, tt.pbuilderID,
nil, nil, nil, fn)
cmd := verify.VerifyImageCommand{
SourceURI: tt.source,
SourceBranch: nil,
BuilderID: tt.pbuilderID,
SourceTag: nil,
SourceVersionTag: nil,
DigestFn: fn,
ProvenancePath: &provenance,
}
outBuilderID, err := cmd.Exec(context.Background(), []string{image})
if !errCmp(err, tt.err) {
t.Errorf(cmp.Diff(err, tt.err, cmpopts.EquateErrors()))
@@ -861,6 +885,7 @@ func Test_runVerifyGCBArtifactImage(t *testing.T) {
if tt.outBuilderID != "" && outBuilderID != tt.outBuilderID {
t.Errorf(cmp.Diff(outBuilderID, tt.outBuilderID))
}
}
})
}
+130
View File
@@ -0,0 +1,130 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"errors"
"fmt"
"os"
"github.com/slsa-framework/slsa-verifier/cli/slsa-verifier/verify"
"github.com/spf13/cobra"
)
const (
SUCCESS = "PASSED: Verified SLSA provenance"
FAILURE = "FAILED: SLSA verification failed"
)
func verifyArtifactCmd() *cobra.Command {
o := &verify.VerifyOptions{}
cmd := &cobra.Command{
Use: "verify-artifact",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("expects a single path to an artifact")
}
return nil
},
Short: "Verifies SLSA provenance on an artifact blob",
RunE: func(cmd *cobra.Command, args []string) error {
v := verify.VerifyArtifactCommand{
ProvenancePath: o.ProvenancePath,
SourceURI: o.SourceURI,
PrintProvenance: o.PrintProvenance,
BuildWorkflowInputs: o.BuildWorkflowInputs.AsMap(),
}
if cmd.Flags().Changed("source-branch") {
v.SourceTag = &o.SourceBranch
}
if cmd.Flags().Changed("source-tag") {
v.SourceTag = &o.SourceTag
}
if cmd.Flags().Changed("source-versioned-tag") {
v.SourceVersionTag = &o.SourceVersionTag
}
if cmd.Flags().Changed("builder-id") {
if !ExperimentalEnabled() {
return fmt.Errorf("builder-id only supported with experimental flag")
}
v.BuilderID = &o.BuilderID
}
if _, err := v.Exec(cmd.Context(), args); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", FAILURE, err)
return err
}
fmt.Fprintf(os.Stderr, "%s\n", SUCCESS)
return nil
},
}
o.AddFlags(cmd)
cmd.MarkFlagRequired("provenance-path")
return cmd
}
func verifyImageCmd() *cobra.Command {
o := &verify.VerifyOptions{}
cmd := &cobra.Command{
Use: "verify-image",
Args: func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("expects a single path to an image")
}
return nil
},
Short: "Verifies SLSA provenance on a container image",
RunE: func(cmd *cobra.Command, args []string) error {
v := verify.VerifyImageCommand{
SourceURI: o.SourceURI,
PrintProvenance: o.PrintProvenance,
BuildWorkflowInputs: o.BuildWorkflowInputs.AsMap(),
}
if cmd.Flags().Changed("provenance-path") {
v.ProvenancePath = &o.ProvenancePath
}
if cmd.Flags().Changed("source-branch") {
v.SourceTag = &o.SourceBranch
}
if cmd.Flags().Changed("source-tag") {
v.SourceTag = &o.SourceTag
}
if cmd.Flags().Changed("source-versioned-tag") {
v.SourceVersionTag = &o.SourceVersionTag
}
if cmd.Flags().Changed("builder-id") {
if !ExperimentalEnabled() {
return fmt.Errorf("builder-id only supported with experimental flag")
}
v.BuilderID = &o.BuilderID
}
if _, err := v.Exec(cmd.Context(), args); err != nil {
fmt.Fprintf(os.Stderr, "%s: %v\n", FAILURE, err)
return err
}
fmt.Fprintf(os.Stderr, "%s\n", SUCCESS)
return nil
},
}
o.AddFlags(cmd)
return cmd
}
+100
View File
@@ -0,0 +1,100 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package verify
import (
"fmt"
"strings"
serrors "github.com/slsa-framework/slsa-verifier/errors"
"github.com/spf13/cobra"
)
type Interface interface {
// AddFlags adds this options' flags to the cobra command.
AddFlags(cmd *cobra.Command)
}
// VerifyOptions is the top-level options for all `verify` commands.
type VerifyOptions struct {
/* Source requirements */
SourceURI string
SourceBranch string
SourceTag string
SourceVersionTag string
/* Builder Requirements */
BuildWorkflowInputs workflowInputs
BuilderID string
/* Other */
ProvenancePath string
PrintProvenance bool
}
var _ Interface = (*VerifyOptions)(nil)
// AddFlags implements Interface
func (o *VerifyOptions) AddFlags(cmd *cobra.Command) {
/* Builder options */
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", "", "EXPERIMENTAL: 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().BoolVar(&o.PrintProvenance, "print-provenance", false,
"print the verified provenance to stdout")
cmd.MarkFlagRequired("source-uri")
cmd.MarkFlagsMutuallyExclusive("source-versioned-tag", "source-tag")
}
type workflowInputs struct {
kv map[string]string
}
func (i *workflowInputs) Type() string {
return fmt.Sprintf("%v", i.kv)
}
func (i *workflowInputs) String() string {
return fmt.Sprintf("%v", i.kv)
}
func (i *workflowInputs) Set(value string) error {
l := strings.Split(value, "=")
if len(l) != 2 {
return fmt.Errorf("%w: expected 'key=value' format, got '%s'", serrors.ErrorInvalidFormat, value)
}
i.kv[l[0]] = l[1]
return nil
}
func (i *workflowInputs) AsMap() map[string]string {
return i.kv
}
@@ -0,0 +1,88 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package verify
import (
"context"
"crypto/sha256"
"encoding/hex"
"fmt"
"io"
"os"
"github.com/slsa-framework/slsa-verifier/options"
"github.com/slsa-framework/slsa-verifier/verifiers"
)
// Note: nil branch, tag, version-tag and builder-id means we ignore them during verification.
type VerifyArtifactCommand struct {
ProvenancePath string
BuilderID *string
SourceURI string
SourceBranch *string
SourceTag *string
SourceVersionTag *string
BuildWorkflowInputs map[string]string
PrintProvenance bool
}
func (c *VerifyArtifactCommand) Exec(ctx context.Context, artifacts []string) (string, error) {
artifactHash, err := getArtifactHash(artifacts[0])
if err != nil {
return "", err
}
provenanceOpts := &options.ProvenanceOpts{
ExpectedSourceURI: c.SourceURI,
ExpectedBranch: c.SourceBranch,
ExpectedDigest: artifactHash,
ExpectedVersionedTag: c.SourceVersionTag,
ExpectedTag: c.SourceTag,
ExpectedWorkflowInputs: c.BuildWorkflowInputs,
}
builderOpts := &options.BuilderOpts{
ExpectedID: c.BuilderID,
}
provenance, err := os.ReadFile(c.ProvenancePath)
if err != nil {
return "", err
}
verifiedProvenance, outBuilderID, err := verifiers.VerifyArtifact(ctx, provenance, artifactHash, provenanceOpts, builderOpts)
if err != nil {
return "", err
}
if c.PrintProvenance {
fmt.Fprintf(os.Stdout, "%s\n", string(verifiedProvenance))
}
return outBuilderID, nil
}
func getArtifactHash(artifactPath string) (string, error) {
f, err := os.Open(artifactPath)
if err != nil {
return "", err
}
defer f.Close()
h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
return "", err
}
return hex.EncodeToString(h.Sum(nil)), nil
}
+90
View File
@@ -0,0 +1,90 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package verify
import (
"context"
"fmt"
"os"
"github.com/slsa-framework/slsa-verifier/options"
"github.com/slsa-framework/slsa-verifier/verifiers"
"github.com/slsa-framework/slsa-verifier/verifiers/container"
)
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
DigestFn ComputeDigestFn
}
func (c *VerifyImageCommand) Exec(ctx context.Context, artifacts []string) (string, error) {
artifactImage := artifacts[0]
// Retrieve the image digest.
if c.DigestFn == nil {
c.DigestFn = container.GetImageDigest
}
digest, err := c.DigestFn(artifactImage)
if err != nil {
return "", err
}
// Verify that the reference is immutable.
if err := container.ValidateArtifactReference(artifactImage, digest); err != nil {
return "", err
}
provenanceOpts := &options.ProvenanceOpts{
ExpectedSourceURI: c.SourceURI,
ExpectedBranch: c.SourceBranch,
ExpectedDigest: digest,
ExpectedVersionedTag: c.SourceVersionTag,
ExpectedTag: c.SourceTag,
ExpectedWorkflowInputs: c.BuildWorkflowInputs,
}
builderOpts := &options.BuilderOpts{
ExpectedID: c.BuilderID,
}
var provenance []byte
if c.ProvenancePath != nil {
provenance, err = os.ReadFile(*c.ProvenancePath)
if err != nil {
return "", err
}
}
verifiedProvenance, outBuilderID, err := verifiers.VerifyImage(ctx, artifacts[0], provenance, provenanceOpts, builderOpts)
if err != nil {
return "", err
}
if c.PrintProvenance {
fmt.Fprintf(os.Stdout, "%s\n", string(verifiedProvenance))
}
return outBuilderID, nil
}
+33
View File
@@ -0,0 +1,33 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"fmt"
"github.com/spf13/cobra"
"github.com/slsa-framework/slsa-verifier/version"
)
func versionCmd() *cobra.Command {
return &cobra.Command{
Use: "version",
Short: "Print the version and exit",
Run: func(cmd *cobra.Command, args []string) {
fmt.Println(version.Version)
},
}
}
+1 -1
View File
@@ -129,7 +129,7 @@ func verifyHandlerV1(r *http.Request) *v1Result {
}
ctx := context.Background()
p, builderID, err := verifiers.Verify(ctx, "", []byte(query.DsseEnvelope),
p, builderID, err := verifiers.VerifyArtifact(ctx, []byte(query.DsseEnvelope),
query.ArtifactHash, provenanceOpts, builderOpts)
if err != nil {
return results.withError(err)
+29 -16
View File
@@ -11,35 +11,48 @@ import (
"github.com/slsa-framework/slsa-verifier/verifiers/internal/gha"
)
func Verify(ctx context.Context, artifactImage string,
provenance []byte, artifactHash string,
provenanceOpts *options.ProvenanceOpts,
builderOpts *options.BuilderOpts,
) ([]byte, string, error) {
func getVerifier(builderOpts *options.BuilderOpts) (register.SLSAVerifier, error) {
// By default, use the GHA builders
verifier := register.SLSAVerifiers[gha.VerifierName]
// If user provids a builderID, find the right verifier based on its ID.
if builderOpts.ExpectedID != nil &&
*builderOpts.ExpectedID != "" {
foundBuilder := false
for _, v := range register.SLSAVerifiers {
if v.IsAuthoritativeFor(*builderOpts.ExpectedID) {
foundBuilder = true
verifier = v
break
return v, nil
}
}
if !foundBuilder {
// No builder found.
return nil, "", fmt.Errorf("%w: %s", serrors.ErrorVerifierNotSupported, *builderOpts.ExpectedID)
}
// No builder found.
return nil, fmt.Errorf("%w: %s", serrors.ErrorVerifierNotSupported, *builderOpts.ExpectedID)
}
// By default, try the GHA builders.
if artifactImage != "" {
return verifier.VerifyImage(ctx, provenance, artifactImage, provenanceOpts, builderOpts)
return verifier, nil
}
func VerifyImage(ctx context.Context, artifactImage string,
provenance []byte,
provenanceOpts *options.ProvenanceOpts,
builderOpts *options.BuilderOpts,
) ([]byte, string, error) {
verifier, err := getVerifier(builderOpts)
if err != nil {
return nil, "", err
}
return verifier.VerifyImage(ctx, provenance, artifactImage, provenanceOpts, builderOpts)
}
func VerifyArtifact(ctx context.Context,
provenance []byte, artifactHash string,
provenanceOpts *options.ProvenanceOpts,
builderOpts *options.BuilderOpts,
) ([]byte, string, error) {
verifier, err := getVerifier(builderOpts)
if err != nil {
return nil, "", err
}
return verifier.VerifyArtifact(ctx, provenance, artifactHash,
provenanceOpts, builderOpts)
}
+20
View File
@@ -0,0 +1,20 @@
// Copyright 2022 SLSA Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package version
// Version is the version of slsa-verifier.
// It is meant to be overwritten with
// -ldflags="-X github.com/slsa-framework/slsa-verifier/version.Version=X.Y".
var Version = `unknown`