Skip to content

Commit

Permalink
Merge pull request #78 from philips-labs/slsa-0.2
Browse files Browse the repository at this point in the history
Bump implementation to slsa-0.2 spec
  • Loading branch information
marcofranssen authored Nov 12, 2021
2 parents f1d82ec + 9086b31 commit de8de05
Show file tree
Hide file tree
Showing 7 changed files with 224 additions and 97 deletions.
86 changes: 46 additions & 40 deletions .github/test_resource/example_provenance.json
Original file line number Diff line number Diff line change
@@ -1,42 +1,48 @@
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "salsa.txt",
"digest": {
"sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.1",
"predicate": {
"builder": {
"id": "https://github.com/philips-labs/slsa-provenance-action/Attestations/GitHubHostedActions@v1"
},
"metadata": {
"buildInvocationId": "https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967",
"completeness": {
"arguments": true,
"environment": false,
"materials": false
},
"reproducible": false,
"buildFinishedOn": "2021-10-04T11:08:34Z"
},
"recipe": {
"type": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
"definedInMaterial": 0,
"entryPoint": "Integration test file provenance",
"arguments": null,
"environment": null
},
"materials": [
{
"uri": "git+https://github.com/philips-labs/slsa-provenance-action",
"digest": {
"sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc"
}
}
]
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "salsa.txt",
"digest": {
"sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993"
}
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"builder": {
"id": "https://github.com/philips-labs/slsa-provenance-action/Attestations/GitHubHostedActions@v1"
},
"buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
"invocation": {
"configSource": {
"entryPoint": "ci.yaml:build",
"uri": "git+https://github.com/philips-labs/slsa-provenance-action",
"digest": {
"sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc"
}
},
"parameters": null,
"environment": null
},
"buildConfig": null,
"metadata": {
"buildInvocationId": "https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967",
"buildFinishedOn": "2021-10-04T11:08:34Z",
"completeness": {
"parameters": true,
"environment": false,
"materials": false
},
"reproducible": false
},
"materials": [
{
"uri": "git+https://github.com/philips-labs/slsa-provenance-action",
"digest": {
"sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc"
}
}
]
}
}
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ jobs:

- name: Lint
run: |
result=$(make lint)
result="$(make lint)"
echo "$result"
[ -n "$(echo "$result" | grep 'diff -u')" ] && exit 1 || exit 0
Expand Down
4 changes: 2 additions & 2 deletions lib/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ const (
HostedIDSuffix = "/Attestations/GitHubHostedActions@v1"
// SelfHostedIDSuffix the GitHub self hosted attestation type
SelfHostedIDSuffix = "/Attestations/SelfHostedActions@v1"
// RecipeType the attestion type for a recipe
RecipeType = "https://github.com/Attestations/GitHubActionsWorkflow@v1"
// BuildType URI indicating what type of build was performed. It determines the meaning of invocation, buildConfig and materials.
BuildType = "https://github.com/Attestations/GitHubActionsWorkflow@v1"
// PayloadContentType used to define the Envelope content type
// See: https://github.com/in-toto/attestation#provenance-example
PayloadContentType = "application/vnd.in-toto+json"
Expand Down
4 changes: 2 additions & 2 deletions lib/github/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ func (e *Environment) GenerateProvenanceStatement(ctx context.Context, artifactP
intoto.WithMetadata(fmt.Sprintf("%s/actions/runs/%s", repoURI, e.Context.RunID)),
// NOTE: This is inexact as multiple workflows in a repo can have the same name.
// See https://github.com/github/feedback/discussions/4188
intoto.WithRecipe(
RecipeType,
intoto.WithInvocation(
BuildType,
e.Context.Workflow,
nil,
event.Inputs,
Expand Down
16 changes: 8 additions & 8 deletions lib/github/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,12 +271,13 @@ func TestGenerateProvenance(t *testing.T) {
assert.Equal(intoto.StatementType, stmt.Type)

predicate := stmt.Predicate
assert.Equal(github.BuildType, predicate.BuildType)
assert.Equal(fmt.Sprintf("%s%s", repoURL, github.HostedIDSuffix), predicate.ID)
assert.Equal(materials, predicate.Materials)
assert.Equal(fmt.Sprintf("%s%s", repoURL, github.HostedIDSuffix), predicate.Builder.ID)

assertMetadata(assert, predicate.Metadata, gh, repoURL)
assertRecipe(assert, predicate.Recipe)
assertInvocation(assert, predicate.Invocation)
}

func TestGenerateProvenanceFromGitHubRelease(t *testing.T) {
Expand Down Expand Up @@ -351,9 +352,10 @@ func TestGenerateProvenanceFromGitHubRelease(t *testing.T) {
assert.Equal(fmt.Sprintf("%s%s", repoURL, github.HostedIDSuffix), predicate.ID)
assert.Equal(materials, predicate.Materials)
assert.Equal(fmt.Sprintf("%s%s", repoURL, github.HostedIDSuffix), predicate.Builder.ID)
assert.Equal(github.BuildType, predicate.BuildType)

assertMetadata(assert, predicate.Metadata, ghContext, repoURL)
assertRecipe(assert, predicate.Recipe)
assertInvocation(assert, predicate.Invocation)

stmtPath := path.Join(artifactPath, "build.provenance")

Expand Down Expand Up @@ -393,20 +395,18 @@ func TestGenerateProvenanceFromGitHubReleaseErrors(t *testing.T) {
assert.Nil(stmt)
}

func assertRecipe(assert *assert.Assertions, recipe intoto.Recipe) {
assert.Equal(github.RecipeType, recipe.Type)
assert.Equal(0, recipe.DefinedInMaterial)
assert.Equal("", recipe.EntryPoint)
func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) {
assert.Equal("", recipe.ConfigSource.EntryPoint)
assert.Nil(recipe.Environment)
assert.Nil(recipe.Arguments)
assert.Nil(recipe.Parameters)
}

func assertMetadata(assert *assert.Assertions, meta intoto.Metadata, gh github.Context, repoURL string) {
bft, err := time.Parse(time.RFC3339, meta.BuildFinishedOn)
assert.NoError(err)
assert.WithinDuration(time.Now().UTC(), bft, 1200*time.Millisecond)
assert.Equal(fmt.Sprintf("%s/%s/%s", repoURL, "actions/runs", gh.RunID), meta.BuildInvocationID)
assert.Equal(true, meta.Completeness.Arguments)
assert.Equal(true, meta.Completeness.Parameters)
assert.Equal(false, meta.Completeness.Environment)
assert.Equal(false, meta.Completeness.Materials)
assert.Equal(false, meta.Reproducible)
Expand Down
68 changes: 40 additions & 28 deletions lib/intoto/intoto.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (

const (
// SlsaPredicateType the predicate type for SLSA intoto statements
SlsaPredicateType = "https://slsa.dev/provenance/v0.1"
SlsaPredicateType = "https://slsa.dev/provenance/v0.2"
// StatementType the type of the intoto statement
StatementType = "https://in-toto.io/Statement/v0.1"
)
Expand Down Expand Up @@ -57,7 +57,7 @@ func WithMetadata(buildInvocationID string) StatementOption {
return func(s *Statement) {
s.Predicate.Metadata = Metadata{
Completeness: Completeness{
Arguments: true,
Parameters: true,
Environment: false,
Materials: false,
},
Expand All @@ -68,19 +68,18 @@ func WithMetadata(buildInvocationID string) StatementOption {
}
}

// WithRecipe sets the Predicate Recipe and Materials
func WithRecipe(predicateType string, entryPoint string, environment json.RawMessage, arguments json.RawMessage, materials []Item) StatementOption {
// WithInvocation sets the Predicate Invocation and Materials
func WithInvocation(buildType, entryPoint string, environment json.RawMessage, parameters json.RawMessage, materials []Item) StatementOption {
return func(s *Statement) {
s.Predicate.Recipe = Recipe{
Type: predicateType,
EntryPoint: entryPoint,
Arguments: arguments,
// Subject to change and simplify https://github.com/slsa-framework/slsa/issues/178
// Index in materials containing the recipe steps that are not implied by recipe.type. For example, if the recipe type were "make", then this would point to the source containing the Makefile, not the make program itself.
// Omit this field (or use null) if the recipe doesn't come from a material.
// TODO: What if there is more than one material?
DefinedInMaterial: 0,
Environment: environment,
s.Predicate.BuildType = buildType
s.Predicate.Invocation = Invocation{
ConfigSource: ConfigSource{
EntryPoint: entryPoint,
URI: materials[0].URI,
Digest: materials[0].Digest,
},
Parameters: parameters,
Environment: environment,
}
s.Predicate.Materials = append(s.Predicate.Materials, materials...)
}
Expand Down Expand Up @@ -108,10 +107,17 @@ type Subject struct {
//
// A predicate has a required predicateType (TypeURI) identifying what the predicate means, plus an optional predicate (object) containing additional, type-dependent parameters.
type Predicate struct {
Builder `json:"builder"`
Metadata `json:"metadata"`
Recipe `json:"recipe"`
Materials []Item `json:"materials"`
Builder `json:"builder"`
BuildType string `json:"buildType"`
Invocation `json:"invocation"`
BuildConfig *BuildConfig `json:"build_config,omitempty"`
Metadata `json:"metadata,omitempty"`
Materials []Item `json:"materials"`
}

// BuildConfig Lists the steps in the build.
// If invocation.sourceConfig is not available, buildConfig can be used to verify information about the build.
type BuildConfig struct {
}

// Builder Identifies the entity that executed the recipe, which is trusted to have correctly performed the operation and populated this provenance.
Expand All @@ -127,24 +133,30 @@ type Builder struct {
// Metadata Other properties of the build.
type Metadata struct {
BuildInvocationID string `json:"buildInvocationId"`
Completeness `json:"completeness"`
Reproducible bool `json:"reproducible"`
// BuildStartedOn not defined as it's not available from a GitHub Action.
BuildFinishedOn string `json:"buildFinishedOn"`
Completeness `json:"completeness"`
Reproducible bool `json:"reproducible"`
}

// Invocation Identifies the configuration used for the build. When combined with materials, this SHOULD fully describe the build, such that re-running this recipe results in bit-for-bit identical output (if the build is reproducible).
type Invocation struct {
ConfigSource ConfigSource `json:"configSource"`
Parameters json.RawMessage `json:"parameters"`
Environment json.RawMessage `json:"environment"`
}

// Recipe Identifies the configuration used for the build. When combined with materials, this SHOULD fully describe the build, such that re-running this recipe results in bit-for-bit identical output (if the build is reproducible).
type Recipe struct {
Type string `json:"type"`
DefinedInMaterial int `json:"definedInMaterial"`
EntryPoint string `json:"entryPoint"`
Arguments json.RawMessage `json:"arguments"`
Environment json.RawMessage `json:"environment"`
// ConfigSource Describes where the config file that kicked off the build came from.
// This is effectively a pointer to the source where buildConfig came from.
type ConfigSource struct {
EntryPoint string `json:"entryPoint"`
URI string `json:"uri,omitempty"`
Digest DigestSet `json:"digest,omitempty"`
}

// Completeness Indicates that the builder claims certain fields in this message to be complete.
type Completeness struct {
Arguments bool `json:"arguments"`
Parameters bool `json:"parameters"`
Environment bool `json:"environment"`
Materials bool `json:"materials"`
}
Expand Down
Loading

0 comments on commit de8de05

Please sign in to comment.