Skip to content

Commit

Permalink
EC-802 Include SHA/Image Digest in report output
Browse files Browse the repository at this point in the history
This commit adds functionality to display the SHA/Image digest in the
report output. Speicifically it ensures that for git and oci sources,
the identifying SHA/Image Digests are determined and provided in the
report output, allowing for auditing and reproducibility.

Signed-off-by: robnester-rh <[email protected]>
  • Loading branch information
robnester-rh committed Oct 7, 2024
1 parent a17b9de commit 290f4cc
Show file tree
Hide file tree
Showing 21 changed files with 851 additions and 142 deletions.
4 changes: 4 additions & 0 deletions acceptance/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,10 @@ func setupGitHost(ctx context.Context, vars map[string]string, environment []str
environment = append(environment, fmt.Sprintf("SSL_CERT_FILE=%s", git.CertificatePath(ctx)), "GIT_SSL_NO_VERIFY=true")

vars["GITHOST"] = git.Host(ctx)
latestCommit := git.LatestCommit(ctx)
if latestCommit != "" {
vars["LATEST_COMMIT"] = latestCommit
}
return environment, vars, nil
}

Expand Down
9 changes: 8 additions & 1 deletion acceptance/git/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type gitState struct {
HostAndPort string
RepositoriesDir string
CertificatePath string
LatestCommit string
}

func (g gitState) Key() any {
Expand Down Expand Up @@ -186,6 +187,10 @@ func Host(ctx context.Context) string {
return testenv.FetchState[gitState](ctx).HostAndPort
}

func LatestCommit(ctx context.Context) string {
return testenv.FetchState[gitState](ctx).LatestCommit
}

// CertificatePath returns the path to the self-signed certificate used for TLS
// handshake
func CertificatePath(ctx context.Context) string {
Expand Down Expand Up @@ -270,13 +275,15 @@ func createGitRepository(ctx context.Context, repositoryName string, files *godo
}

// do a `git commit`
_, err = w.Commit("test data", &git.CommitOptions{
h, err := w.Commit("test data", &git.CommitOptions{
Author: &object.Signature{
Name: "Testy McTestface",
Email: "[email protected]",
When: time.Date(1970, time.January, 1, 0, 9, 9, 9, time.UTC), // makes commits deterministic
},
})

state.LatestCommit = h.String()
if err != nil {
return err
}
Expand Down
15 changes: 8 additions & 7 deletions cmd/inspect/inspect_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"testing"

"github.com/enterprise-contract/go-gather/metadata"
fileMetadata "github.com/enterprise-contract/go-gather/metadata/file"
"github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
Expand All @@ -42,7 +43,7 @@ type mockDownloader struct {
func (m *mockDownloader) Download(_ context.Context, dest string, sourceUrl string, showMsg bool) (metadata.Metadata, error) {
args := m.Called(dest, sourceUrl, showMsg)

return nil, args.Error(0)
return args.Get(0).(metadata.Metadata), args.Error(1)
}

func TestFetchSourcesFromPolicy(t *testing.T) {
Expand All @@ -63,9 +64,9 @@ func TestFetchSourcesFromPolicy(t *testing.T) {
}
}

downloader.On("Download", mock.Anything, "one", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "two", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "three", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "one", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)
downloader.On("Download", mock.Anything, "two", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)
downloader.On("Download", mock.Anything, "three", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)

inspectPolicyCmd := inspectPolicyCmd()
cmd := setUpCobra(inspectPolicyCmd)
Expand Down Expand Up @@ -105,9 +106,9 @@ func TestFetchSources(t *testing.T) {
}
}

downloader.On("Download", mock.Anything, "one", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "two", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "three", false).Return(nil).Run(createDir)
downloader.On("Download", mock.Anything, "one", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)
downloader.On("Download", mock.Anything, "two", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)
downloader.On("Download", mock.Anything, "three", false).Return(&fileMetadata.FileMetadata{}, nil).Run(createDir)

inspectPolicyCmd := inspectPolicyCmd()
cmd := setUpCobra(inspectPolicyCmd)
Expand Down
4 changes: 2 additions & 2 deletions cmd/validate/__snapshots__/image_test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
]
},
"policy": [
"quay.io/hacbs-contract/ec-release-policy:latest"
"oci://quay.io/hacbs-contract/ec-release-policy:latest@sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"
]
}
]
Expand Down Expand Up @@ -66,7 +66,7 @@
]
},
"policy": [
"quay.io/hacbs-contract/ec-release-policy:latest"
"oci://quay.io/hacbs-contract/ec-release-policy:latest@sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"
]
}
]
Expand Down
11 changes: 11 additions & 0 deletions cmd/validate/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package validate
import (
"context"

"github.com/enterprise-contract/go-gather/metadata"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -56,6 +57,16 @@ func (e *mockEvaluator) CapabilitiesPath() string {
return args.String(0)
}

type MockDownloader struct {
mock.Mock
}

func (m *MockDownloader) Download(_ context.Context, dest string, sourceUrl string, showMsg bool) (metadata.Metadata, error) {
args := m.Called(dest, sourceUrl, showMsg)

return args.Get(0).(metadata.Metadata), args.Error(1)
}

func setUpCobra(command *cobra.Command) *cobra.Command {
validateCmd := NewValidateCmd()
validateCmd.AddCommand(command)
Expand Down
11 changes: 7 additions & 4 deletions cmd/validate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
}
data.policyConfiguration = policyConfiguration

if p, err := policy.NewPolicy(cmd.Context(), policy.Options{
policyOptions := policy.Options{
EffectiveTime: data.effectiveTime,
Identity: cosign.Identity{
Issuer: data.certificateOIDCIssuer,
Expand All @@ -220,7 +220,11 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
PolicyRef: data.policyConfiguration,
PublicKey: data.publicKey,
RekorURL: data.rekorURL,
}); err != nil {
}

// We're not currently using the policyCache returned from PreProcessPolicy, but we could
// use it to cache the policy for future use.
if p, _, err := policy.PreProcessPolicy(ctx, policyOptions); err != nil {
allErrors = errors.Join(allErrors, err)
} else {
// inject extra variables into rule data per source
Expand Down Expand Up @@ -322,8 +326,7 @@ func validateImageCmd(validate imageValidationFunc) *cobra.Command {
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.spec, data.policy, evaluators, data.info)
out, err := validate(cmd.Context(), comp, data.spec, data.policy, evaluators, data.info)
res := result{
err: err,
component: applicationsnapshot.Component{
Expand Down
14 changes: 10 additions & 4 deletions cmd/validate/image_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"time"

"github.com/enterprise-contract/enterprise-contract-controller/api/v1alpha1"
ociMetadata "github.com/enterprise-contract/go-gather/metadata/oci"
app "github.com/konflux-ci/application-api/api/v1alpha1"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
Expand All @@ -44,15 +45,19 @@ import (
)

func TestEvaluatorLifecycle(t *testing.T) {
noEvaluators := 100

ctx := utils.WithFS(context.Background(), afero.NewMemMapFs())
client := fake.FakeClient{}
commonMockClient(&client)
ctx = oci.WithClient(ctx, &client)

noEvaluators := 100
mdl := MockDownloader{}
downloaderCall := mdl.On("Download", mock.Anything, mock.Anything, false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil).Times(noEvaluators)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

evaluators := make([]*mockEvaluator, 0, noEvaluators)
expectations := make([]*mock.Call, 0, noEvaluators)
expectations := make([]*mock.Call, 0, noEvaluators+1)
expectations = append(expectations, downloaderCall)

for i := 0; i < noEvaluators; i++ {
e := mockEvaluator{}
Expand All @@ -67,7 +72,8 @@ func TestEvaluatorLifecycle(t *testing.T) {
}

newConftestEvaluator = func(_ context.Context, s []source.PolicySource, _ evaluator.ConfigProvider, _ v1alpha1.Source) (evaluator.Evaluator, error) {
idx, err := strconv.Atoi(s[0].PolicyUrl())
// We are splitting this url to get to the index of the evaluator.
idx, err := strconv.Atoi(strings.Split(strings.Split(s[0].PolicyUrl(), "@")[0], "://")[1])
require.NoError(t, err)

return evaluators[idx], nil
Expand Down
38 changes: 34 additions & 4 deletions cmd/validate/image_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,18 +29,21 @@ import (
"time"

hd "github.com/MakeNowJust/heredoc"
ociMetadata "github.com/enterprise-contract/go-gather/metadata/oci"
"github.com/gkampitakis/go-snaps/snaps"
app "github.com/konflux-ci/application-api/api/v1alpha1"
"github.com/sigstore/cosign/v2/pkg/cosign"
log "github.com/sirupsen/logrus"
"github.com/sirupsen/logrus/hooks/test"
"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"

"github.com/enterprise-contract/ec-cli/internal/applicationsnapshot"
"github.com/enterprise-contract/ec-cli/internal/evaluator"
"github.com/enterprise-contract/ec-cli/internal/output"
"github.com/enterprise-contract/ec-cli/internal/policy"
"github.com/enterprise-contract/ec-cli/internal/policy/source"
"github.com/enterprise-contract/ec-cli/internal/utils"
"github.com/enterprise-contract/ec-cli/internal/utils/oci"
"github.com/enterprise-contract/ec-cli/internal/utils/oci/fake"
Expand Down Expand Up @@ -528,6 +531,11 @@ spec:
fs := afero.NewMemMapFs()
ctx := utils.WithFS(context.Background(), fs)
ctx = oci.WithClient(ctx, &client)

mdl := MockDownloader{}
mdl.On("Download", mock.Anything, "quay.io/hacbs-contract/ec-release-policy:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

cmd.SetContext(ctx)

err := afero.WriteFile(fs, "/policy.yaml", []byte(c.config), 0644)
Expand Down Expand Up @@ -568,6 +576,12 @@ func Test_ValidateImageCommandJSONPolicyFile(t *testing.T) {
fs := afero.NewMemMapFs()
ctx := utils.WithFS(context.Background(), fs)
ctx = oci.WithClient(ctx, &client)

mdl := MockDownloader{}
mdl.On("Download", mock.Anything, "registry/policy:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
mdl.On("Download", mock.Anything, "registry/policy-data:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

cmd.SetContext(ctx)

testPolicyJSON := `sources:
Expand Down Expand Up @@ -614,6 +628,11 @@ func Test_ValidateImageCommandExtraData(t *testing.T) {
commonMockClient(&client)
ctx = oci.WithClient(ctx, &client)

mdl := MockDownloader{}
mdl.On("Download", mock.Anything, "registry/policy:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
mdl.On("Download", mock.Anything, "registry/policy-data:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

cmd.SetContext(ctx)

testPolicyJSON := `sources:
Expand Down Expand Up @@ -680,10 +699,10 @@ spec:
assert.NoError(t, err)
assert.JSONEq(t, `{
"data": [
"registry/policy-data:latest"
"oci://registry/policy-data:latest@sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"
],
"policy": [
"registry/policy:latest"
"oci://registry/policy:latest@sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"
],
"ruleData": {
"custom_rule_data":{"prefix_data":["registry1"]},
Expand All @@ -705,6 +724,12 @@ func Test_ValidateImageCommandEmptyPolicyFile(t *testing.T) {
fs := afero.NewMemMapFs()
ctx := utils.WithFS(context.Background(), fs)
ctx = oci.WithClient(ctx, &client)

mdl := MockDownloader{}
mdl.On("Download", mock.Anything, "registry/policy:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
mdl.On("Download", mock.Anything, "registry/policy-data:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

cmd.SetContext(ctx)

err := afero.WriteFile(fs, "/policy.yaml", []byte(nil), 0644)
Expand Down Expand Up @@ -747,13 +772,18 @@ func Test_ValidateImageErrorLog(t *testing.T) {
commonMockClient(&client)
ctx = oci.WithClient(ctx, &client)

mdl := MockDownloader{}
mdl.On("Download", mock.Anything, "oci::registry/policy:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
mdl.On("Download", mock.Anything, "oci::registry/policy-data:latest", false).Return(&ociMetadata.OCIMetadata{Digest: "sha256:da54bca5477bf4e3449bc37de1822888fa0fbb8d89c640218cb31b987374d357"}, nil)
ctx = context.WithValue(ctx, source.DownloaderFuncKey, &mdl)

cmd.SetContext(ctx)

testPolicyJSON := `sources:
- policy:
- "registry/policy:latest"
- "oci::registry/policy:latest"
data:
- "registry/policy-data:latest"
- "oci::registry/policy-data:latest"
config:
include:
- '@minimal'
Expand Down
8 changes: 4 additions & 4 deletions features/__snapshots__/inspect_policy.snap
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Error: Merge error. The 'rule_data' key was found more than once!

[json output:stdout - 1]
{
"git::https://${GITHOST}/git/policy.git": [
"git::https://${GITHOST}/git/policy.git?ref=${LATEST_COMMIT}": [
{
"annotations": {
"custom": {
Expand Down Expand Up @@ -57,7 +57,7 @@ Error: Merge error. The 'rule_data' key was found more than once!
---

[default output:stdout - 1]
# Source: git::https://${GITHOST}/git/policy.git
# Source: git::https://${GITHOST}/git/policy.git?ref=${LATEST_COMMIT}

policy.release.kitty.purr (deny)
https://enterprisecontract.dev/docs/ec-policies/release_policy.html#kitty__purr
Expand Down Expand Up @@ -94,14 +94,14 @@ kitty.purr
---

[sources from ECP:stdout - 1]
# Source: git::https://${GITHOST}/git/policy1.git
# Source: git::https://${GITHOST}/git/policy1.git?ref=8288b21ca5e7d8863efffb47c2bc3eac1274d1ff

policy.release.kitty.purr (deny)
https://enterprisecontract.dev/docs/ec-policies/release_policy.html#kitty__purr
Kittens
Fluffy
--
# Source: git::https://${GITHOST}/git/policy2.git
# Source: git::https://${GITHOST}/git/policy2.git?ref=${LATEST_COMMIT}

main.rejector (deny)
Reject rule
Expand Down
2 changes: 1 addition & 1 deletion features/__snapshots__/track_bundle.snap
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Error: expected "git+https://${GITHOST}/git/tasks.git//task.yaml" to contain the
trusted_tasks:
git+https://${GITHOST}/git/tasks.git//task.yaml:
- effective_on: "${TIMESTAMP}"
ref: 60079661c514e31e542a55aefb8de67bd40597a9
ref: ${LATEST_COMMIT}

---

Expand Down
Loading

0 comments on commit 290f4cc

Please sign in to comment.