diff --git a/pkg/fanal/image/remote.go b/pkg/fanal/image/remote.go index 3927d1177e..83d7e71d09 100644 --- a/pkg/fanal/image/remote.go +++ b/pkg/fanal/image/remote.go @@ -7,6 +7,7 @@ import ( "github.com/google/go-containerregistry/pkg/name" v1 "github.com/google/go-containerregistry/pkg/v1" + "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/remote" @@ -20,6 +21,11 @@ func tryRemote(ctx context.Context, imageName string, ref name.Reference, option if err != nil { return nil, cleanup, err } + // ArtifactType being non-empty indicates this is not a regular container image + // (e.g., Helm charts, WASM modules, or other OCI artifacts) + if desc.ArtifactType != "" { + return nil, cleanup, xerrors.Errorf("unsupported artifact type %q for image %q", desc.ArtifactType, imageName) + } img, err := desc.Image() if err != nil { return nil, cleanup, err diff --git a/pkg/fanal/image/remote_test.go b/pkg/fanal/image/remote_test.go index ededa04da2..0593565158 100644 --- a/pkg/fanal/image/remote_test.go +++ b/pkg/fanal/image/remote_test.go @@ -1,11 +1,21 @@ package image import ( + "io" + "log" + "net/http/httptest" + "net/url" "testing" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/random" + "github.com/google/go-containerregistry/pkg/v1/remote" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func Test_implicitReference_TagName(t *testing.T) { @@ -82,3 +92,100 @@ func Test_implicitReference_RepositoryName(t *testing.T) { }) } } + +func Test_tryRemote(t *testing.T) { + // Create a test image + img, err := random.Image(1024, 5) + require.NoError(t, err) + + // Get the image digest for test expectations + digest, err := img.Digest() + require.NoError(t, err) + + // Set up registry server with null logger to suppress log output + nullLogger := log.New(io.Discard, "", 0) + s := httptest.NewServer(registry.New(registry.Logger(nullLogger))) + t.Cleanup(s.Close) + + u, err := url.Parse(s.URL) + require.NoError(t, err) + + tests := []struct { + name string + imageName string + setupImage func(t *testing.T, ref name.Reference) + wantName string + wantErr string + }{ + { + name: "successful image retrieval", + imageName: "test/alpine:3.10", + setupImage: func(t *testing.T, ref name.Reference) { + err := remote.Write(ref, img) + require.NoError(t, err) + }, + wantName: "/test/alpine:3.10", + }, + { + name: "helm chart config media type", + imageName: "test/helm:chart", + setupImage: func(t *testing.T, ref name.Reference) { + configFile, err := img.ConfigFile() + require.NoError(t, err) + + // Create a new config with helm chart media type + imageToWrite, err := mutate.Config(img, configFile.Config) + require.NoError(t, err) + + imageToWrite = mutate.ConfigMediaType(imageToWrite, "application/vnd.cncf.helm.chart") + + err = remote.Write(ref, imageToWrite) + require.NoError(t, err) + }, + wantErr: "unsupported artifact type", + }, + { + name: "image not found", + imageName: "test/notfound:latest", + wantErr: "NAME_UNKNOWN", + setupImage: func(*testing.T, name.Reference) {}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // Parse the image name with the test server address + fullImageName := u.Host + "/" + tt.imageName + ref, err := name.ParseReference(fullImageName) + require.NoError(t, err) + + // Set up the image in registry if needed + tt.setupImage(t, ref) + + ctx := t.Context() + got, cleanup, err := tryRemote(ctx, fullImageName, ref, types.ImageOptions{ + RegistryOptions: types.RegistryOptions{ + Insecure: true, + }, + }) + t.Cleanup(cleanup) + + if tt.wantErr != "" { + assert.ErrorContains(t, err, tt.wantErr) + return + } + + require.NoError(t, err) + assert.NotNil(t, got) + assert.Contains(t, got.Name(), tt.wantName) + + // Verify RepoTags and RepoDigests contain expected values + repoTags := got.RepoTags() + repoDigests := got.RepoDigests() + assert.Len(t, repoTags, 1) + assert.Contains(t, repoTags[0], tt.imageName) + assert.Len(t, repoDigests, 1) + assert.Contains(t, repoDigests[0], digest.String()) + }) + } +}