From 1270117a67988a7054a8aa47faccf1bea6a8c45c Mon Sep 17 00:00:00 2001 From: Yashvardhan Nanavati Date: Tue, 2 Jul 2024 23:34:53 -0700 Subject: [PATCH] Provide all components as input to policy check ec-cli spawns workers with individual components of the input to perform policy check. This commit adds all input components when performing the policy check as an additional parameter. So each worker has access to all input components during policy check resolves: CVP-4191 Signed-off-by: Yashvardhan Nanavati --- cmd/validate/image.go | 9 +- cmd/validate/image_integration_test.go | 2 +- cmd/validate/image_test.go | 28 +++--- .../application_snapshot_image_test.snap | 96 +++++++++++++++++++ .../application_snapshot_image.go | 8 +- .../application_snapshot_image_test.go | 20 +++- internal/image/validate.go | 4 +- internal/image/validate_test.go | 4 +- 8 files changed, 143 insertions(+), 28 deletions(-) diff --git a/cmd/validate/image.go b/cmd/validate/image.go index bf80df092..7344b7486 100644 --- a/cmd/validate/image.go +++ b/cmd/validate/image.go @@ -42,7 +42,7 @@ import ( validate_utils "github.com/enterprise-contract/ec-cli/internal/validate" ) -type imageValidationFunc func(context.Context, app.SnapshotComponent, policy.Policy, []evaluator.Evaluator, bool) (*output.Output, error) +type imageValidationFunc func(context.Context, app.SnapshotComponent, policy.Policy, []evaluator.Evaluator, bool, []app.SnapshotComponent) (*output.Output, error) var newConftestEvaluator = evaluator.NewConftestEvaluator @@ -315,12 +315,13 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command { // worker is responsible for processing one component at a time from the jobs channel, // and for emitting a corresponding result for the component on the results channel. - worker := func(id int, jobs <-chan app.SnapshotComponent, results chan<- result) { + worker := func(id int, jobs <-chan app.SnapshotComponent, results chan<- result, appComponents []app.SnapshotComponent) { log.Debugf("Starting worker %d", id) for comp := range jobs { log.Debugf("Worker %d got a component %q", id, comp.ContainerImage) ctx := cmd.Context() - out, err := validate(ctx, comp, data.policy, evaluators, data.info) + log.Debugf("Worker %d got appComponents %v", id, appComponents) + out, err := validate(ctx, comp, data.policy, evaluators, data.info, appComponents) res := result{ err: err, component: applicationsnapshot.Component{ @@ -365,7 +366,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command { // Initialize each worker. They will wait patiently until a job is sent to the jobs // channel, or the jobs channel is closed. for i := 0; i <= numWorkers; i++ { - go worker(i, jobs, results) + go worker(i, jobs, results, appComponents) } // Initialize all the jobs. Each worker will pick a job from the channel when the worker // is ready to consume a new job. diff --git a/cmd/validate/image_integration_test.go b/cmd/validate/image_integration_test.go index 951a0d84d..4eb528b83 100644 --- a/cmd/validate/image_integration_test.go +++ b/cmd/validate/image_integration_test.go @@ -76,7 +76,7 @@ func TestEvaluatorLifecycle(t *testing.T) { newConftestEvaluator = evaluator.NewConftestEvaluator }) - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, evaluators []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, evaluators []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { for _, e := range evaluators { _, _, err := e.Evaluate(ctx, []string{}) require.NoError(t, err) diff --git a/cmd/validate/image_test.go b/cmd/validate/image_test.go index 22a0523be..be2e582c5 100644 --- a/cmd/validate/image_test.go +++ b/cmd/validate/image_test.go @@ -256,7 +256,7 @@ func Test_determineInputSpec(t *testing.T) { } func Test_ValidateImageCommand(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -336,7 +336,7 @@ func Test_ValidateImageCommand(t *testing.T) { } func Test_ValidateImageCommandImages(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -458,7 +458,7 @@ func Test_ValidateImageCommandImages(t *testing.T) { func Test_ValidateImageCommandKeyless(t *testing.T) { called := false - validateImageCmd := validateImageCmd(func(_ context.Context, _ app.SnapshotComponent, p policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validateImageCmd := validateImageCmd(func(_ context.Context, _ app.SnapshotComponent, p policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { assert.Equal(t, cosign.Identity{ Issuer: "my-certificate-oidc-issuer", Subject: "my-certificate-identity", @@ -503,7 +503,7 @@ func Test_ValidateImageCommandKeyless(t *testing.T) { } func Test_ValidateImageCommandYAMLPolicyFile(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -621,7 +621,7 @@ spec: } func Test_ValidateImageCommandJSONPolicyFile(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -700,7 +700,7 @@ configuration: } func Test_ValidateImageCommandExtraData(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -825,7 +825,7 @@ spec: } func Test_ValidateImageCommandEmptyPolicyFile(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -893,7 +893,7 @@ func Test_ValidateImageCommandEmptyPolicyFile(t *testing.T) { func Test_ValidateImageErrorLog(t *testing.T) { // TODO: Enhance this test to cover other Error Log messages - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -1057,7 +1057,7 @@ func Test_ValidateErrorCommand(t *testing.T) { } for _, c := range cases { t.Run(c.name, func(t *testing.T) { - validate := func(context.Context, app.SnapshotComponent, policy.Policy, []evaluator.Evaluator, bool) (*output.Output, error) { + validate := func(context.Context, app.SnapshotComponent, policy.Policy, []evaluator.Evaluator, bool, []app.SnapshotComponent) (*output.Output, error) { return nil, errors.New("expected") } @@ -1087,7 +1087,7 @@ func Test_ValidateErrorCommand(t *testing.T) { } func Test_FailureImageAccessibility(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: false, @@ -1158,7 +1158,7 @@ func Test_FailureImageAccessibility(t *testing.T) { } func Test_FailureOutput(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: false, @@ -1227,7 +1227,7 @@ func Test_FailureOutput(t *testing.T) { } func Test_WarningOutput(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -1301,7 +1301,7 @@ func Test_WarningOutput(t *testing.T) { } func Test_FailureImageAccessibilityNonStrict(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, @@ -1369,7 +1369,7 @@ func Test_FailureImageAccessibilityNonStrict(t *testing.T) { } func TestValidateImageCommand_RunE(t *testing.T) { - validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool) (*output.Output, error) { + validate := func(_ context.Context, component app.SnapshotComponent, _ policy.Policy, _ []evaluator.Evaluator, _ bool, _ []app.SnapshotComponent) (*output.Output, error) { return &output.Output{ ImageSignatureCheck: output.VerificationStatus{ Passed: true, diff --git a/internal/evaluation_target/application_snapshot_image/__snapshots__/application_snapshot_image_test.snap b/internal/evaluation_target/application_snapshot_image/__snapshots__/application_snapshot_image_test.snap index 76e2b34b3..0caa9de9e 100755 --- a/internal/evaluation_target/application_snapshot_image/__snapshots__/application_snapshot_image_test.snap +++ b/internal/evaluation_target/application_snapshot_image/__snapshots__/application_snapshot_image_test.snap @@ -13,6 +13,18 @@ [TestWriteInputFile/single_attestations - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": [ { "statement": { @@ -40,6 +52,18 @@ [TestWriteInputFile/multiple_attestations - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": [ { "statement": { @@ -83,6 +107,18 @@ [TestWriteInputFile/image_signatures - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": [ { "statement": { @@ -124,6 +160,18 @@ [TestWriteInputFile/image_config - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": null, "image": { "config": { @@ -139,6 +187,18 @@ [TestWriteInputFile/parent_image_config - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": null, "image": { "parent": { @@ -157,6 +217,18 @@ [TestWriteInputFile/attestation_with_signature - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": [ { "signatures": [ @@ -200,6 +272,18 @@ [TestWriteInputFileMultipleAttestations - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": [ { "statement": { @@ -227,6 +311,18 @@ [TestWriteInputFile/component_with_source - 1] { + "allcomponents": [ + { + "containerImage": "registry.io/repository/image:tag", + "name": "", + "source": {} + }, + { + "containerImage": "registry.io/other-repository/image2:tag", + "name": "", + "source": {} + } + ], "attestations": null, "image": { "ref": "registry.io/repository/image:tag", diff --git a/internal/evaluation_target/application_snapshot_image/application_snapshot_image.go b/internal/evaluation_target/application_snapshot_image/application_snapshot_image.go index 38c20195e..29e7886ec 100644 --- a/internal/evaluation_target/application_snapshot_image/application_snapshot_image.go +++ b/internal/evaluation_target/application_snapshot_image/application_snapshot_image.go @@ -324,12 +324,13 @@ type image struct { } type Input struct { - Attestations []attestationData `json:"attestations"` - Image image `json:"image"` + Attestations []attestationData `json:"attestations"` + Image image `json:"image"` + AllComponents []app.SnapshotComponent `json:"allcomponents"` } // WriteInputFile writes the JSON from the attestations to input.json in a random temp dir -func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string, []byte, error) { +func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context, allcomp []app.SnapshotComponent) (string, []byte, error) { log.Debugf("Attempting to write %d attestations to input file", len(a.attestations)) var attestations []attestationData @@ -349,6 +350,7 @@ func (a *ApplicationSnapshotImage) WriteInputFile(ctx context.Context) (string, Files: a.files, Source: a.component.Source, }, + AllComponents: allcomp, } if a.parentRef != nil { diff --git a/internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go b/internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go index 4a69a4577..eefb78892 100644 --- a/internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go +++ b/internal/evaluation_target/application_snapshot_image/application_snapshot_image_test.go @@ -214,7 +214,15 @@ func TestWriteInputFile(t *testing.T) { t.Run(tt.name, func(t *testing.T) { fs := afero.NewMemMapFs() ctx := utils.WithFS(context.Background(), fs) - inputPath, inputJSON, err := tt.snapshot.WriteInputFile(ctx) + allComps := []app.SnapshotComponent{ + { + ContainerImage: "registry.io/repository/image:tag", + }, + { + ContainerImage: "registry.io/other-repository/image2:tag", + }, + } + inputPath, inputJSON, err := tt.snapshot.WriteInputFile(ctx, allComps) assert.NoError(t, err) assert.NotEmpty(t, inputPath) @@ -241,7 +249,15 @@ func TestWriteInputFileMultipleAttestations(t *testing.T) { fs := afero.NewMemMapFs() ctx := utils.WithFS(context.Background(), fs) - inputPath, inputJSON, err := a.WriteInputFile(ctx) + allComps := []app.SnapshotComponent{ + { + ContainerImage: "registry.io/repository/image:tag", + }, + { + ContainerImage: "registry.io/other-repository/image2:tag", + }, + } + inputPath, inputJSON, err := a.WriteInputFile(ctx, allComps) assert.NoError(t, err) assert.NotEmpty(t, inputPath) diff --git a/internal/image/validate.go b/internal/image/validate.go index 60dd93a8d..aac7b90fc 100644 --- a/internal/image/validate.go +++ b/internal/image/validate.go @@ -35,7 +35,7 @@ import ( // ValidateImage executes the required method calls to evaluate a given policy // against a given image url. -func ValidateImage(ctx context.Context, comp app.SnapshotComponent, p policy.Policy, evaluators []evaluator.Evaluator, detailed bool) (*output.Output, error) { +func ValidateImage(ctx context.Context, comp app.SnapshotComponent, p policy.Policy, evaluators []evaluator.Evaluator, detailed bool, allcomp []app.SnapshotComponent) (*output.Output, error) { log.Debugf("Validating image %s", comp.ContainerImage) out := &output.Output{ImageURL: comp.ContainerImage, Detailed: detailed, Policy: p} @@ -99,7 +99,7 @@ func ValidateImage(ctx context.Context, comp app.SnapshotComponent, p policy.Pol return out, nil } - inputPath, inputJSON, err := a.WriteInputFile(ctx) + inputPath, inputJSON, err := a.WriteInputFile(ctx, allcomp) if err != nil { log.Debug("Problem writing input files!") return nil, err diff --git a/internal/image/validate_test.go b/internal/image/validate_test.go index f91c6a308..01244f3ce 100644 --- a/internal/image/validate_test.go +++ b/internal/image/validate_test.go @@ -146,7 +146,7 @@ func TestBuiltinChecks(t *testing.T) { client := ecoci.NewClient(ctx) c.setup(client.(*fake.FakeClient)) - actual, err := ValidateImage(ctx, c.component, p, evaluators, false) + actual, err := ValidateImage(ctx, c.component, p, evaluators, false, []app.SnapshotComponent{}) assert.NoError(t, err) assert.Equal(t, c.expectedWarnings, actual.Warnings()) @@ -316,7 +316,7 @@ func TestEvaluatorLifecycle(t *testing.T) { evaluators := []evaluator.Evaluator{e} - _, err = ValidateImage(ctx, component, policy, evaluators, false) + _, err = ValidateImage(ctx, component, policy, evaluators, false, []app.SnapshotComponent{}) require.NoError(t, err) }