Skip to content

Commit ff0ced4

Browse files
authored
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 <[email protected]>
1 parent 0211941 commit ff0ced4

File tree

13 files changed

+591
-234
lines changed

13 files changed

+591
-234
lines changed

.github/config-release.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,7 @@ flags:
1111
goos: linux
1212
goarch: amd64
1313
binary: slsa-verifier-{{ .Os }}-{{ .Arch }}
14-
dir: ./cli/slsa-verifier
14+
dir: ./cli/slsa-verifier
15+
16+
ldflags:
17+
- "-X version.Version={{ .Version }}"

.github/workflows/pre-submit.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
go mod vendor
2323
2424
# Build cli
25-
go build -mod=vendor -o slsa-verifier ./cli/slsa-verifier/main.go
25+
go build -mod=vendor -o slsa-verifier ./cli/slsa-verifier/
2626
2727
# Builder service
28-
go build -mod=vendor -o service ./cli/experimental/service/main.go
28+
go build -mod=vendor -o service ./cli/experimental/service/
2929
3030
# Tests
3131
go test -mod=vendor -v ./...

README.md

+33-18
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,56 @@ $ sha256sum -c --strict SHA256SUM.md
4949

5050
## Verification of Provenance
5151

52+
We currently support artifact verification (for binary blobs) and container images.
53+
5254
### Available options
5355

54-
Below is a list of options currently supported. Note that signature verification is handled seamlessly without the need for developers to manipulate public keys.
56+
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.
5557

5658
```bash
5759
$ git clone [email protected]:slsa-framework/slsa-verifier.git
58-
$ go run ./cli/slsa-verifier --help
59-
Usage of ./slsa-verifier:
60-
-artifact-path string
61-
path to an artifact to verify
62-
-branch string
63-
expected branch the binary was compiled from (default "main")
64-
-print-provenance
65-
output the verified provenance
66-
-provenance string
67-
path to a provenance file
68-
-source string
69-
expected source repository that should have produced the binary, e.g. github.com/some/repo
70-
-tag string
71-
[optional] expected tag the binary was compiled from
72-
-versioned-tag string
73-
[optional] expected version the binary was compiled from. Uses semantic version to match the tag
60+
$ go run ./cli/slsa-verifier/ verify-artifact --help
61+
Verifies SLSA provenance on an artifact blob
62+
63+
Usage:
64+
slsa-verifier verify-artifact [flags]
65+
66+
Flags:
67+
--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[])
68+
--builder-id string EXPERIMENTAL: the unique builder ID who created the provenance
69+
-h, --help help for verify-artifact
70+
--print-provenance print the verified provenance to stdout
71+
--provenance-path string path to a provenance file
72+
--source-branch string [optional] expected branch the binary was compiled from
73+
--source-tag string [optional] expected tag the binary was compiled from
74+
--source-uri string expected source repository that should have produced the binary, e.g. github.com/some/repo
75+
--source-versioned-tag string [optional] expected version the binary was compiled from. Uses semantic version to match the tag
7476
```
7577

7678
### Example
7779

7880
```bash
79-
$ 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
81+
$ 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
8082
Verified signature against tlog entry index 3189970 at URL: https://rekor.sigstore.dev/api/v1/log/entries/206071d5ca7a2346e4db4dcb19a648c7f13b4957e655f4382b735894059bd199
8183
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
8284
PASSED: Verified SLSA provenance
8385
```
8486

8587
The verified in-toto statement may be written to stdout with the `--print-provenance` flag to pipe into policy engines.
8688

89+
### Options Details
90+
91+
The following options are supported for [SLSA GitHub builders and generators](https://github.com/slsa-framework/slsa-github-generator#generation-of-provenance):
92+
93+
| Option | Description |
94+
| --- | ----------- |
95+
| `source-uri` | Expects a source, for e.g. `github.com/org/repo`. |
96+
| `source-branch` | Expects a `branch` like `main` or `dev`. Not supported for all GitHub Workflow triggers. |
97+
| `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. |
98+
| `source-versioned-tag` | Like `tag`, but verifies using semantic versioning. |
99+
| `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. |
100+
101+
87102
## Technical design
88103

89104
### Blog post

cli/slsa-verifier/main.go

+24-184
Original file line numberDiff line numberDiff line change
@@ -1,202 +1,42 @@
11
package main
22

33
import (
4-
"context"
5-
"crypto/sha256"
6-
"encoding/hex"
7-
"flag"
4+
"errors"
85
"fmt"
9-
"io"
106
"os"
11-
"strings"
127

13-
serrors "github.com/slsa-framework/slsa-verifier/errors"
14-
15-
"github.com/slsa-framework/slsa-verifier/options"
16-
"github.com/slsa-framework/slsa-verifier/verifiers"
17-
"github.com/slsa-framework/slsa-verifier/verifiers/container"
8+
"github.com/spf13/cobra"
189
)
1910

20-
type workflowInputs struct {
21-
kv map[string]string
22-
}
23-
24-
var (
25-
provenancePath string
26-
builderID string
27-
artifactPath string
28-
artifactImage string
29-
source string
30-
branch string
31-
tag string
32-
versiontag string
33-
inputs workflowInputs
34-
printProvenance bool
35-
)
36-
37-
func experimentalEnabled() bool {
38-
return os.Getenv("SLSA_VERIFIER_EXPERIMENTAL") == "1"
39-
}
40-
41-
func (i *workflowInputs) String() string {
42-
return fmt.Sprintf("%v", i.kv)
43-
}
44-
45-
func (i *workflowInputs) Set(value string) error {
46-
l := strings.Split(value, "=")
47-
if len(l) != 2 {
48-
return fmt.Errorf("%w: expected 'key=value' format, got '%s'", serrors.ErrorInvalidFormat, value)
49-
}
50-
i.kv[l[0]] = l[1]
51-
return nil
52-
}
53-
54-
func (i *workflowInputs) AsMap() map[string]string {
55-
return i.kv
56-
}
57-
58-
func main() {
59-
if experimentalEnabled() {
60-
flag.StringVar(&builderID, "builder-id", "", "EXPERIMENTAL: the unique builder ID who created the provenance")
61-
}
62-
flag.StringVar(&provenancePath, "provenance", "", "path to a provenance file")
63-
flag.StringVar(&artifactPath, "artifact-path", "", "path to an artifact to verify")
64-
flag.StringVar(&artifactImage, "artifact-image", "", "name of the OCI image to verify")
65-
flag.StringVar(&source, "source", "",
66-
"expected source repository that should have produced the binary, e.g. github.com/some/repo")
67-
flag.StringVar(&branch, "branch", "", "[optional] expected branch the binary was compiled from")
68-
flag.StringVar(&tag, "tag", "", "[optional] expected tag the binary was compiled from")
69-
flag.StringVar(&versiontag, "versioned-tag", "",
70-
"[optional] expected version the binary was compiled from. Uses semantic version to match the tag")
71-
flag.BoolVar(&printProvenance, "print-provenance", false,
72-
"print the verified provenance to std out")
73-
inputs.kv = make(map[string]string)
74-
flag.Var(&inputs, "workflow-input",
75-
"[optional] a workflow input provided by a user at trigger time in the format 'key=value'. (Only for 'workflow_dispatch' events).")
76-
flag.Parse()
77-
78-
if artifactImage != "" && artifactPath != "" {
79-
fmt.Fprintf(os.Stderr, "'artifact-image' and 'artifact-path' cannot be specified together\n")
80-
flag.Usage()
81-
os.Exit(1)
82-
}
83-
84-
if source == "" {
85-
flag.Usage()
86-
os.Exit(1)
87-
}
88-
89-
var pbuilderID, pbranch, ptag, pversiontag *string
90-
91-
// Note: nil tag, version-tag and builder-id means we ignore them during verification.
92-
if isFlagPassed("tag") {
93-
ptag = &tag
94-
}
95-
if isFlagPassed("versioned-tag") {
96-
pversiontag = &versiontag
97-
}
98-
if experimentalEnabled() && isFlagPassed("builder-id") {
99-
pbuilderID = &builderID
100-
}
101-
if isFlagPassed("branch") {
102-
pbranch = &branch
103-
}
104-
105-
if ptag != nil && pversiontag != nil {
106-
fmt.Fprintf(os.Stderr, "'version' and 'tag' options cannot be used together\n")
107-
os.Exit(1)
108-
}
109-
110-
verifiedProvenance, _, err := runVerify(artifactImage, artifactPath, provenancePath, source,
111-
pbranch, pbuilderID, ptag, pversiontag, inputs.AsMap(), nil)
11+
func check(err error) {
11212
if err != nil {
113-
fmt.Fprintf(os.Stderr, "FAILED: SLSA verification failed: %v\n", err)
114-
os.Exit(2)
115-
}
116-
117-
fmt.Fprintf(os.Stderr, "PASSED: Verified SLSA provenance\n")
118-
if printProvenance {
119-
fmt.Fprintf(os.Stdout, "%s\n", string(verifiedProvenance))
13+
fmt.Fprintln(os.Stderr, err)
14+
os.Exit(1)
12015
}
12116
}
12217

123-
func isFlagPassed(name string) bool {
124-
found := false
125-
flag.Visit(func(f *flag.Flag) {
126-
if f.Name == name {
127-
found = true
128-
}
129-
})
130-
return found
18+
func ExperimentalEnabled() bool {
19+
return os.Getenv("SLSA_VERIFIER_EXPERIMENTAL") == "1"
13120
}
13221

133-
type ComputeDigestFn func(string) (string, error)
134-
135-
func runVerify(artifactImage, artifactPath, provenancePath, source string,
136-
branch, builderID, ptag, pversiontag *string, inputs map[string]string,
137-
fn ComputeDigestFn,
138-
) ([]byte, string, error) {
139-
ctx := context.Background()
140-
141-
// Artifact hash retrieval depends on the artifact type.
142-
artifactHash, err := getArtifactHash(artifactImage, artifactPath, fn)
143-
if err != nil {
144-
return nil, "", err
22+
func rootCmd() *cobra.Command {
23+
c := &cobra.Command{
24+
Use: "slsa-verifier",
25+
Short: "Verify SLSA provenance for Github Actions",
26+
Long: `Verify SLSA provenance for Github Actions.
27+
For more information on SLSA, visit https://slsa.dev`,
28+
RunE: func(cmd *cobra.Command, args []string) error {
29+
return errors.New("expected command")
30+
},
14531
}
146-
147-
provenanceOpts := &options.ProvenanceOpts{
148-
ExpectedSourceURI: source,
149-
ExpectedBranch: branch,
150-
ExpectedDigest: artifactHash,
151-
ExpectedVersionedTag: pversiontag,
152-
ExpectedTag: ptag,
153-
ExpectedWorkflowInputs: inputs,
154-
}
155-
156-
builderOpts := &options.BuilderOpts{
157-
ExpectedID: builderID,
158-
}
159-
160-
var provenance []byte
161-
if provenancePath != "" {
162-
provenance, err = os.ReadFile(provenancePath)
163-
if err != nil {
164-
return nil, "", err
165-
}
166-
}
167-
168-
return verifiers.Verify(ctx, artifactImage, provenance, artifactHash, provenanceOpts, builderOpts)
32+
c.AddCommand(versionCmd())
33+
c.AddCommand(verifyArtifactCmd())
34+
c.AddCommand(verifyImageCmd())
35+
// We print our own errors and usage in the check function.
36+
c.SilenceErrors = true
37+
return c
16938
}
17039

171-
func getArtifactHash(artifactImage, artifactPath string,
172-
// This function is used to handle unit tests and adapt
173-
// digest computation to local images.
174-
fn ComputeDigestFn,
175-
) (string, error) {
176-
if artifactPath != "" {
177-
f, err := os.Open(artifactPath)
178-
if err != nil {
179-
return "", err
180-
}
181-
defer f.Close()
182-
h := sha256.New()
183-
if _, err := io.Copy(h, f); err != nil {
184-
return "", err
185-
}
186-
return hex.EncodeToString(h.Sum(nil)), nil
187-
}
188-
// Retrieve the image digest.
189-
if fn == nil {
190-
fn = container.GetImageDigest
191-
}
192-
digest, err := fn(artifactImage)
193-
if err != nil {
194-
return "", err
195-
}
196-
197-
// Verify that the reference is immutable.
198-
if err := container.ValidateArtifactReference(artifactImage, digest); err != nil {
199-
return "", err
200-
}
201-
return digest, nil
40+
func main() {
41+
check(rootCmd().Execute())
20242
}

0 commit comments

Comments
 (0)