Skip to content
Merged
Show file tree
Hide file tree
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 Mar 29, 2022
272b096
Add utility command to just generate provenance
ianlewis Mar 30, 2022
8f041a9
Fix github repo URI
ianlewis Mar 30, 2022
dd59049
We don't need no indentation
ianlewis Mar 30, 2022
5bffe08
Add buildType
ianlewis Mar 30, 2022
6cb107e
Get arch via Go runtime
ianlewis Mar 30, 2022
b020803
Add reusable workflow
ianlewis Mar 30, 2022
8633a15
Set repo name
ianlewis Mar 30, 2022
57ea3c7
Set ref
ianlewis Mar 30, 2022
2335bd5
Fix generator build command
ianlewis Mar 30, 2022
13bccd6
Merge branch 'slsa-gen-lib' into slsa-gen-lib-ianlewis
ianlewis Mar 30, 2022
aa2efc2
Require sha hash for subjects
ianlewis Mar 30, 2022
ba09895
fix comment
ianlewis Mar 30, 2022
dc1b6bb
Generate the builder ID dynamically
ianlewis Apr 1, 2022
1812ca6
Add CLI option for buildType
ianlewis Apr 1, 2022
453e1d8
Update format of subjects field
ianlewis Apr 1, 2022
6b91a2d
Print help when no command is set
ianlewis Apr 1, 2022
6ccdda1
Updated description for artifacts input
ianlewis Apr 1, 2022
32c779f
Remove arch key since it's misleading
ianlewis Apr 1, 2022
a43753a
Add comment for hash check
ianlewis Apr 1, 2022
9be821f
Add comment on script injection
ianlewis Apr 1, 2022
42f478e
Rework provenance generation
ianlewis Apr 3, 2022
4070b4c
Support non reusable workflow builder id
ianlewis Apr 3, 2022
cd66e2e
Add licence headers
ianlewis Apr 3, 2022
341c40d
Combine jobs
ianlewis Apr 3, 2022
3e79a85
Add sanity check for hash
ianlewis Apr 3, 2022
49fd5e3
Change sig on Upload to not depend on Rekor
ianlewis Apr 3, 2022
1643f03
Move WorkflowRun to github.go
ianlewis Apr 3, 2022
87bef75
Merge branch 'slsa-gen-lib' into slsa-gen-lib-ianlewis
ianlewis Apr 3, 2022
f164a1b
Remove unnecessary echo
ianlewis Apr 3, 2022
e5a7ac8
Revert workflow repo and ref
ianlewis Apr 5, 2022
9d88f7b
Fix workflow outputs
ianlewis Apr 5, 2022
bee0c01
Update format for subjects flag
ianlewis Apr 5, 2022
96c4054
fix subjects input
ianlewis Apr 5, 2022
2915cfa
Fix order of subject args
ianlewis Apr 5, 2022
43155ea
Improve subjects parsing
ianlewis Apr 5, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .github/workflows/provenance.yml
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:
Comment thread
laurentsimon marked this conversation as resolved.
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 }}"
Comment thread
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
138 changes: 138 additions & 0 deletions cmd/slsa-github-generator/attest.go
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 {
Comment thread
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
Comment thread
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
}
107 changes: 107 additions & 0 deletions cmd/slsa-github-generator/attest_test.go
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)
}
}
})
}
}
Loading