mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 15:50:15 -08:00
392 lines
10 KiB
Go
392 lines
10 KiB
Go
package policy_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
v1 "github.com/google/go-containerregistry/pkg/v1"
|
|
fakei "github.com/google/go-containerregistry/pkg/v1/fake"
|
|
"github.com/google/go-containerregistry/pkg/v1/tarball"
|
|
"github.com/google/go-containerregistry/pkg/v1/types"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/utils/clock"
|
|
fake "k8s.io/utils/clock/testing"
|
|
|
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
"github.com/aquasecurity/trivy/pkg/oci"
|
|
"github.com/aquasecurity/trivy/pkg/policy"
|
|
)
|
|
|
|
type fakeLayer struct {
|
|
v1.Layer
|
|
}
|
|
|
|
func (f fakeLayer) MediaType() (types.MediaType, error) {
|
|
return "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", nil
|
|
}
|
|
|
|
func newFakeLayer(t *testing.T) v1.Layer {
|
|
layer, err := tarball.LayerFromFile("testdata/bundle.tar.gz")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, layer)
|
|
|
|
return fakeLayer{layer}
|
|
}
|
|
|
|
type brokenLayer struct {
|
|
v1.Layer
|
|
}
|
|
|
|
func (b brokenLayer) MediaType() (types.MediaType, error) {
|
|
return "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip", nil
|
|
}
|
|
|
|
func (b brokenLayer) Compressed() (io.ReadCloser, error) {
|
|
return nil, errors.New("compressed error")
|
|
}
|
|
|
|
func newBrokenLayer(t *testing.T) v1.Layer {
|
|
layer, err := tarball.LayerFromFile("testdata/bundle.tar.gz")
|
|
require.NoError(t, err)
|
|
|
|
return brokenLayer{layer}
|
|
}
|
|
|
|
func TestClient_LoadBuiltinPolicies(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cacheDir string
|
|
want []string
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
cacheDir: "testdata/happy",
|
|
want: []string{
|
|
filepath.Join("testdata", "happy", "policy", "content", "kubernetes"),
|
|
filepath.Join("testdata", "happy", "policy", "content", "docker"),
|
|
},
|
|
},
|
|
{
|
|
name: "empty roots",
|
|
cacheDir: "testdata/empty",
|
|
want: []string{
|
|
filepath.Join("testdata", "empty", "policy", "content"),
|
|
},
|
|
},
|
|
{
|
|
name: "broken manifest",
|
|
cacheDir: "testdata/broken",
|
|
want: []string{},
|
|
wantErr: "json decode error",
|
|
},
|
|
{
|
|
name: "no such file",
|
|
cacheDir: "testdata/unknown",
|
|
want: []string{},
|
|
wantErr: "manifest file open error",
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Mock image
|
|
img := new(fakei.FakeImage)
|
|
img.LayersReturns([]v1.Layer{newFakeLayer(t)}, nil)
|
|
img.ManifestReturns(&v1.Manifest{
|
|
Layers: []v1.Descriptor{
|
|
{
|
|
MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip",
|
|
Size: 100,
|
|
Digest: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8",
|
|
},
|
|
Annotations: map[string]string{
|
|
"org.opencontainers.image.title": "bundle.tar.gz",
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
// Mock OCI artifact
|
|
art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img))
|
|
c, err := policy.NewClient(tt.cacheDir, true, "", policy.WithOCIArtifact(art))
|
|
require.NoError(t, err)
|
|
|
|
got, err := c.LoadBuiltinChecks()
|
|
if tt.wantErr != "" {
|
|
require.ErrorContains(t, err, tt.wantErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_NeedsUpdate(t *testing.T) {
|
|
type digestReturns struct {
|
|
h v1.Hash
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
clock clock.Clock
|
|
digestReturns digestReturns
|
|
metadata any
|
|
want bool
|
|
wantErr bool
|
|
}{
|
|
{
|
|
name: "recent download",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
digestReturns: digestReturns{
|
|
h: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
},
|
|
},
|
|
metadata: policy.Metadata{
|
|
Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`,
|
|
DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "same digest",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)),
|
|
digestReturns: digestReturns{
|
|
h: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
},
|
|
},
|
|
metadata: policy.Metadata{
|
|
Digest: `sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d`,
|
|
DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
want: false,
|
|
},
|
|
{
|
|
name: "different digest",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)),
|
|
digestReturns: digestReturns{
|
|
h: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
},
|
|
},
|
|
metadata: policy.Metadata{
|
|
Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`,
|
|
DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "sad: Digest returns an error",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 2, 1, 0, 0, 0, time.UTC)),
|
|
digestReturns: digestReturns{
|
|
err: errors.New("error"),
|
|
},
|
|
metadata: policy.Metadata{
|
|
Digest: `sha256:922e50f14ab484f11ae65540c3d2d76009020213f1027d4331d31141575e5414`,
|
|
DownloadedAt: time.Date(2021, 1, 1, 0, 0, 0, 0, time.UTC),
|
|
},
|
|
want: false,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "sad: non-existent metadata",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
want: true,
|
|
},
|
|
{
|
|
name: "sad: broken metadata",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
metadata: `"foo"`,
|
|
want: true,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
// Set up a temporary directory
|
|
tmpDir := t.TempDir()
|
|
|
|
// Mock image
|
|
img := new(fakei.FakeImage)
|
|
img.LayersReturns([]v1.Layer{newFakeLayer(t)}, nil)
|
|
img.DigestReturns(tt.digestReturns.h, tt.digestReturns.err)
|
|
img.ManifestReturns(&v1.Manifest{
|
|
Layers: []v1.Descriptor{
|
|
{
|
|
MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip",
|
|
Size: 100,
|
|
Digest: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8",
|
|
},
|
|
Annotations: map[string]string{
|
|
"org.opencontainers.image.title": "bundle.tar.gz",
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
// Create a check directory
|
|
err := os.MkdirAll(filepath.Join(tmpDir, "policy"), os.ModePerm)
|
|
require.NoError(t, err)
|
|
|
|
if tt.metadata != nil {
|
|
b, err := json.Marshal(tt.metadata)
|
|
require.NoError(t, err)
|
|
|
|
// Write a metadata file
|
|
metadataPath := filepath.Join(tmpDir, "policy", "metadata.json")
|
|
err = os.WriteFile(metadataPath, b, os.ModePerm)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img))
|
|
c, err := policy.NewClient(tmpDir, true, "", policy.WithOCIArtifact(art), policy.WithClock(tt.clock))
|
|
require.NoError(t, err)
|
|
|
|
// Assert results
|
|
got, err := c.NeedsUpdate(t.Context(), ftypes.RegistryOptions{})
|
|
assert.Equal(t, tt.wantErr, err != nil)
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_DownloadBuiltinPolicies(t *testing.T) {
|
|
type digestReturns struct {
|
|
h v1.Hash
|
|
err error
|
|
}
|
|
type layersReturns struct {
|
|
layers []v1.Layer
|
|
err error
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
clock clock.Clock
|
|
layersReturns layersReturns
|
|
digestReturns digestReturns
|
|
want *policy.Metadata
|
|
wantErr string
|
|
}{
|
|
{
|
|
name: "happy path",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
layersReturns: layersReturns{
|
|
layers: []v1.Layer{newFakeLayer(t)},
|
|
},
|
|
digestReturns: digestReturns{
|
|
h: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
},
|
|
},
|
|
want: &policy.Metadata{
|
|
Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC),
|
|
},
|
|
},
|
|
{
|
|
name: "sad: broken layer",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
layersReturns: layersReturns{
|
|
layers: []v1.Layer{newBrokenLayer(t)},
|
|
},
|
|
digestReturns: digestReturns{
|
|
h: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
},
|
|
},
|
|
wantErr: "compressed error",
|
|
},
|
|
{
|
|
name: "sad: Digest returns an error",
|
|
clock: fake.NewFakeClock(time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC)),
|
|
layersReturns: layersReturns{
|
|
layers: []v1.Layer{newFakeLayer(t)},
|
|
},
|
|
digestReturns: digestReturns{
|
|
err: errors.New("error"),
|
|
},
|
|
want: &policy.Metadata{
|
|
Digest: "sha256:01e033e78bd8a59fa4f4577215e7da06c05e1152526094d8d79d2aa06e98cb9d",
|
|
DownloadedAt: time.Date(2021, 1, 1, 1, 0, 0, 0, time.UTC),
|
|
},
|
|
wantErr: "digest error",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tempDir := t.TempDir()
|
|
|
|
// Mock image
|
|
img := new(fakei.FakeImage)
|
|
img.DigestReturns(tt.digestReturns.h, tt.digestReturns.err)
|
|
img.LayersReturns(tt.layersReturns.layers, tt.layersReturns.err)
|
|
img.ManifestReturns(&v1.Manifest{
|
|
Layers: []v1.Descriptor{
|
|
{
|
|
MediaType: "application/vnd.cncf.openpolicyagent.layer.v1.tar+gzip",
|
|
Size: 100,
|
|
Digest: v1.Hash{
|
|
Algorithm: "sha256",
|
|
Hex: "cba33656188782852f58993f45b68bfb8577f64cdcf02a604e3fc2afbeb5f2d8",
|
|
},
|
|
Annotations: map[string]string{
|
|
"org.opencontainers.image.title": "bundle.tar.gz",
|
|
},
|
|
},
|
|
},
|
|
}, nil)
|
|
|
|
// Mock OCI artifact
|
|
art := oci.NewArtifact("repo", ftypes.RegistryOptions{}, oci.WithImage(img))
|
|
c, err := policy.NewClient(tempDir, true, "", policy.WithClock(tt.clock), policy.WithOCIArtifact(art))
|
|
require.NoError(t, err)
|
|
|
|
err = c.DownloadBuiltinChecks(t.Context(), ftypes.RegistryOptions{})
|
|
if tt.wantErr != "" {
|
|
require.ErrorContains(t, err, tt.wantErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// Assert metadata.json
|
|
metadata := filepath.Join(tempDir, "policy", "metadata.json")
|
|
b, err := os.ReadFile(metadata)
|
|
require.NoError(t, err)
|
|
|
|
got := new(policy.Metadata)
|
|
err = json.Unmarshal(b, got)
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.want, got)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestClient_Clear(t *testing.T) {
|
|
cacheDir := t.TempDir()
|
|
err := os.MkdirAll(filepath.Join(cacheDir, "policy"), 0755)
|
|
require.NoError(t, err)
|
|
|
|
c, err := policy.NewClient(cacheDir, true, "")
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.Clear())
|
|
}
|