Skip to content

Commit

Permalink
Only store attestation data that is needed
Browse files Browse the repository at this point in the history
The whole attestation is being stored for each
component that is evaluated, but in most cases
only a subset of the attestation printed.
This change captures only the needed attestation
data based on the output selected at runtime.

https://issues.redhat.com/browse/EC-1026
  • Loading branch information
joejstuart committed Dec 9, 2024
1 parent 27b87f9 commit 3dceef7
Show file tree
Hide file tree
Showing 8 changed files with 167 additions and 70 deletions.
19 changes: 13 additions & 6 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,10 +364,17 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
}

res.component.Signatures = out.Signatures
res.component.Attestations = out.Attestations
// Create a new result object for attestations. The point is to only keep the data that's needed.
// For example, the Statement is only needed when the full attestation is printed.
for _, att := range out.Attestations {
attResult := applicationsnapshot.NewAttestationResult(att)
if containsOutput(data.output, "attestation") {
attResult.Statement = att.Statement()
}
res.component.Attestations = append(res.component.Attestations, attResult)

Check warning on line 374 in cmd/validate/image.go

View check run for this annotation

Codecov / codecov/patch

cmd/validate/image.go#L370-L374

Added lines #L370 - L374 were not covered by tests
}
res.component.ContainerImage = out.ImageURL
res.data = out.Data
res.component.Attestations = out.Attestations
res.policyInput = out.PolicyInput
}
res.component.Success = err == nil && len(res.component.Violations) == 0
Expand Down Expand Up @@ -411,7 +418,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
} else {
components = append(components, r.component)
// evaluator data is duplicated per component, so only collect it once.
if len(evaluatorData) == 0 && containsData(data.output) {
if len(evaluatorData) == 0 && containsOutput(data.output, "data") {
evaluatorData = append(evaluatorData, r.data)
}
manyPolicyInput = append(manyPolicyInput, r.policyInput)
Expand Down Expand Up @@ -542,11 +549,11 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
return cmd
}

// find if the slice contains "data" output
func containsData(data []string) bool {
// find if the slice contains "value" output
func containsOutput(data []string, value string) bool {
for _, item := range data {
newItem := strings.Split(item, "=")
if newItem[0] == "data" {
if newItem[0] == value {
return true
}
}
Expand Down
25 changes: 24 additions & 1 deletion cmd/validate/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1322,7 +1322,30 @@ func TestContainsData(t *testing.T) {
}

for _, test := range tests {
result := containsData(test.input)
result := containsOutput(test.input, "data")
assert.Equal(t, test.expected, result, test.name)
}
}

func TestContainsAttestation(t *testing.T) {
tests := []struct {
input []string
expected bool
name string
}{
{[]string{"attestation"}, true, "Match single attestation"},
{[]string{"attestation=some-file.att"}, true, "Match attestation=some-file.att"},
{[]string{"meta=attestation.json"}, false, "Do not match meta=attestation.json"},
{[]string{"config", "attestation=custom-attestation.yaml"}, true, "Match attestation in slice with multiple values"},
{[]string{"attestation text"}, false, "Do not match attestation text"},
{[]string{"attest"}, false, "Do not match attest"},
{[]string{"attestation123"}, false, "Do not match attestation123"},
{[]string{"attestation="}, true, "Match attestation="},
{[]string{""}, false, "Do not match empty string"},
}

for _, test := range tests {
result := containsOutput(test.input, "attestation")
assert.Equal(t, test.expected, result, test.name)
}
}
2 changes: 1 addition & 1 deletion cmd/validate/input.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ func validateInputCmd(validate InputValidationFunc) *cobra.Command {
} else {
inputs = append(inputs, r.input)
// evaluator data is duplicated per component, so only collect it once.
if len(evaluatorData) == 0 && containsData(data.output) {
if len(evaluatorData) == 0 && containsOutput(data.output, "data") {

Check warning on line 180 in cmd/validate/input.go

View check run for this annotation

Codecov / codecov/patch

cmd/validate/input.go#L180

Added line #L180 was not covered by tests
evaluatorData = append(evaluatorData, r.data)
}
manyPolicyInput = append(manyPolicyInput, r.policyInput)
Expand Down
33 changes: 31 additions & 2 deletions internal/applicationsnapshot/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,43 @@ import (
"encoding/json"

"github.com/in-toto/in-toto-golang/in_toto"

"github.com/enterprise-contract/ec-cli/internal/attestation"
"github.com/enterprise-contract/ec-cli/internal/signature"
)

type SLSAProvenance interface {
attestation.Attestation
PredicateBuildType() string
}

type AttestationResult struct {
Type string `json:"type,omitempty"`
PredicateType string `json:"predicateType,omitempty"`
PredicateBuildType string `json:"predicateBuildType,omitempty"`
Signatures []signature.EntitySignature `json:"signatures,omitempty"`
Statement []byte `json:"-"`
}

func NewAttestationResult(att attestation.Attestation) AttestationResult {
attResult := AttestationResult{
Type: att.Type(),
PredicateType: att.PredicateType(),
Signatures: att.Signatures(),
}
if value, ok := att.(SLSAProvenance); ok {
attResult.PredicateBuildType = value.PredicateBuildType()

}
return attResult
}

func (r *Report) renderAttestations() ([]byte, error) {
byts := make([][]byte, 0, len(r.Components)*2)

for _, c := range r.Components {
for _, a := range c.Attestations {
byts = append(byts, a.Statement())
byts = append(byts, a.Statement)
}
}

Expand All @@ -40,7 +69,7 @@ func (r *Report) attestations() ([]in_toto.Statement, error) {
for _, c := range r.Components {
for _, a := range c.Attestations {
var statement in_toto.Statement
err := json.Unmarshal(a.Statement(), &statement)
err := json.Unmarshal(a.Statement, &statement)
if err != nil {
return []in_toto.Statement{}, nil
}
Expand Down
110 changes: 86 additions & 24 deletions internal/applicationsnapshot/attestation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import (
app "github.com/konflux-ci/application-api/api/v1alpha1"
"github.com/stretchr/testify/assert"

"github.com/enterprise-contract/ec-cli/internal/attestation"
"github.com/enterprise-contract/ec-cli/internal/evaluator"
"github.com/enterprise-contract/ec-cli/internal/signature"
)
Expand Down Expand Up @@ -55,7 +54,7 @@ func TestAttestationReport(t *testing.T) {
SnapshotComponent: app.SnapshotComponent{
ContainerImage: "registry.io/repository/image:tag",
},
Attestations: []attestation.Attestation{
Attestations: []AttestationResult{
att("attestation1"),
},
},
Expand All @@ -68,7 +67,7 @@ func TestAttestationReport(t *testing.T) {
SnapshotComponent: app.SnapshotComponent{
ContainerImage: "registry.io/repository/image1:tag",
},
Attestations: []attestation.Attestation{
Attestations: []AttestationResult{
att("attestation1"),
att("attestation2"),
},
Expand All @@ -77,7 +76,7 @@ func TestAttestationReport(t *testing.T) {
SnapshotComponent: app.SnapshotComponent{
ContainerImage: "registry.io/repository/image2:tag",
},
Attestations: []attestation.Attestation{
Attestations: []AttestationResult{
att("attestation3"),
att("attestation4"),
},
Expand All @@ -91,7 +90,7 @@ func TestAttestationReport(t *testing.T) {
SnapshotComponent: app.SnapshotComponent{
ContainerImage: "registry.io/repository/image1:tag",
},
Attestations: []attestation.Attestation{
Attestations: []AttestationResult{
att("attestation1"),
},
},
Expand All @@ -104,7 +103,7 @@ func TestAttestationReport(t *testing.T) {
SnapshotComponent: app.SnapshotComponent{
ContainerImage: "registry.io/repository/image3:tag",
},
Attestations: []attestation.Attestation{
Attestations: []AttestationResult{
att("attestation2"),
att("attestation3"),
},
Expand Down Expand Up @@ -145,9 +144,9 @@ func TestAttestations(t *testing.T) {
Message: "violation1",
},
},
Attestations: []attestation.Attestation{
provenance{
data: data,
Attestations: []AttestationResult{
{
Statement: data,
},
},
},
Expand All @@ -159,32 +158,95 @@ func TestAttestations(t *testing.T) {
assert.Equal(t, []in_toto.Statement{statement}, att)
}

type mockAttestation struct {
data string
func att(data string) AttestationResult {
return AttestationResult{
Statement: []byte(data),
}
}

type provenance struct {
statement in_toto.Statement
data []byte
signatures []signature.EntitySignature
}

func (a mockAttestation) Type() string {
return "type"
func (p provenance) Type() string {
return "generic-provenance-type"
}

func (a mockAttestation) PredicateType() string {
return "predicateType"
func (p provenance) PredicateType() string {
return "generic-predicate-type"
}

func (a mockAttestation) Statement() []byte {
return []byte(a.data)
func (p provenance) Signatures() []signature.EntitySignature {
return p.signatures
}

func (a mockAttestation) Signatures() []signature.EntitySignature {
return nil
func (p provenance) Statement() []byte {
return p.data
}

func (a mockAttestation) Subject() []in_toto.Subject {
return []in_toto.Subject{}
func (p provenance) Subject() []in_toto.Subject {
return p.statement.Subject
}

func att(data string) attestation.Attestation {
return &mockAttestation{
data: data,
type slsaProvenance struct {
statement in_toto.ProvenanceStatementSLSA02
data []byte
signatures []signature.EntitySignature
}

func (s slsaProvenance) Type() string {
return "slsa-type"
}

func (s slsaProvenance) Statement() []byte {
return s.data
}

func (s slsaProvenance) Subject() []in_toto.Subject {
return s.statement.Subject
}

func (s slsaProvenance) PredicateType() string {
return "slsa-predicate-type"
}

func (s slsaProvenance) Signatures() []signature.EntitySignature {
return s.signatures
}

// PredicateBuildType implements SLSAProvenance
func (s slsaProvenance) PredicateBuildType() string {
return "slsa-build-type"
}

func TestNewAttestationResultWithProvenanceOnly(t *testing.T) {
p := provenance{
statement: in_toto.Statement{},
data: []byte("some data"),
signatures: []signature.EntitySignature{{KeyID: "key1"}},
}

result := NewAttestationResult(p) // p implements attestation.Attestation

assert.Equal(t, "generic-provenance-type", result.Type)
assert.Equal(t, "generic-predicate-type", result.PredicateType)
assert.Len(t, result.Signatures, 1)
assert.Empty(t, result.PredicateBuildType, "expected PredicateBuildType to be empty for non-SLSAProvenance attestation")
}

func TestNewAttestationResultWithSLSAProvenance(t *testing.T) {
s := slsaProvenance{
statement: in_toto.ProvenanceStatementSLSA02{},
data: []byte("some slsa data"),
signatures: []signature.EntitySignature{{KeyID: "key-slsa"}},
}

result := NewAttestationResult(s) // s implements SLSAProvenance

assert.Equal(t, "slsa-type", result.Type)
assert.Equal(t, "slsa-predicate-type", result.PredicateType)
assert.Len(t, result.Signatures, 1)
assert.Equal(t, "slsa-build-type", result.PredicateBuildType, "expected PredicateBuildType to be set for SLSAProvenance attestation")
}
3 changes: 1 addition & 2 deletions internal/applicationsnapshot/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import (
app "github.com/konflux-ci/application-api/api/v1alpha1"
"sigs.k8s.io/yaml"

"github.com/enterprise-contract/ec-cli/internal/attestation"
"github.com/enterprise-contract/ec-cli/internal/evaluator"
"github.com/enterprise-contract/ec-cli/internal/format"
"github.com/enterprise-contract/ec-cli/internal/policy"
Expand All @@ -46,7 +45,7 @@ type Component struct {
Success bool `json:"success"`
SuccessCount int `json:"-"`
Signatures []signature.EntitySignature `json:"signatures,omitempty"`
Attestations []attestation.Attestation `json:"attestations,omitempty"`
Attestations []AttestationResult `json:"attestations,omitempty"`
}

type Report struct {
Expand Down
Loading

0 comments on commit 3dceef7

Please sign in to comment.