Skip to content

Commit ffb8fdf

Browse files
joejstuartzregvart
authored andcommitted
Support imageRef field in volatileConfig
Allows restricting the include/exclude configuration to particular image via `imageRef` field in `volatileConfig`. Reference: EC-631
1 parent e1cc4f5 commit ffb8fdf

19 files changed

Lines changed: 637 additions & 124 deletions

acceptance/kubernetes/stub/stub.go

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"errors"
2222
"fmt"
2323
"os"
24+
"regexp"
2425
"strings"
2526

2627
"k8s.io/client-go/tools/clientcmd"
@@ -46,12 +47,56 @@ func (s stubCluster) CreateNamespace(ctx context.Context) (context.Context, erro
4647
return ctx, nil
4748
}
4849

50+
func expandSpecification(ctx context.Context, specification string) (string, error) {
51+
vars := make(map[string]string)
52+
if registry.IsRunning(ctx) {
53+
digests, err := registry.AllDigests(ctx)
54+
if err != nil {
55+
return "", err
56+
}
57+
58+
for repositoryAndTag, digest := range digests {
59+
vars[fmt.Sprintf("REGISTRY_%s_DIGEST", repositoryAndTag)] = digest
60+
}
61+
}
62+
63+
return os.Expand(specification, func(key string) string {
64+
// Handle predefined keys
65+
switch key {
66+
case "GITHOST":
67+
return git.Host(ctx)
68+
case "REGISTRY":
69+
uri, err := registry.Url(ctx)
70+
if err != nil {
71+
panic(err)
72+
}
73+
return uri
74+
}
75+
76+
// Use a regular expression to match and extract dynamic keys
77+
re := regexp.MustCompile(`^REGISTRY_(.+)_DIGEST$`)
78+
matches := re.FindStringSubmatch(key)
79+
if len(matches) == 2 {
80+
if value, ok := vars[key]; ok {
81+
return value
82+
}
83+
}
84+
return ""
85+
}), nil
86+
}
87+
4988
// CreateNamedPolicy stubs a response from the apiserver to fetch a EnterpriseContractPolicy
5089
// custom resource from the `acceptance` namespace with the given name and specification
5190
// the specification part can be templated using ${...} notation and supports
5291
// `GITHOST` and `REGISTRY` variable substitution
5392
func (s stubCluster) CreateNamedPolicy(ctx context.Context, name string, specification string) error {
5493
ns := "acceptance" // TODO: namespace support
94+
95+
specification, err := expandSpecification(ctx, specification)
96+
if err != nil {
97+
return err
98+
}
99+
55100
return wiremock.StubFor(ctx, wiremock.Get(wiremock.URLPathEqualTo(fmt.Sprintf("/apis/appstudio.redhat.com/v1alpha1/namespaces/%s/enterprisecontractpolicies/%s", ns, name))).
56101
WillReturnResponse(wiremock.NewResponse().WithBody(fmt.Sprintf(`{
57102
"apiVersion": "appstudio.redhat.com/v1alpha1",
@@ -61,20 +106,7 @@ func (s stubCluster) CreateNamedPolicy(ctx context.Context, name string, specifi
61106
"namespace": "%s"
62107
},
63108
"spec": %s
64-
}`, name, ns, os.Expand(specification, func(key string) string {
65-
switch key {
66-
case "GITHOST":
67-
return git.Host(ctx)
68-
case "REGISTRY":
69-
uri, err := registry.Url(ctx)
70-
if err != nil {
71-
panic(err)
72-
}
73-
return uri
74-
}
75-
76-
return ""
77-
}))).WithHeaders(map[string]string{"Content-Type": "application/json"}).WithStatus(200)))
109+
}`, name, ns, specification)).WithHeaders(map[string]string{"Content-Type": "application/json"}).WithStatus(200)))
78110
}
79111

80112
// CreateNamedSnapshot stubs a response from the apiserver to fetch a Snapshot

cmd/validate/common_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ type mockEvaluator struct {
4040
mock.Mock
4141
}
4242

43-
func (e *mockEvaluator) Evaluate(ctx context.Context, inputs []string) ([]evaluator.Outcome, evaluator.Data, error) {
44-
args := e.Called(ctx, inputs)
43+
func (e *mockEvaluator) Evaluate(ctx context.Context, target evaluator.EvaluationTarget) ([]evaluator.Outcome, evaluator.Data, error) {
44+
args := e.Called(ctx, target.Inputs)
4545

4646
return args.Get(0).([]evaluator.Outcome), args.Get(1).(evaluator.Data), args.Error(2)
4747
}

cmd/validate/image_integration_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ func TestEvaluatorLifecycle(t *testing.T) {
7878

7979
validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, evaluators []evaluator.Evaluator, _ bool) (*output.Output, error) {
8080
for _, e := range evaluators {
81-
_, _, err := e.Evaluate(ctx, []string{})
81+
_, _, err := e.Evaluate(ctx, evaluator.EvaluationTarget{Inputs: []string{}})
8282
require.NoError(t, err)
8383
}
8484

features/__snapshots__/validate_image.snap

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,107 @@ Error: success criteria not met
808808

809809
---
810810

811+
[policy rule filtering on imageRef:stdout - 1]
812+
{
813+
"success": true,
814+
"components": [
815+
{
816+
"name": "Unnamed",
817+
"containerImage": "${REGISTRY}/acceptance/ec-happy-day@sha256:${REGISTRY_acceptance/ec-happy-day:latest_DIGEST}",
818+
"source": {},
819+
"successes": [
820+
{
821+
"msg": "Pass",
822+
"metadata": {
823+
"code": "builtin.attestation.signature_check"
824+
}
825+
},
826+
{
827+
"msg": "Pass",
828+
"metadata": {
829+
"code": "builtin.attestation.syntax_check"
830+
}
831+
},
832+
{
833+
"msg": "Pass",
834+
"metadata": {
835+
"code": "builtin.image.signature_check"
836+
}
837+
},
838+
{
839+
"msg": "Pass",
840+
"metadata": {
841+
"code": "filtering.always_pass"
842+
}
843+
},
844+
{
845+
"msg": "Pass",
846+
"metadata": {
847+
"code": "filtering.always_pass_with_collection"
848+
}
849+
}
850+
],
851+
"success": true,
852+
"signatures": [
853+
{
854+
"keyid": "",
855+
"sig": "${IMAGE_SIGNATURE_acceptance/ec-happy-day}"
856+
}
857+
],
858+
"attestations": [
859+
{
860+
"type": "https://in-toto.io/Statement/v0.1",
861+
"predicateType": "https://slsa.dev/provenance/v0.2",
862+
"predicateBuildType": "https://tekton.dev/attestations/chains/pipelinerun@v2",
863+
"signatures": [
864+
{
865+
"keyid": "",
866+
"sig": "${ATTESTATION_SIGNATURE_acceptance/ec-happy-day}"
867+
}
868+
]
869+
}
870+
]
871+
}
872+
],
873+
"key": "${known_PUBLIC_KEY_JSON}",
874+
"policy": {
875+
"sources": [
876+
{
877+
"policy": [
878+
"git::https://${GITHOST}/git/happy-day-policy.git"
879+
],
880+
"volatileConfig": {
881+
"exclude": [
882+
{
883+
"value": "filtering.always_fail",
884+
"imageRef": "sha256:${REGISTRY_acceptance/ec-happy-day:latest_DIGEST}"
885+
},
886+
{
887+
"value": "filtering.always_fail_with_collection",
888+
"imageRef": "sha256:${REGISTRY_acceptance/ec-happy-day:latest_DIGEST}"
889+
}
890+
]
891+
}
892+
}
893+
],
894+
"configuration": {
895+
"include": [
896+
"@stamps",
897+
"filtering.always_pass"
898+
]
899+
},
900+
"rekorUrl": "${REKOR}",
901+
"publicKey": "${known_PUBLIC_KEY}"
902+
},
903+
"ec-version": "${EC_VERSION}",
904+
"effective-time": "${TIMESTAMP}"
905+
}
906+
---
907+
908+
[policy rule filtering on imageRef:stderr - 1]
909+
910+
---
911+
811912
[application snapshot reference:stdout - 1]
812913
{
813914
"success": true,

features/validate_image.feature

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,46 @@ Feature: evaluate enterprise contract
411411
When ec command is run with "validate image --image ${REGISTRY}/acceptance/ec-happy-day --policy acceptance/ec-policy --public-key ${known_PUBLIC_KEY} --rekor-url ${REKOR} --show-successes"
412412
Then the exit status should be 0
413413
Then the output should match the snapshot
414+
415+
Scenario: policy rule filtering on imageRef
416+
Given a key pair named "known"
417+
Given an image named "acceptance/ec-happy-day"
418+
Given a valid image signature of "acceptance/ec-happy-day" image signed by the "known" key
419+
Given a valid Rekor entry for image signature of "acceptance/ec-happy-day"
420+
Given a valid attestation of "acceptance/ec-happy-day" signed by the "known" key
421+
Given a valid Rekor entry for attestation of "acceptance/ec-happy-day"
422+
Given a git repository named "happy-day-policy" with
423+
| filtering.rego | examples/filtering.rego |
424+
Given policy configuration named "ec-policy" with specification
425+
"""
426+
{
427+
"configuration": {
428+
"include": ["@stamps", "filtering.always_pass"]
429+
},
430+
"sources": [
431+
{
432+
"volatileConfig": {
433+
"exclude": [
434+
{
435+
"value": "filtering.always_fail",
436+
"imageRef": "sha256:${REGISTRY_acceptance/ec-happy-day:latest_DIGEST}"
437+
},
438+
{
439+
"value": "filtering.always_fail_with_collection",
440+
"imageRef": "sha256:${REGISTRY_acceptance/ec-happy-day:latest_DIGEST}"
441+
}
442+
]
443+
},
444+
"policy": [
445+
"git::https://${GITHOST}/git/happy-day-policy.git"
446+
]
447+
}
448+
]
449+
}
450+
"""
451+
When ec command is run with "validate image --image ${REGISTRY}/acceptance/ec-happy-day --policy acceptance/ec-policy --public-key ${known_PUBLIC_KEY} --rekor-url ${REKOR} --show-successes"
452+
Then the exit status should be 0
453+
Then the output should match the snapshot
414454

415455
Scenario: policy rule filtering for successes
416456
Given a key pair named "known"

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ require (
150150
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
151151
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 // indirect
152152
github.com/dimchansky/utfbom v1.1.1 // indirect
153+
github.com/distribution/reference v0.6.0 // indirect
153154
github.com/docker/cli v25.0.3+incompatible // indirect
154155
github.com/docker/distribution v2.8.3+incompatible // indirect
155156
github.com/docker/docker v25.0.5+incompatible // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7 h1:lxmTCgmHE1G
510510
github.com/digitorus/timestamp v0.0.0-20231217203849-220c5c2851b7/go.mod h1:GvWntX9qiTlOud0WkQ6ewFm0LPy5JUR1Xo0Ngbd1w6Y=
511511
github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U=
512512
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
513+
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
514+
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
513515
github.com/docker/cli v25.0.3+incompatible h1:KLeNs7zws74oFuVhgZQ5ONGZiXUUdgsdy6/EsX/6284=
514516
github.com/docker/cli v25.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
515517
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=

internal/definition/validate.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"github.com/spf13/afero"
2626

2727
"github.com/enterprise-contract/ec-cli/internal/evaluation_target/definition"
28+
"github.com/enterprise-contract/ec-cli/internal/evaluator"
2829
"github.com/enterprise-contract/ec-cli/internal/output"
2930
"github.com/enterprise-contract/ec-cli/internal/policy/source"
3031
"github.com/enterprise-contract/ec-cli/internal/utils"
@@ -45,7 +46,7 @@ func ValidateDefinition(ctx context.Context, fpath string, sources []source.Poli
4546
return nil, err
4647
}
4748

48-
results, _, err := p.Evaluator.Evaluate(ctx, defFiles)
49+
results, _, err := p.Evaluator.Evaluate(ctx, evaluator.EvaluationTarget{Inputs: defFiles})
4950
if err != nil {
5051
log.Debug("Problem running conftest policy check!")
5152
return nil, err

internal/definition/validate_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ type (
4040
badMockEvaluator struct{}
4141
)
4242

43-
func (e mockEvaluator) Evaluate(ctx context.Context, inputs []string) ([]evaluator.Outcome, evaluator.Data, error) {
43+
func (e mockEvaluator) Evaluate(ctx context.Context, target evaluator.EvaluationTarget) ([]evaluator.Outcome, evaluator.Data, error) {
4444
return []evaluator.Outcome{}, nil, nil
4545
}
4646

@@ -51,7 +51,7 @@ func (e mockEvaluator) CapabilitiesPath() string {
5151
return ""
5252
}
5353

54-
func (b badMockEvaluator) Evaluate(ctx context.Context, inputs []string) ([]evaluator.Outcome, evaluator.Data, error) {
54+
func (b badMockEvaluator) Evaluate(ctx context.Context, target evaluator.EvaluationTarget) ([]evaluator.Outcome, evaluator.Data, error) {
5555
return nil, nil, errors.New("Evaluator error")
5656
}
5757

0 commit comments

Comments
 (0)