Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
Add support for parsing a SLSA v1 provenance to the internal provenan…
Browse files Browse the repository at this point in the history
…ce representation
  • Loading branch information
rbehjati committed May 3, 2023
1 parent d8894ae commit fb42a0a
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 13 deletions.
47 changes: 43 additions & 4 deletions internal/model/provenance.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"os"

slsav02 "github.com/project-oak/transparent-release/pkg/intoto/slsa_provenance/v0.2"
slsav1 "github.com/project-oak/transparent-release/pkg/intoto/slsa_provenance/v1"

"github.com/project-oak/transparent-release/pkg/intoto"
"github.com/project-oak/transparent-release/pkg/types"
Expand Down Expand Up @@ -168,15 +169,19 @@ func FromValidatedProvenance(prov *types.ValidatedProvenance) (*ProvenanceIR, er
default:
return nil, fmt.Errorf("unsupported buildType (%q) for SLSA0v2 provenance", pred.BuildType)
}
case slsav1.PredicateSLSAProvenance, slsav1.PredicateSLSAProvenanceDraft:
return fromSLSAv1(prov)
default:
return nil, fmt.Errorf("unsupported predicateType (%q) for provenance", predType)
}
}

// fromSLSAv02 maps data from a validated SLSA v0.2 provenance to ProvenanceIR.
// invariant: for every data X in a validated SLSA v0.2 provenance that can be mapped to a field in `ProvenanceIR`, `fromSLSAv02` sets a non-nil value v for X by using `WithX(v)`.
// Invariant: for every data `X` in a validated SLSA v0.2 provenance that can
// be mapped to a field in `ProvenanceIR`, `fromSLSAv02` sets a non-nil value
// `v` for `X` by using `WithX(v)`.
func fromSLSAv02(provenance *types.ValidatedProvenance) (*ProvenanceIR, error) {
// A *tyes.ValidatedProvenance contains a SHA256 hash of a single subject.
// A types.ValidatedProvenance contains a SHA256 hash of a single subject.
binarySHA256Digest := provenance.GetBinarySHA256Digest()
buildType := slsav02.GenericSLSABuildType

Expand All @@ -185,10 +190,11 @@ func fromSLSAv02(provenance *types.ValidatedProvenance) (*ProvenanceIR, error) {
return nil, fmt.Errorf("could not parse provenance predicate: %v", err)
}

// We collect repo uris from where they appear in the provenance to verify that they point to the same reference repo uri.
// We collect repo uris from where they appear in the provenance to verify
// that they point to the same reference repo uri.
repoURIs := slsav02.GetMaterialsGitURI(*predicate)

// A *types.ValidatedProvenance has a binary name.
// A types.ValidatedProvenance has a binary name.
binaryName := provenance.GetBinaryName()

builder := predicate.Builder.ID
Expand All @@ -200,6 +206,39 @@ func fromSLSAv02(provenance *types.ValidatedProvenance) (*ProvenanceIR, error) {
return provenanceIR, nil
}

// fromSLSAv1 maps data from a validated SLSA v1 provenance to ProvenanceIR.
// Invariant: for every data `X` in a validated SLSA v1 provenance that can be
// mapped to a field in `ProvenanceIR`, `fromSLSAv1` sets a non-nil value `v`
// for `X` by using `WithX(v)`.
func fromSLSAv1(provenance *types.ValidatedProvenance) (*ProvenanceIR, error) {
// A types.ValidatedProvenance contains a SHA256 hash of a single subject.
binarySHA256Digest := provenance.GetBinarySHA256Digest()
buildType := slsav1.DockerBasedBuildType
binaryName := provenance.GetBinaryName()

predicate, err := slsav1.ParseContainerBasedSLSAv1Provenance(provenance.GetProvenance().Predicate)
if err != nil {
return nil, fmt.Errorf("parsing SLSA v1 provenance predicate: %v", err)
}

repoURIs := slsav1.GitURI(*predicate)
builder := slsav1.BuilderID(*predicate)
buildCmd := slsav1.BuildCmd(*predicate)
builderImageDigest, err := slsav1.BuilderImageDigest(*predicate)
if err != nil {
return nil, fmt.Errorf("getting builder image digest from SLSA v1 provenance: %v", err)
}

provenanceIR := NewProvenanceIR(binarySHA256Digest, buildType, binaryName,
WithRepoURIs(repoURIs),
WithTrustedBuilder(builder),
WithBuildCmd(buildCmd),
WithBuilderImageSHA256Digest(builderImageDigest),
)

return provenanceIR, nil
}

// ComputeSHA256Digest returns the SHA256 digest of the file in the given path, or an error if the
// file cannot be read.
func ComputeSHA256Digest(path string) (string, error) {
Expand Down
28 changes: 21 additions & 7 deletions internal/model/provenance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@
package model

import (
"fmt"
"os"
"path/filepath"
"testing"

"github.com/google/go-cmp/cmp"
slsav02 "github.com/project-oak/transparent-release/pkg/intoto/slsa_provenance/v0.2"
slsav1 "github.com/project-oak/transparent-release/pkg/intoto/slsa_provenance/v1"
"github.com/project-oak/transparent-release/pkg/types"
)

Expand Down Expand Up @@ -81,12 +81,26 @@ func TestFromProvenance_Slsav1(t *testing.T) {
t.Fatalf("couldn't parse the provenance file: %v", err)
}

// Currently SLSA v1.0 provenances are not supported, so we expect an error.
want := fmt.Sprintf("unsupported predicateType (%q) for provenance", "https://slsa.dev/provenance/v1.0?draft")
_, err = FromValidatedProvenance(provenance)
got := fmt.Sprintf("%v", err)
want := NewProvenanceIR("813841dda3818d616aa3e706e49d0286dc825c5dbad4a75cfb37b91ba412238b",
slsav1.DockerBasedBuildType, "oak_functions_enclave_app",
WithBuildCmd([]string{
"env",
"--chdir=oak_functions_enclave_app",
"cargo",
"build",
"--release",
}),
WithBuilderImageSHA256Digest("51532c757d1008bbff696d053a1d05226f6387cf232aa80b6f9c13b0759ccea0"),
WithRepoURIs([]string{"git+https://github.com/project-oak/oak"}),
WithTrustedBuilder("https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_docker-based_slsa3.yml@refs/tags/v1.6.0-rc.0"),
)

got, err := FromValidatedProvenance(provenance)
if err != nil {
t.Fatalf("couldn't map provenance to ProvenanceIR: %v", err)
}

if got != want {
t.Fatalf("got error %q, want error %q", got, want)
if diff := cmp.Diff(got, want, cmp.AllowUnexported(ProvenanceIR{})); diff != "" {
t.Errorf("unexpected provenanceIR: %s", diff)
}
}
8 changes: 6 additions & 2 deletions pkg/amber/endorsement.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,12 @@ type VerifiedProvenanceSet struct {
Provenances []ProvenanceData
}

// ProvenanceData contains metadata about a provenance statement, identified by a URI and the
// SHA256 digest of the content of the provenance.
// ProvenanceData contains metadata about a provenance statement. The statement may be wrapped in a
// DSSE envelope, or a Sigstore Bundle. The metadata identifies the provenance via a URI and a
// SHA256 digest. The digest may be the SHA256 digest of the provenance content, the DSSE envelope,
// or the Sigstore Bundle. We don't need to explicitly distinguish between these different media
// types in the ProvenanceData, because this metadata is used as the evidence of an Endorsement
// statement, where the media type has no use or relevance.
type ProvenanceData struct {
URI string
SHA256Digest string
Expand Down
227 changes: 227 additions & 0 deletions pkg/intoto/slsa_provenance/v1/provenance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// Copyright 2023 The Project Oak 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.

// Package v1 contains structs representing SLSA provenance v1.0.
package v1

// For more details about the SLSA v1 provenance format see
// https://github.com/slsa-framework/slsa/blob/8df69c20b6f5a08fc71e8591ee2035a780557182/docs/provenance/schema/v1/provenance.proto
// and for the container-based build type, see
// https://github.com/slsa-framework/slsa-github-generator/blob/main/internal/builders/docker/pkg/common.go.

import (
"encoding/json"
"fmt"
"strings"
"time"

"github.com/project-oak/transparent-release/pkg/intoto"
)

const (
// PredicateSLSAProvenance is the predicate type of a SLSA v1 provenance.
PredicateSLSAProvenance = "https://slsa.dev/provenance/v1"

// PredicateSLSAProvenanceDraft is the predicate type of an earlier draft of SLSA v1 provenance.
PredicateSLSAProvenanceDraft = "https://slsa.dev/provenance/v1.0?draft"

// DockerBasedBuildType is the build type of container-based builds.
DockerBasedBuildType = "https://slsa.dev/container-based-build/v0.1?draft"
)

// ProvenancePredicate is the provenance predicate definition.
type ProvenancePredicate struct {
// The BuildDefinition describes all of the inputs to the build.
BuildDefinition ProvenanceBuildDefinition `json:"buildDefinition"`

// The RunDetails describes this particular execution of the build.
RunDetails ProvenanceRunDetails `json:"runDetails"`
}

// ProvenanceBuildDefinition describes all inputs to the build.
type ProvenanceBuildDefinition struct {
// A human-readable URI identifying the template for how to perform the
// build and interpret the parameters and dependencies.
BuildType string `json:"buildType"`

// Parameters that are under external control, such as those set by a user.
ExternalParameters interface{} `json:"externalParameters"`

// Parameters that are under the control of the entity represented by
// builder.id.
InternalParameters interface{} `json:"internalParameters,omitempty"`

// Unordered collection of artifacts needed at build time.
ResolvedDependencies []ResourceDescriptor `json:"resolvedDependencies,omitempty"`
}

// ProvenanceRunDetails includes details specific to a particular execution of a
// build.
type ProvenanceRunDetails struct {
// Identifies the entity that executed the build.
Builder Builder `json:"builder"`

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

// 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.
Byproducts []ResourceDescriptor `json:"byproducts,omitempty"`
}

// ResourceDescriptor describes a particular software resource.
type ResourceDescriptor struct {
// A URI used to identify the resource globally. This field is REQUIRED
// unless either digest or content is set.
URI string `json:"uri,omitempty"`

// A set of cryptographic digests of the contents of the resource. This
// field is REQUIRED unless either uri or content is set.
Digest intoto.DigestSet `json:"digest,omitempty"`

// Machine-readable identifier for distinguishing between descriptors.
Name string `json:"name,omitempty"`

// Location of the described resource, if different from the uri.
DownloadLocation string `json:"downloadLocation,omitempty"`

// The MIME Type (i.e., media type) of the described resource.
MediaType string `json:"mediaType,omitempty"`

// The contents of the resource. This field is REQUIRED unless either uri
// or digest is set.
Content []byte `json:"content,omitempty"`

// This field MAY be used to provide additional information or metadata
// about the resource that may be useful to the consumer when evaluating
// the attestation against a policy.
Annotations map[string]interface{} `json:"annotations,omitempty"`
}

// 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,omitempty"`

// 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 []ResourceDescriptor `json:"builderDependencies,omitempty"`
}

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"`
}

// DockerBasedExternalParameters is a representation of the top level inputs to
// a container-based build.
type DockerBasedExternalParameters struct {
// The source GitHub repo
Source ResourceDescriptor `json:"source"`

// The Docker builder image
BuilderImage ResourceDescriptor `json:"builderImage"`

// Path to a configuration file relative to the root of the repository.
ConfigPath string `json:"configPath"`

// Unpacked build config parameters
Config BuildConfig `json:"buildConfig"`
}

// BuildConfig is a collection of parameters to use for building the artifact
// in a container-based build.
type BuildConfig struct {
// The path, relative to the root of the git repository, where the artifact
// built by the `docker run` command is expected to be found.
ArtifactPath string `toml:"artifact_path"`

// Build command that is passed to `docker run`.
Command []string `toml:"command"`
}

// ParseContainerBasedSLSAv1Provenance parses the given object as a
// ProvenancePredicate, with its BuildDefinition.ExternalParameters parsed into
// an instance of DockerBasedExternalParameters. Returns an error if any of the
// conversions is unsuccessful.
func ParseContainerBasedSLSAv1Provenance(predicate interface{}) (*ProvenancePredicate, error) {
predicateBytes, err := json.Marshal(predicate)
if err != nil {
return nil, fmt.Errorf("marshaling Predicate map into JSON bytes: %v", err)
}

var pred ProvenancePredicate
if err = json.Unmarshal(predicateBytes, &pred); err != nil {
return nil, fmt.Errorf("unmarshaling JSON bytes into a SLSA v1 ProvenancePredicate: %v", err)
}

var extParams DockerBasedExternalParameters
extParamsBytes, err := json.Marshal(pred.BuildDefinition.ExternalParameters)
if err != nil {
return nil, fmt.Errorf("marshaling ExternalParameters map into JSON bytes: %v", err)
}
if err = json.Unmarshal(extParamsBytes, &extParams); err != nil {
return nil, fmt.Errorf("unmarshaling JSON bytes into DockerBasedExternalParameters: %v", err)
}

pred.BuildDefinition.ExternalParameters = extParams

return &pred, nil
}

// BuildCmd extracts and returns the build command from the given ProvenancePredicate.
func BuildCmd(predicate ProvenancePredicate) []string {
return predicate.BuildDefinition.ExternalParameters.(DockerBasedExternalParameters).Config.Command
}

// BuilderImageDigest extracts and returns the digest for the Builder Image.
func BuilderImageDigest(predicate ProvenancePredicate) (string, error) {
digestSet := predicate.BuildDefinition.ExternalParameters.(DockerBasedExternalParameters).BuilderImage.Digest
digest, ok := digestSet["sha256"]
if !ok {
return "", fmt.Errorf("no SHA256 builder image digest in the digest set: %v", digestSet)
}

return digest, nil
}

// GitURI returns references to a Git repo.
func GitURI(predicate ProvenancePredicate) []string {
src := predicate.BuildDefinition.ExternalParameters.(DockerBasedExternalParameters).Source
gitURIs := []string{}
if strings.Contains(src.URI, "git") {
gitURIs = append(gitURIs, src.URI)
}
return gitURIs
}

// BuilderID extracts and returns the builder ID from the given ProvenancePredicate.
func BuilderID(predicate ProvenancePredicate) string {
return predicate.RunDetails.Builder.ID
}

0 comments on commit fb42a0a

Please sign in to comment.