From 9551b975f592d3b5161e56be00195a249dc25fca Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Mon, 8 Nov 2021 13:28:18 +0100 Subject: [PATCH 01/10] Rename recipe to invocation Signed-off-by: Marco Franssen --- lib/github/provenance.go | 2 +- lib/github/provenance_test.go | 6 +++--- lib/intoto/intoto.go | 18 +++++++++--------- lib/intoto/intoto_test.go | 12 ++++++------ 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/lib/github/provenance.go b/lib/github/provenance.go index 46faa76a..0f4319ae 100644 --- a/lib/github/provenance.go +++ b/lib/github/provenance.go @@ -38,7 +38,7 @@ 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( + intoto.WithInvocation( RecipeType, e.Context.Workflow, nil, diff --git a/lib/github/provenance_test.go b/lib/github/provenance_test.go index 062d7177..6e65a17a 100644 --- a/lib/github/provenance_test.go +++ b/lib/github/provenance_test.go @@ -276,7 +276,7 @@ func TestGenerateProvenance(t *testing.T) { 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) { @@ -353,7 +353,7 @@ func TestGenerateProvenanceFromGitHubRelease(t *testing.T) { assert.Equal(fmt.Sprintf("%s%s", repoURL, github.HostedIDSuffix), predicate.Builder.ID) assertMetadata(assert, predicate.Metadata, ghContext, repoURL) - assertRecipe(assert, predicate.Recipe) + assertInvocation(assert, predicate.Invocation) stmtPath := path.Join(artifactPath, "build.provenance") @@ -393,7 +393,7 @@ func TestGenerateProvenanceFromGitHubReleaseErrors(t *testing.T) { assert.Nil(stmt) } -func assertRecipe(assert *assert.Assertions, recipe intoto.Recipe) { +func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) { assert.Equal(github.RecipeType, recipe.Type) assert.Equal(0, recipe.DefinedInMaterial) assert.Equal("", recipe.EntryPoint) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index dd72bfac..23adbdb3 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -68,10 +68,10 @@ 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(predicateType string, entryPoint string, environment json.RawMessage, arguments json.RawMessage, materials []Item) StatementOption { return func(s *Statement) { - s.Predicate.Recipe = Recipe{ + s.Predicate.Invocation = Invocation{ Type: predicateType, EntryPoint: entryPoint, Arguments: arguments, @@ -108,10 +108,10 @@ 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"` + Metadata `json:"metadata"` + Invocation `json:"invocation"` + Materials []Item `json:"materials"` } // Builder Identifies the entity that executed the recipe, which is trusted to have correctly performed the operation and populated this provenance. @@ -133,8 +133,8 @@ type Metadata struct { BuildFinishedOn string `json:"buildFinishedOn"` } -// 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 { +// 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 { Type string `json:"type"` DefinedInMaterial int `json:"definedInMaterial"` EntryPoint string `json:"entryPoint"` diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index ae7ed378..f83212b4 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -62,7 +62,7 @@ func TestSLSAProvenanceStatement(t *testing.T) { stmt = SLSAProvenanceStatement( WithSubject(make([]Subject, 1)), WithBuilder(builderID), - WithRecipe( + WithInvocation( recipeType, "CI workflow", nil, @@ -70,14 +70,14 @@ func TestSLSAProvenanceStatement(t *testing.T) { provenanceActionMaterial, ), ) - r := stmt.Predicate.Recipe + i := stmt.Predicate.Invocation assert.Equal(SlsaPredicateType, stmt.PredicateType) assert.Equal(StatementType, stmt.Type) assert.Len(stmt.Subject, 1) assert.Equal(builderID, stmt.Predicate.Builder.ID) - assert.Equal(recipeType, r.Type) - assert.Equal("CI workflow", r.EntryPoint) - assert.Nil(r.Arguments) - assert.Equal(0, r.DefinedInMaterial) + assert.Equal(recipeType, i.Type) + assert.Equal("CI workflow", i.EntryPoint) + assert.Nil(i.Arguments) + assert.Equal(0, i.DefinedInMaterial) assert.Equal(provenanceActionMaterial, stmt.Predicate.Materials) } From 10c44b61f2dcac1620b1725f3cafe20073f4d07d Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Tue, 9 Nov 2021 17:01:32 +0100 Subject: [PATCH 02/10] Move recipe.type one level up as buildType Signed-off-by: Marco Franssen --- lib/github/github.go | 4 ++-- lib/github/provenance.go | 2 +- lib/github/provenance_test.go | 3 ++- lib/intoto/intoto.go | 6 +++--- lib/intoto/intoto_test.go | 6 +++--- 5 files changed, 11 insertions(+), 10 deletions(-) diff --git a/lib/github/github.go b/lib/github/github.go index c695599e..ca5146cd 100644 --- a/lib/github/github.go +++ b/lib/github/github.go @@ -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" diff --git a/lib/github/provenance.go b/lib/github/provenance.go index 0f4319ae..b769edc1 100644 --- a/lib/github/provenance.go +++ b/lib/github/provenance.go @@ -39,7 +39,7 @@ func (e *Environment) GenerateProvenanceStatement(ctx context.Context, artifactP // NOTE: This is inexact as multiple workflows in a repo can have the same name. // See https://github.com/github/feedback/discussions/4188 intoto.WithInvocation( - RecipeType, + BuildType, e.Context.Workflow, nil, event.Inputs, diff --git a/lib/github/provenance_test.go b/lib/github/provenance_test.go index 6e65a17a..34f6532c 100644 --- a/lib/github/provenance_test.go +++ b/lib/github/provenance_test.go @@ -271,6 +271,7 @@ 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) @@ -351,6 +352,7 @@ 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) assertInvocation(assert, predicate.Invocation) @@ -394,7 +396,6 @@ func TestGenerateProvenanceFromGitHubReleaseErrors(t *testing.T) { } func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) { - assert.Equal(github.RecipeType, recipe.Type) assert.Equal(0, recipe.DefinedInMaterial) assert.Equal("", recipe.EntryPoint) assert.Nil(recipe.Environment) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index 23adbdb3..9b1641ed 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -69,10 +69,10 @@ func WithMetadata(buildInvocationID string) StatementOption { } // WithInvocation sets the Predicate Invocation and Materials -func WithInvocation(predicateType string, entryPoint string, environment json.RawMessage, arguments json.RawMessage, materials []Item) StatementOption { +func WithInvocation(buildType, entryPoint string, environment json.RawMessage, arguments json.RawMessage, materials []Item) StatementOption { return func(s *Statement) { + s.Predicate.BuildType = buildType s.Predicate.Invocation = Invocation{ - Type: predicateType, EntryPoint: entryPoint, Arguments: arguments, // Subject to change and simplify https://github.com/slsa-framework/slsa/issues/178 @@ -109,6 +109,7 @@ 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"` + BuildType string `json:"buildType"` Metadata `json:"metadata"` Invocation `json:"invocation"` Materials []Item `json:"materials"` @@ -135,7 +136,6 @@ type Metadata struct { // 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 { - Type string `json:"type"` DefinedInMaterial int `json:"definedInMaterial"` EntryPoint string `json:"entryPoint"` Arguments json.RawMessage `json:"arguments"` diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index f83212b4..849c10b1 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -13,7 +13,7 @@ func TestSLSAProvenanceStatement(t *testing.T) { repoURI := "https://github.com/philips-labs/slsa-provenance-action" builderID := repoURI + "/Attestations/GitHubHostedActions@v1" buildInvocationID := repoURI + "/actions/runs/123498765" - recipeType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" + buildType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" stmt := SLSAProvenanceStatement() assert.Equal(SlsaPredicateType, stmt.PredicateType) @@ -63,7 +63,7 @@ func TestSLSAProvenanceStatement(t *testing.T) { WithSubject(make([]Subject, 1)), WithBuilder(builderID), WithInvocation( - recipeType, + buildType, "CI workflow", nil, nil, @@ -75,7 +75,7 @@ func TestSLSAProvenanceStatement(t *testing.T) { assert.Equal(StatementType, stmt.Type) assert.Len(stmt.Subject, 1) assert.Equal(builderID, stmt.Predicate.Builder.ID) - assert.Equal(recipeType, i.Type) + assert.Equal(buildType, stmt.Predicate.BuildType) assert.Equal("CI workflow", i.EntryPoint) assert.Nil(i.Arguments) assert.Equal(0, i.DefinedInMaterial) From af780dc824f258fe22a08e626e95e43891c97502 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Tue, 9 Nov 2021 17:28:53 +0100 Subject: [PATCH 03/10] Update example_provenance.json to slsa 0.2 spec Signed-off-by: Marco Franssen --- .github/test_resource/example_provenance.json | 86 ++++++++++--------- 1 file changed, 46 insertions(+), 40 deletions(-) diff --git a/.github/test_resource/example_provenance.json b/.github/test_resource/example_provenance.json index eee6a276..d9dfaeb3 100644 --- a/.github/test_resource/example_provenance.json +++ b/.github/test_resource/example_provenance.json @@ -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" + } } -} \ No newline at end of file + ], + "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" + } + } + ] + } +} From 19073a9d18874d126a2742334d6502ab90f23b47 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 11:38:14 +0100 Subject: [PATCH 04/10] Refactor invocation entrypoint to configSource Signed-off-by: Marco Franssen --- .github/workflows/ci.yaml | 2 +- lib/github/provenance_test.go | 2 +- lib/intoto/intoto.go | 16 +++++-- lib/intoto/intoto_test.go | 78 +++++++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 7dad33e0..f366cb29 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -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 diff --git a/lib/github/provenance_test.go b/lib/github/provenance_test.go index 34f6532c..606d8027 100644 --- a/lib/github/provenance_test.go +++ b/lib/github/provenance_test.go @@ -397,7 +397,7 @@ func TestGenerateProvenanceFromGitHubReleaseErrors(t *testing.T) { func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) { assert.Equal(0, recipe.DefinedInMaterial) - assert.Equal("", recipe.EntryPoint) + assert.Equal("", recipe.ConfigSource.EntryPoint) assert.Nil(recipe.Environment) assert.Nil(recipe.Arguments) } diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index 9b1641ed..c6b3eeed 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -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" ) @@ -73,8 +73,10 @@ func WithInvocation(buildType, entryPoint string, environment json.RawMessage, a return func(s *Statement) { s.Predicate.BuildType = buildType s.Predicate.Invocation = Invocation{ - EntryPoint: entryPoint, - Arguments: arguments, + ConfigSource: ConfigSource{ + 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. @@ -137,11 +139,17 @@ type Metadata struct { // 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 { DefinedInMaterial int `json:"definedInMaterial"` - EntryPoint string `json:"entryPoint"` + ConfigSource ConfigSource `json:"configSource"` 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"` +} + // Completeness Indicates that the builder claims certain fields in this message to be complete. type Completeness struct { Arguments bool `json:"arguments"` diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index 849c10b1..f1c0acd4 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -1,6 +1,8 @@ package intoto import ( + "encoding/json" + "fmt" "testing" "time" @@ -64,20 +66,90 @@ func TestSLSAProvenanceStatement(t *testing.T) { WithBuilder(builderID), WithInvocation( buildType, - "CI workflow", + "ci.yaml:build", nil, nil, provenanceActionMaterial, ), ) + assertStatement(assert, stmt, builderID, buildType, provenanceActionMaterial) +} + +func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buildType string, material []Item) { i := stmt.Predicate.Invocation assert.Equal(SlsaPredicateType, stmt.PredicateType) assert.Equal(StatementType, stmt.Type) assert.Len(stmt.Subject, 1) assert.Equal(builderID, stmt.Predicate.Builder.ID) assert.Equal(buildType, stmt.Predicate.BuildType) - assert.Equal("CI workflow", i.EntryPoint) + assert.Equal("ci.yaml:build", i.ConfigSource.EntryPoint) assert.Nil(i.Arguments) assert.Equal(0, i.DefinedInMaterial) - assert.Equal(provenanceActionMaterial, stmt.Predicate.Materials) + assert.Equal(material, stmt.Predicate.Materials) +} + +func TestSLSAProvenanceStatementJSON(t *testing.T) { + assert := assert.New(t) + + builderID := "https://github.com/philips-labs/slsa-provenance-action/Attestations/GitHubHostedActions@v1" + buildType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" + materialJSON := `[ + { + "uri": "git+https://github.com/philips-labs/slsa-provenance-action", + "digest": { + "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" + } + } + ]` + var material []Item + err := json.Unmarshal([]byte(materialJSON), &material) + assert.NoError(err) + + jsonStatement := fmt.Sprintf(`{ + "_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": "%s" + }, + "buildType": "%s", + "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": %s + } + } +`, builderID, buildType, materialJSON) + + var stmt Statement + err = json.Unmarshal([]byte(jsonStatement), &stmt) + assert.NoError(err) + assertStatement(assert, &stmt, builderID, buildType, material) } From 5e82c3012ceb71cd1e5f02ba85514e1d7d4c1426 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 11:45:10 +0100 Subject: [PATCH 05/10] Add URI and Digest to ConfigSource Signed-off-by: Marco Franssen --- lib/intoto/intoto.go | 6 +++++- lib/intoto/intoto_test.go | 8 +++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index c6b3eeed..5ade7207 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -75,6 +75,8 @@ func WithInvocation(buildType, entryPoint string, environment json.RawMessage, a s.Predicate.Invocation = Invocation{ ConfigSource: ConfigSource{ EntryPoint: entryPoint, + URI: materials[0].URI, + Digest: materials[0].Digest, }, Arguments: arguments, // Subject to change and simplify https://github.com/slsa-framework/slsa/issues/178 @@ -147,7 +149,9 @@ type Invocation struct { // 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"` + 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. diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index f1c0acd4..25027df5 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -82,12 +82,18 @@ func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buil assert.Len(stmt.Subject, 1) assert.Equal(builderID, stmt.Predicate.Builder.ID) assert.Equal(buildType, stmt.Predicate.BuildType) - assert.Equal("ci.yaml:build", i.ConfigSource.EntryPoint) + assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) assert.Nil(i.Arguments) assert.Equal(0, i.DefinedInMaterial) assert.Equal(material, stmt.Predicate.Materials) } +func assertConfigSource(assert *assert.Assertions, cs ConfigSource, materials []Item) { + assert.Equal("ci.yaml:build", cs.EntryPoint) + assert.Equal(materials[0].URI, cs.URI) + assert.Equal(materials[0].Digest, cs.Digest) +} + func TestSLSAProvenanceStatementJSON(t *testing.T) { assert := assert.New(t) From b79087acb30f3f71e0a031c7596595778a00fdc2 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 12:31:52 +0100 Subject: [PATCH 06/10] Refactor arguments to parameters Signed-off-by: Marco Franssen --- lib/github/provenance_test.go | 4 ++-- lib/intoto/intoto.go | 10 +++++----- lib/intoto/intoto_test.go | 15 ++++++++------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/lib/github/provenance_test.go b/lib/github/provenance_test.go index 606d8027..ebfb547e 100644 --- a/lib/github/provenance_test.go +++ b/lib/github/provenance_test.go @@ -399,7 +399,7 @@ func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) { assert.Equal(0, recipe.DefinedInMaterial) 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) { @@ -407,7 +407,7 @@ func assertMetadata(assert *assert.Assertions, meta intoto.Metadata, gh github.C 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) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index 5ade7207..ce0af0af 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -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, }, @@ -69,7 +69,7 @@ func WithMetadata(buildInvocationID string) StatementOption { } // WithInvocation sets the Predicate Invocation and Materials -func WithInvocation(buildType, entryPoint string, environment json.RawMessage, arguments json.RawMessage, materials []Item) StatementOption { +func WithInvocation(buildType, entryPoint string, environment json.RawMessage, parameters json.RawMessage, materials []Item) StatementOption { return func(s *Statement) { s.Predicate.BuildType = buildType s.Predicate.Invocation = Invocation{ @@ -78,7 +78,7 @@ func WithInvocation(buildType, entryPoint string, environment json.RawMessage, a URI: materials[0].URI, Digest: materials[0].Digest, }, - Arguments: arguments, + Parameters: parameters, // 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. @@ -142,7 +142,7 @@ type Metadata struct { type Invocation struct { DefinedInMaterial int `json:"definedInMaterial"` ConfigSource ConfigSource `json:"configSource"` - Arguments json.RawMessage `json:"arguments"` + Parameters json.RawMessage `json:"parameters"` Environment json.RawMessage `json:"environment"` } @@ -156,7 +156,7 @@ type ConfigSource struct { // 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"` } diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index 25027df5..85e4db35 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -51,7 +51,7 @@ func TestSLSAProvenanceStatement(t *testing.T) { bft, err := time.Parse(time.RFC3339, m.BuildFinishedOn) assert.NoError(err) assert.WithinDuration(time.Now().UTC(), bft, 1200*time.Millisecond) - assert.Equal(Completeness{Arguments: true, Environment: false, Materials: false}, stmt.Predicate.Metadata.Completeness) + assert.Equal(Completeness{Parameters: true, Environment: false, Materials: false}, stmt.Predicate.Metadata.Completeness) assert.False(m.Reproducible) provenanceActionMaterial := []Item{ @@ -72,10 +72,10 @@ func TestSLSAProvenanceStatement(t *testing.T) { provenanceActionMaterial, ), ) - assertStatement(assert, stmt, builderID, buildType, provenanceActionMaterial) + assertStatement(assert, stmt, builderID, buildType, provenanceActionMaterial, nil) } -func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buildType string, material []Item) { +func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buildType string, material []Item, parameters json.RawMessage) { i := stmt.Predicate.Invocation assert.Equal(SlsaPredicateType, stmt.PredicateType) assert.Equal(StatementType, stmt.Type) @@ -83,7 +83,7 @@ func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buil assert.Equal(builderID, stmt.Predicate.Builder.ID) assert.Equal(buildType, stmt.Predicate.BuildType) assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) - assert.Nil(i.Arguments) + assert.Equal(parameters, i.Parameters) assert.Equal(0, i.DefinedInMaterial) assert.Equal(material, stmt.Predicate.Materials) } @@ -107,6 +107,7 @@ func TestSLSAProvenanceStatementJSON(t *testing.T) { } } ]` + parametersJSON := `{ "inputs": { "skip_integration": true } }` var material []Item err := json.Unmarshal([]byte(materialJSON), &material) assert.NoError(err) @@ -135,7 +136,7 @@ func TestSLSAProvenanceStatementJSON(t *testing.T) { "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" } }, - "parameters": null, + "parameters": %s, "environment": null }, "buildConfig": null, @@ -152,10 +153,10 @@ func TestSLSAProvenanceStatementJSON(t *testing.T) { "materials": %s } } -`, builderID, buildType, materialJSON) +`, builderID, buildType, parametersJSON, materialJSON) var stmt Statement err = json.Unmarshal([]byte(jsonStatement), &stmt) assert.NoError(err) - assertStatement(assert, &stmt, builderID, buildType, material) + assertStatement(assert, &stmt, builderID, buildType, material, []byte(parametersJSON)) } From 64422885bcbfae5502cfd5c86f4b358a74f4a999 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 12:47:27 +0100 Subject: [PATCH 07/10] Add BuildConfig to predicate structure Signed-off-by: Marco Franssen --- lib/intoto/intoto.go | 16 +++++++++++----- lib/intoto/intoto_test.go | 1 + 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index ce0af0af..2741bc4b 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -112,11 +112,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"` - BuildType string `json:"buildType"` - Metadata `json:"metadata"` - Invocation `json:"invocation"` - 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. diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index 85e4db35..74fc5a2f 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -83,6 +83,7 @@ func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buil assert.Equal(builderID, stmt.Predicate.Builder.ID) assert.Equal(buildType, stmt.Predicate.BuildType) assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) + assert.Nil(stmt.Predicate.BuildConfig) assert.Equal(parameters, i.Parameters) assert.Equal(0, i.DefinedInMaterial) assert.Equal(material, stmt.Predicate.Materials) From 968662e5969c9201267c88b597bebc719015d175 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 12:51:31 +0100 Subject: [PATCH 08/10] Remove definedInMaterial from invocation Signed-off-by: Marco Franssen --- lib/github/provenance_test.go | 1 - lib/intoto/intoto.go | 16 +++++----------- lib/intoto/intoto_test.go | 1 - 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/lib/github/provenance_test.go b/lib/github/provenance_test.go index ebfb547e..6e7f670f 100644 --- a/lib/github/provenance_test.go +++ b/lib/github/provenance_test.go @@ -396,7 +396,6 @@ func TestGenerateProvenanceFromGitHubReleaseErrors(t *testing.T) { } func assertInvocation(assert *assert.Assertions, recipe intoto.Invocation) { - assert.Equal(0, recipe.DefinedInMaterial) assert.Equal("", recipe.ConfigSource.EntryPoint) assert.Nil(recipe.Environment) assert.Nil(recipe.Parameters) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index 2741bc4b..0b2e2d06 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -78,13 +78,8 @@ func WithInvocation(buildType, entryPoint string, environment json.RawMessage, p URI: materials[0].URI, Digest: materials[0].Digest, }, - Parameters: parameters, - // 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, + Parameters: parameters, + Environment: environment, } s.Predicate.Materials = append(s.Predicate.Materials, materials...) } @@ -146,10 +141,9 @@ type Metadata struct { // 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 { - DefinedInMaterial int `json:"definedInMaterial"` - ConfigSource ConfigSource `json:"configSource"` - Parameters json.RawMessage `json:"parameters"` - Environment json.RawMessage `json:"environment"` + ConfigSource ConfigSource `json:"configSource"` + Parameters json.RawMessage `json:"parameters"` + Environment json.RawMessage `json:"environment"` } // ConfigSource Describes where the config file that kicked off the build came from. diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index 74fc5a2f..2564eb77 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -85,7 +85,6 @@ func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buil assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) assert.Nil(stmt.Predicate.BuildConfig) assert.Equal(parameters, i.Parameters) - assert.Equal(0, i.DefinedInMaterial) assert.Equal(material, stmt.Predicate.Materials) } From 4d1028ae9b467a5a621dc948f98e91dc000af6c5 Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 13:34:34 +0100 Subject: [PATCH 09/10] Add test to verify code is producing the correct JSON Signed-off-by: Marco Franssen --- lib/intoto/intoto.go | 4 +- lib/intoto/intoto_test.go | 90 +++++++++++++++++++++++---------------- 2 files changed, 55 insertions(+), 39 deletions(-) diff --git a/lib/intoto/intoto.go b/lib/intoto/intoto.go index 0b2e2d06..6a82f76e 100644 --- a/lib/intoto/intoto.go +++ b/lib/intoto/intoto.go @@ -133,10 +133,10 @@ 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). diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index 2564eb77..cee99e00 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -100,63 +100,79 @@ func TestSLSAProvenanceStatementJSON(t *testing.T) { builderID := "https://github.com/philips-labs/slsa-provenance-action/Attestations/GitHubHostedActions@v1" buildType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" materialJSON := `[ - { - "uri": "git+https://github.com/philips-labs/slsa-provenance-action", - "digest": { - "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" - } - } - ]` - parametersJSON := `{ "inputs": { "skip_integration": true } }` + { + "uri": "git+https://github.com/philips-labs/slsa-provenance-action", + "digest": { + "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" + } + } + ]` + parametersJSON := `{ + "inputs": { + "skip_integration": true + } + }` + buildFinishedOn := time.Now().UTC().Format(time.RFC3339) + var material []Item err := json.Unmarshal([]byte(materialJSON), &material) assert.NoError(err) jsonStatement := fmt.Sprintf(`{ - "_type": "https://in-toto.io/Statement/v0.1", - "subject": [ - { + "_type": "https://in-toto.io/Statement/v0.1", + "subject": [ + { "name": "salsa.txt", "digest": { - "sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993" + "sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993" } - } - ], - "predicateType": "https://slsa.dev/provenance/v0.2", - "predicate": { - "builder": { + } + ], + "predicateType": "https://slsa.dev/provenance/v0.2", + "predicate": { + "builder": { "id": "%s" - }, - "buildType": "%s", - "invocation": { + }, + "buildType": "%s", + "invocation": { "configSource": { - "entryPoint": "ci.yaml:build", - "uri": "git+https://github.com/philips-labs/slsa-provenance-action", - "digest": { - "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" - } + "entryPoint": "ci.yaml:build", + "uri": "git+https://github.com/philips-labs/slsa-provenance-action", + "digest": { + "sha1": "a3bc1c27230caa1cc3c27961f7e9cab43cd208dc" + } }, "parameters": %s, "environment": null - }, - "buildConfig": null, - "metadata": { + }, + "metadata": { "buildInvocationId": "https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967", - "buildFinishedOn": "2021-10-04T11:08:34Z", + "buildFinishedOn": "%s", "completeness": { - "parameters": true, - "environment": false, - "materials": false + "parameters": true, + "environment": false, + "materials": false }, "reproducible": false - }, - "materials": %s - } - } -`, builderID, buildType, parametersJSON, materialJSON) + }, + "materials": %s + } +}`, builderID, buildType, parametersJSON, buildFinishedOn, materialJSON) var stmt Statement err = json.Unmarshal([]byte(jsonStatement), &stmt) assert.NoError(err) assertStatement(assert, &stmt, builderID, buildType, material, []byte(parametersJSON)) + + newStmt := SLSAProvenanceStatement( + WithSubject([]Subject{{Name: "salsa.txt", Digest: DigestSet{"sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993"}}}), + WithBuilder(builderID), + WithMetadata("https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967"), + WithInvocation(buildType, "ci.yaml:build", nil, []byte(parametersJSON), material), + ) + + newStmtJSON, err := json.MarshalIndent(newStmt, "", "\t") + assert.NoError(err) + + assert.Equal(jsonStatement, string(newStmtJSON)) } From 9086b3141ca81fdb66a9b37f3669ae9d8971fffd Mon Sep 17 00:00:00 2001 From: Marco Franssen Date: Wed, 10 Nov 2021 14:29:15 +0100 Subject: [PATCH 10/10] Add assertions for metadata Co-authored-by: Brend Smits Signed-off-by: Marco Franssen --- lib/intoto/intoto_test.go | 68 +++++++++++++++++++++++---------------- 1 file changed, 41 insertions(+), 27 deletions(-) diff --git a/lib/intoto/intoto_test.go b/lib/intoto/intoto_test.go index cee99e00..9a4551a0 100644 --- a/lib/intoto/intoto_test.go +++ b/lib/intoto/intoto_test.go @@ -9,14 +9,16 @@ import ( "github.com/stretchr/testify/assert" ) +const ( + repoURI = "https://github.com/philips-labs/slsa-provenance-action" + builderID = repoURI + "/Attestations/GitHubHostedActions@v1" + buildInvocationID = repoURI + "/actions/runs/123498765" + buildType = "https://github.com/Attestations/GitHubActionsWorkflow@v1" +) + func TestSLSAProvenanceStatement(t *testing.T) { assert := assert.New(t) - repoURI := "https://github.com/philips-labs/slsa-provenance-action" - builderID := repoURI + "/Attestations/GitHubHostedActions@v1" - buildInvocationID := repoURI + "/actions/runs/123498765" - buildType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" - stmt := SLSAProvenanceStatement() assert.Equal(SlsaPredicateType, stmt.PredicateType) assert.Equal(StatementType, stmt.Type) @@ -62,8 +64,9 @@ func TestSLSAProvenanceStatement(t *testing.T) { } stmt = SLSAProvenanceStatement( - WithSubject(make([]Subject, 1)), + WithSubject([]Subject{{Name: "salsa.txt", Digest: DigestSet{"sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993"}}}), WithBuilder(builderID), + WithMetadata("https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967"), WithInvocation( buildType, "ci.yaml:build", @@ -75,30 +78,9 @@ func TestSLSAProvenanceStatement(t *testing.T) { assertStatement(assert, stmt, builderID, buildType, provenanceActionMaterial, nil) } -func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buildType string, material []Item, parameters json.RawMessage) { - i := stmt.Predicate.Invocation - assert.Equal(SlsaPredicateType, stmt.PredicateType) - assert.Equal(StatementType, stmt.Type) - assert.Len(stmt.Subject, 1) - assert.Equal(builderID, stmt.Predicate.Builder.ID) - assert.Equal(buildType, stmt.Predicate.BuildType) - assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) - assert.Nil(stmt.Predicate.BuildConfig) - assert.Equal(parameters, i.Parameters) - assert.Equal(material, stmt.Predicate.Materials) -} - -func assertConfigSource(assert *assert.Assertions, cs ConfigSource, materials []Item) { - assert.Equal("ci.yaml:build", cs.EntryPoint) - assert.Equal(materials[0].URI, cs.URI) - assert.Equal(materials[0].Digest, cs.Digest) -} - func TestSLSAProvenanceStatementJSON(t *testing.T) { assert := assert.New(t) - builderID := "https://github.com/philips-labs/slsa-provenance-action/Attestations/GitHubHostedActions@v1" - buildType := "https://github.com/Attestations/GitHubActionsWorkflow@v1" materialJSON := `[ { "uri": "git+https://github.com/philips-labs/slsa-provenance-action", @@ -176,3 +158,35 @@ func TestSLSAProvenanceStatementJSON(t *testing.T) { assert.Equal(jsonStatement, string(newStmtJSON)) } + +func assertStatement(assert *assert.Assertions, stmt *Statement, builderID, buildType string, material []Item, parameters json.RawMessage) { + i := stmt.Predicate.Invocation + assert.Equal(SlsaPredicateType, stmt.PredicateType) + assert.Equal(StatementType, stmt.Type) + assert.Len(stmt.Subject, 1) + assert.Equal(Subject{Name: "salsa.txt", Digest: DigestSet{"sha256": "f8161d035cdf328c7bb124fce192cb90b603f34ca78d73e33b736b4f6bddf993"}}, stmt.Subject[0]) + assert.Equal(builderID, stmt.Predicate.Builder.ID) + assert.Equal(buildType, stmt.Predicate.BuildType) + assertConfigSource(assert, i.ConfigSource, stmt.Predicate.Materials) + assert.Nil(stmt.Predicate.BuildConfig) + assert.Equal(parameters, i.Parameters) + assert.Equal(material, stmt.Predicate.Materials) + assertMetadata(assert, stmt.Predicate.Metadata) +} + +func assertConfigSource(assert *assert.Assertions, cs ConfigSource, materials []Item) { + assert.Equal("ci.yaml:build", cs.EntryPoint) + assert.Equal(materials[0].URI, cs.URI) + assert.Equal(materials[0].Digest, cs.Digest) +} + +func assertMetadata(assert *assert.Assertions, md Metadata) { + assert.Equal("https://github.com/philips-labs/slsa-provenance-action/actions/runs/1303916967", md.BuildInvocationID) + bft, err := time.Parse(time.RFC3339, md.BuildFinishedOn) + assert.NoError(err) + assert.WithinDuration(time.Now().UTC(), bft, 1200*time.Millisecond) + assert.True(md.Completeness.Parameters) + assert.False(md.Completeness.Materials) + assert.False(md.Completeness.Environment) + assert.False(md.Reproducible) +}