Skip to content
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

feat: add v1?draft slsa provenance definition #200

Merged
merged 2 commits into from
Feb 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
141 changes: 141 additions & 0 deletions in_toto/slsa_provenance/v1.0/provenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package v1

import (
"time"

"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
)

const (
// PredicateSLSAProvenance represents a build provenance for an artifact.
PredicateSLSAProvenance = "https://slsa.dev/provenance/v1?draft"
)

// ProvenancePredicate is the provenance predicate definition.
type ProvenancePredicate struct {
// The BuildDefinition describes all of the inputs to the build. The
// accuracy and completeness are implied by runDetails.builder.id.
//
// It SHOULD contain all the information necessary and sufficient to
// initialize the build and begin execution.
BuildDefinition ProvenanceBuildDefinition `json:"buildDefinition"`

// Details specific to this particular execution of the build.
RunDetails ProvenanaceRunDetails `json:"runDetails"`
}

// ProvenanceBuildDefinition describes the inputs to the build.
type ProvenanceBuildDefinition struct {
// Identifies the template for how to perform the build and interpret the
// parameters and dependencies.

// The URI SHOULD resolve to a human-readable specification that includes:
// overall description of the build type; schema for externalParameters and
// systemParameters; unambiguous instructions for how to initiate the build
// given this BuildDefinition, and a complete example.
BuildType string `json:"buildType"`

// The parameters that are under external control, such as those set by a
// user or tenant of the build system. They MUST be complete at SLSA Build
// L3, meaning that that there is no additional mechanism for an external
// party to influence the build. (At lower SLSA Build levels, the
// completeness MAY be best effort.)

// The build system SHOULD be designed to minimize the size and complexity
// of externalParameters, in order to reduce fragility and ease
// verification. Consumers SHOULD have an expectation of what “good” looks
// like; the more information that they need to check, the harder that task
// becomes.
ExternalParameters interface{} `json:"externalParamaters"`

// The parameters that are under the control of the builder. The primary
// intention of this field is for debugging, incident response, and
// vulnerability management. The values here MAY be necessary for
// reproducing the build. There is no need to verify these parameters
// because the build system is already trusted, and in many cases it is not
// practical to do so.
SystemParameters interface{} `json:"systemParamaters"`

// Unordered collection of artifacts needed at build time. Completeness is
// best effort, at least through SLSA Build L3. For example, if the build
// script fetches and executes “example.com/foo.sh”, which in turn fetches
// “example.com/bar.tar.gz”, then both “foo.sh” and “bar.tar.gz” SHOULD be
// listed here.
ResolvedDependencies []ArtifactReference `json:"resolvedDependencies"`
}

// ProvenanceRunDetails includes details specific to a particular execution of a
// build.
type ProvenanaceRunDetails struct {
// Identifies the entity that executed the invocation, which is trusted to
// have correctly performed the operation and populated this provenance.
//
// This field is REQUIRED for SLSA Build 1 unless id is implicit from the
// attestation envelope.
Builder Builder `json:"builder"`

// Metadata about this particular execution of the build.
BuildMetadata BuildMetadata `json:"metadata"`

// Additional artifacts generated during the build that are not considered
// the “output” of the build but that might be needed during debugging or
// incident response. For example, this might reference logs generated
// during the build and/or a digest of the fully evaluated build
// configuration.
//
// In most cases, this SHOULD NOT contain all intermediate files generated
// during the build. Instead, this SHOULD only contain files that are
// likely to be useful later and that cannot be easily reproduced.
Byproducts []ArtifactReference `json:"byproducts"`
}

// ArtifactReference describes a particular artifact. At least one of URI or
// digest MUST be specified.
type ArtifactReference struct {
// URI describing where this artifact came from. When possible, this SHOULD
// be a universal and stable identifier, such as a source location or
// Package URL (purl).
URI string `json:"uri"`

// One or more cryptographic digests of the contents of this artifact.
Digest common.DigestSet `json:"digest"`

// The name for this artifact local to the build.
LocalName string `json:"localName"`

// URI identifying the location that this artifact was downloaded from, if
// different and not derivable from uri.
DownloadLocation string `json:"downloadLocation"`

// Media type (aka MIME type) of this artifact was interpreted.
MediaType string `json:"mediaType"`
}

// Builder represents the transitive closure of all the entities that are, by
// necessity, trusted to faithfully run the build and record the provenance.
type Builder struct {
// URI indicating the transitive closure of the trusted builder.
ID string `json:"id"`

// Version numbers of components of the builder.
Version map[string]string `json:"version"`

// Dependencies used by the orchestrator that are not run within the
// workload and that do not affect the build, but might affect the
// provenance generation or security guarantees.
BuilderDependencies []ArtifactReference `json:"builderDependencies"`
}

type BuildMetadata struct {
// Identifies this particular build invocation, which can be useful for
// finding associated logs or other ad-hoc analysis. The exact meaning and
// format is defined by builder.id; by default it is treated as opaque and
// case-sensitive. The value SHOULD be globally unique.
InvocationID string `json:"invocationID,omitempty"`

// The timestamp of when the build started.
StartedOn *time.Time `json:"startedOn,omitempty"`

// The timestamp of when the build completed.
FinishedOn *time.Time `json:"finishedOn,omitempty"`
}
144 changes: 144 additions & 0 deletions in_toto/slsa_provenance/v1.0/provenance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package v1

import (
"encoding/json"
"testing"
"time"

"github.com/google/go-cmp/cmp"
"github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/common"
"github.com/stretchr/testify/assert"
)

func TestDecodeProvenancePredicate(t *testing.T) {
var data = `
{
"buildDefinition": {
"buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
"resolvedDependencies": [
{
"uri": "git+https://github.com/curl/curl-docker@master",
"digest": { "sha1": "d6525c840a62b398424a78d792f457477135d0cf" }
},
{
"uri": "github_hosted_vm:ubuntu-18.04:20210123.1"
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/Attestations/GitHubHostedActions@v1"
},
"metadata": {
"startedOn": "2020-08-19T08:38:00Z"
}
}
}
`
var testTime = time.Unix(1597826280, 0)
var want = ProvenancePredicate{
BuildDefinition: ProvenanceBuildDefinition{
BuildType: "https://github.com/Attestations/GitHubActionsWorkflow@v1",
ResolvedDependencies: []ArtifactReference{
{
URI: "git+https://github.com/curl/curl-docker@master",
Digest: common.DigestSet{
"sha1": "d6525c840a62b398424a78d792f457477135d0cf",
},
},
{
URI: "github_hosted_vm:ubuntu-18.04:20210123.1",
},
},
},
RunDetails: ProvenanaceRunDetails{
Builder: Builder{
ID: "https://github.com/Attestations/GitHubHostedActions@v1",
},
BuildMetadata: BuildMetadata{
StartedOn: &testTime,
},
},
}
var got ProvenancePredicate

if err := json.Unmarshal([]byte(data), &got); err != nil {
t.Fatalf("failed to unmarshal json: %s\n", err)
}

// Make sure parsed time have same location set, location is only used
// for display purposes.
loc := want.RunDetails.BuildMetadata.StartedOn.Location()
tmp := got.RunDetails.BuildMetadata.StartedOn.In(loc)
got.RunDetails.BuildMetadata.StartedOn = &tmp

assert.Equal(t, want, got, "Unexpected object after decoding")
}

func TestEncodeProvenancePredicate(t *testing.T) {
var testTime = time.Unix(1597826280, 0).In(time.UTC)
var p = ProvenancePredicate{
BuildDefinition: ProvenanceBuildDefinition{
BuildType: "https://github.com/Attestations/GitHubActionsWorkflow@v1",
ExternalParameters: map[string]string{
"entryPoint": "build.yaml:maketgz",
"source": "git+https://github.com/curl/curl-docker@master",
},
SystemParameters: map[string]string{
"GITHUB_RUNNER": "github_hosted_vm:ubuntu-18.04:20210123.1",
},
ResolvedDependencies: []ArtifactReference{
{
URI: "git+https://github.com/curl/curl-docker@master",
Digest: common.DigestSet{
"sha1": "d6525c840a62b398424a78d792f457477135d0cf",
},
},
{
URI: "github_hosted_vm:ubuntu-18.04:20210123.1",
},
{
URI: "git+https://github.com/curl/",
},
},
},
RunDetails: ProvenanaceRunDetails{
Builder: Builder{
ID: "https://github.com/Attestations/GitHubHostedActions@v1",
},
BuildMetadata: BuildMetadata{
StartedOn: &testTime,
FinishedOn: &testTime,
},
},
}
var want = `{"buildDefinition":{"buildType":"https://github.com/Attestations/GitHubActionsWorkflow@v1","externalParamaters":{"entryPoint":"build.yaml:maketgz","source":"git+https://github.com/curl/curl-docker@master"},"systemParamaters":{"GITHUB_RUNNER":"github_hosted_vm:ubuntu-18.04:20210123.1"},"resolvedDependencies":[{"uri":"git+https://github.com/curl/curl-docker@master","digest":{"sha1":"d6525c840a62b398424a78d792f457477135d0cf"},"localName":"","downloadLocation":"","mediaType":""},{"uri":"github_hosted_vm:ubuntu-18.04:20210123.1","digest":null,"localName":"","downloadLocation":"","mediaType":""},{"uri":"git+https://github.com/curl/","digest":null,"localName":"","downloadLocation":"","mediaType":""}]},"runDetails":{"builder":{"id":"https://github.com/Attestations/GitHubHostedActions@v1","version":null,"builderDependencies":null},"metadata":{"startedOn":"2020-08-19T08:38:00Z","finishedOn":"2020-08-19T08:38:00Z"},"byproducts":null}}`
b, err := json.Marshal(&p)
assert.Nil(t, err, "Error during JSON marshal")
if d := cmp.Diff(want, string(b)); d != "" {
t.Fatal(d)
}
assert.Equal(t, want, string(b), "Wrong JSON produced")
}

// Test that the default date (January 1, year 1, 00:00:00 UTC) is
// not marshalled
func TestMetadataNoTime(t *testing.T) {
var md = BuildMetadata{
InvocationID: "123456-12-1",
}
var want = `{"invocationID":"123456-12-1"}`
var got BuildMetadata
b, err := json.Marshal(&md)

t.Run("Marshal", func(t *testing.T) {
assert.Nil(t, err, "Error during JSON marshal")
assert.Equal(t, want, string(b), "Wrong JSON produced")
})

t.Run("Unmashal", func(t *testing.T) {
err := json.Unmarshal(b, &got)
assert.Nil(t, err, "Error during JSON unmarshal")
assert.Equal(t, md, got, "Wrong struct after JSON unmarshal")
})
}