Skip to content

Commit

Permalink
Merge pull request #2207 from joejstuart/EC-1026-2
Browse files Browse the repository at this point in the history
Only store attestation data that is needed
  • Loading branch information
joejstuart authored Dec 10, 2024
2 parents 5f30c3f + 3dceef7 commit a0b32fc
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)
}
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") {
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 a0b32fc

Please sign in to comment.