mirror of
https://github.com/slsa-framework/slsa-verifier.git
synced 2026-08-26 09:37:19 +00:00
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:
@@ -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 }}"
|
||||
|
||||
@@ -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 ./...
|
||||
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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`
|
||||
Reference in New Issue
Block a user