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

Only store attestation data that is needed #2207

Merged
merged 1 commit into from
Dec 10, 2024
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
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 @@
}

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 @@
} 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 @@
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 @@
} 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
Loading