-
Notifications
You must be signed in to change notification settings - Fork 180
Initial API for Github provenance generation. #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
de8b66c
Initial API for Github provenance generation.
ianlewis 272b096
Add utility command to just generate provenance
ianlewis 8f041a9
Fix github repo URI
ianlewis dd59049
We don't need no indentation
ianlewis 5bffe08
Add buildType
ianlewis 6cb107e
Get arch via Go runtime
ianlewis b020803
Add reusable workflow
ianlewis 8633a15
Set repo name
ianlewis 57ea3c7
Set ref
ianlewis 2335bd5
Fix generator build command
ianlewis 13bccd6
Merge branch 'slsa-gen-lib' into slsa-gen-lib-ianlewis
ianlewis aa2efc2
Require sha hash for subjects
ianlewis ba09895
fix comment
ianlewis dc1b6bb
Generate the builder ID dynamically
ianlewis 1812ca6
Add CLI option for buildType
ianlewis 453e1d8
Update format of subjects field
ianlewis 6b91a2d
Print help when no command is set
ianlewis 6ccdda1
Updated description for artifacts input
ianlewis 32c779f
Remove arch key since it's misleading
ianlewis a43753a
Add comment for hash check
ianlewis 9be821f
Add comment on script injection
ianlewis 42f478e
Rework provenance generation
ianlewis 4070b4c
Support non reusable workflow builder id
ianlewis cd66e2e
Add licence headers
ianlewis 341c40d
Combine jobs
ianlewis 3e79a85
Add sanity check for hash
ianlewis 49fd5e3
Change sig on Upload to not depend on Rekor
ianlewis 1643f03
Move WorkflowRun to github.go
ianlewis 87bef75
Merge branch 'slsa-gen-lib' into slsa-gen-lib-ianlewis
ianlewis f164a1b
Remove unnecessary echo
ianlewis e5a7ac8
Revert workflow repo and ref
ianlewis 9d88f7b
Fix workflow outputs
ianlewis bee0c01
Update format for subjects flag
ianlewis 96c4054
fix subjects input
ianlewis 2915cfa
Fix order of subject args
ianlewis 43155ea
Improve subjects parsing
ianlewis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,109 @@ | ||
| # 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 | ||
| # | ||
| # http://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. | ||
|
|
||
| name: SLSA provenance generator | ||
|
|
||
| permissions: | ||
| contents: read | ||
|
|
||
| env: | ||
| # This repo. | ||
| GENERATOR_REPOSITORY: slsa-framework/slsa-github-generator | ||
| GENERATOR_REF: main | ||
|
|
||
| ################################################################### | ||
| # # | ||
| # Input and output argument definitions # | ||
| # # | ||
| ################################################################### | ||
| on: | ||
| workflow_call: | ||
| inputs: | ||
| subjects: | ||
| description: "Artifacts for which to generate provenance, formatted the same as the output of sha256sum (SHA256 NAME\\n[...])" | ||
| required: true | ||
| type: string | ||
| outputs: | ||
| attestation-name: | ||
| description: "The artifact name of the signed provenance" | ||
| value: ${{ jobs.generator.outputs.signed-provenance-name }} | ||
|
|
||
| jobs: | ||
| ################################################################### | ||
| # # | ||
| # Build the generator # | ||
| # # | ||
| ################################################################### | ||
| generator: | ||
| outputs: | ||
| signed-provenance-name: ${{ steps.sign-prov.outputs.signed-provenance-name }} | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| id-token: write # Needed for keyless. | ||
| contents: read | ||
| steps: | ||
| - name: Checkout the repository | ||
| uses: actions/checkout@ec3a7ce113134d7a93b817d10a8272cb61118579 # v2.3.4 | ||
| with: | ||
| fetch-depth: 0 | ||
| repository: "${{ env.GENERATOR_REPOSITORY }}" | ||
| ref: "${{ env.GENERATOR_REF }}" | ||
|
ianlewis marked this conversation as resolved.
|
||
|
|
||
| - name: Set up golang environment | ||
| uses: actions/setup-go@424fc82d43fa5a37540bae62709ddcc23d9520d4 # v2.1.3 | ||
| with: | ||
| go-version: 1.18 | ||
|
|
||
| - name: Download dependencies | ||
| shell: bash | ||
| run: | | ||
| set -euo pipefail | ||
| #TODO(reproducible) | ||
| go mod vendor | ||
|
|
||
| # TODO(hermeticity) OS-level. | ||
|
|
||
| - name: Build slsa-github-generator | ||
| shell: bash | ||
| id: generator-gen | ||
| run: | | ||
| set -euo pipefail | ||
|
|
||
| # https://go.dev/ref/mod#build-commands. | ||
| go build -mod=vendor -o slsa-github-generator github.com/slsa-framework/slsa-github-generator/cmd/slsa-github-generator | ||
| chmod a+x slsa-github-generator | ||
|
|
||
| - name: Create and sign provenance | ||
| id: sign-prov | ||
| shell: bash | ||
| # NOTE: Inputs and github context are set to environment variables in | ||
| # order to avoid script injection. | ||
| # See: https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions#understanding-the-risk-of-script-injections | ||
| env: | ||
| SUBJECTS: "${{ inputs.subjects }}" | ||
| GITHUB_CONTEXT: "${{ toJSON(github) }}" | ||
| run: | | ||
| set -euo pipefail | ||
| # Create and sign provenance | ||
| # This sets signed-provenance-name to the name of the signed DSSE envelope. | ||
| ./slsa-github-generator attest --subjects "${SUBJECTS}" -g attestation.intoto.jsonl | ||
| echo "::set-output name=signed-provenance-name::attestation.intoto.jsonl" | ||
|
|
||
| - name: Upload the signed provenance | ||
| uses: actions/upload-artifact@82c141cc518b40d92cc801eee768e7aafc9c2fa2 # v2.3.1 | ||
| with: | ||
| name: "${{ steps.sign-prov.outputs.signed-provenance-name }}" | ||
| path: "${{ steps.sign-prov.outputs.signed-provenance-name }}" | ||
| if-no-files-found: error | ||
| retention-days: 5 | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,138 @@ | ||
| // 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 ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "io" | ||
| "os" | ||
| "regexp" | ||
| "strings" | ||
|
|
||
| intoto "github.com/in-toto/in-toto-golang/in_toto" | ||
| slsav02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" | ||
| "github.com/spf13/cobra" | ||
|
|
||
| "github.com/slsa-framework/slsa-github-generator/github" | ||
| "github.com/slsa-framework/slsa-github-generator/signing/sigstore" | ||
| "github.com/slsa-framework/slsa-github-generator/slsa" | ||
| ) | ||
|
|
||
| var ( | ||
| // shaCheck verifies a hash is has only hexidecimal digits and is 64 | ||
| // characters long. | ||
| shaCheck = regexp.MustCompile(`^[a-fA-F0-9]{64}$`) | ||
| ) | ||
|
|
||
| // parseSubjects parses the value given to the subjects option. | ||
| func parseSubjects(subjectsStr string) ([]intoto.Subject, error) { | ||
| var parsed []intoto.Subject | ||
|
|
||
| subjects := strings.Split(subjectsStr, "\n") | ||
| for _, s := range subjects { | ||
|
ianlewis marked this conversation as resolved.
|
||
| // Split by whitespace, and get values. | ||
| fields := strings.Fields(s) | ||
|
|
||
| // Check for the sha256 digest. | ||
| if len(fields) == 0 { | ||
| // NOTE: Ignore blank or whitespace only lines. | ||
| continue | ||
| } | ||
| // Lowercase the sha digest to comply with the SLSA spec. | ||
| shaDigest := strings.ToLower(fields[0]) | ||
| // Do a sanity check on the SHA to make sure it's a proper hex digest. | ||
| if !shaCheck.MatchString(shaDigest) { | ||
| return nil, fmt.Errorf("unexpected sha256 hash %q", shaDigest) | ||
| } | ||
|
|
||
| // Check for the subject name. | ||
| if len(fields) == 1 { | ||
| return nil, fmt.Errorf("expected subject name for hash %q", shaDigest) | ||
| } | ||
| name := fields[1] | ||
|
|
||
| if len(fields) > 2 { | ||
| return nil, fmt.Errorf("unexpected extra values: %v", fields[2:]) | ||
| } | ||
|
|
||
| parsed = append(parsed, intoto.Subject{ | ||
| Name: name, | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": shaDigest, | ||
| }, | ||
| }) | ||
| } | ||
| return parsed, nil | ||
|
ianlewis marked this conversation as resolved.
|
||
| } | ||
|
|
||
| func getFile(path string) (io.Writer, error) { | ||
| if path == "-" { | ||
| return os.Stdout, nil | ||
| } | ||
| return os.OpenFile(path, os.O_WRONLY|os.O_CREATE, 0600) | ||
| } | ||
|
|
||
| // attestCmd returns the 'attest' command. | ||
| func attestCmd() *cobra.Command { | ||
| var attPath string | ||
| var subjects string | ||
|
|
||
| c := &cobra.Command{ | ||
| Use: "attest", | ||
| Short: "Create a signed SLSA attestation from a Github Action", | ||
| Long: `Generate and sign SLSA provenance from a Github Action to form an attestation | ||
| and upload to a Rekor transparency log. This command assumes that it is being | ||
| run in the context of a Github Actions workflow.`, | ||
|
|
||
| Run: func(cmd *cobra.Command, args []string) { | ||
| ghContext, err := github.GetWorkflowContext() | ||
| check(err) | ||
|
|
||
| parsedSubjects, err := parseSubjects(subjects) | ||
| check(err) | ||
|
|
||
| if len(parsedSubjects) == 0 { | ||
| check(errors.New("expected at least one subject")) | ||
| } | ||
|
|
||
| p, err := slsa.HostedActionsProvenance(slsa.NewWorkflowRun(parsedSubjects, ghContext)) | ||
| check(err) | ||
|
|
||
| if attPath != "" { | ||
| ctx := context.Background() | ||
|
|
||
| s := sigstore.NewDefaultSigner() | ||
| att, err := s.Sign(ctx, p) | ||
| check(err) | ||
|
|
||
| check(s.Upload(ctx, att)) | ||
|
|
||
| f, err := getFile(attPath) | ||
| check(err) | ||
|
|
||
| _, err = f.Write(att.Bytes()) | ||
| check(err) | ||
|
|
||
| } | ||
| }, | ||
| } | ||
|
|
||
| c.Flags().StringVarP(&attPath, "signature", "g", "attestation.intoto.jsonl", "Path to write the signed attestation") | ||
| c.Flags().StringVarP(&subjects, "subjects", "s", "", "Formatted list of subjects in the same format as sha256sum") | ||
|
|
||
| return c | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| package main | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
|
|
||
| intoto "github.com/in-toto/in-toto-golang/in_toto" | ||
| slsav02 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v0.2" | ||
| ) | ||
|
|
||
| // TestParseSubjects tests the parseSubjects function. | ||
| func TestParseSubjects(t *testing.T) { | ||
| testCases := []struct { | ||
| name string | ||
| str string | ||
| expected []intoto.Subject | ||
| err bool | ||
| }{ | ||
| { | ||
| name: "single", | ||
| str: "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2 hoge", | ||
| expected: []intoto.Subject{ | ||
| { | ||
| Name: "hoge", | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "multiple", | ||
| str: `2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2 hoge | ||
| e712aff3705ac314b9a890e0ec208faa20054eee514d86ab913d768f94e01279 fuga`, | ||
| expected: []intoto.Subject{ | ||
| { | ||
| Name: "hoge", | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2", | ||
| }, | ||
| }, | ||
| { | ||
| Name: "fuga", | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": "e712aff3705ac314b9a890e0ec208faa20054eee514d86ab913d768f94e01279", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "empty", | ||
| str: "", | ||
| expected: nil, | ||
| }, | ||
| { | ||
| name: "blank lines", | ||
| str: `2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2 hoge | ||
|
|
||
| e712aff3705ac314b9a890e0ec208faa20054eee514d86ab913d768f94e01279 fuga`, | ||
| expected: []intoto.Subject{ | ||
| { | ||
| Name: "hoge", | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2", | ||
| }, | ||
| }, | ||
| { | ||
| Name: "fuga", | ||
| Digest: slsav02.DigestSet{ | ||
| "sha256": "e712aff3705ac314b9a890e0ec208faa20054eee514d86ab913d768f94e01279", | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| { | ||
| name: "sha only", | ||
| str: "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2", | ||
| err: true, | ||
| }, | ||
| { | ||
| name: "extra fields", | ||
| str: "2e0390eb024a52963db7b95e84a9c2b12c004054a7bad9a97ec0c7c89d4681d2 hoge extra fields", | ||
| err: true, | ||
| }, | ||
| { | ||
| name: "invalid hash", | ||
| str: "abcdef hoge", | ||
| err: true, | ||
| }, | ||
| } | ||
|
|
||
| for _, tc := range testCases { | ||
| t.Run(tc.name, func(t *testing.T) { | ||
| if s, err := parseSubjects(tc.str); err != nil { | ||
| if tc.err { | ||
| // Error was expected. | ||
| return | ||
| } | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } else { | ||
| if want, got := tc.expected, s; !reflect.DeepEqual(want, got) { | ||
| t.Errorf("unexpected subjects, want: %#v, got: %#v", want, got) | ||
| } | ||
| } | ||
| }) | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.