github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/artifact/image/remote_sbom_test.go (about) 1 package image_test 2 3 import ( 4 "context" 5 "net/http" 6 "net/http/httptest" 7 "net/url" 8 "os" 9 "testing" 10 11 v1 "github.com/google/go-containerregistry/pkg/v1" 12 fakei "github.com/google/go-containerregistry/pkg/v1/fake" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 16 "github.com/devseccon/trivy/pkg/fanal/artifact" 17 image2 "github.com/devseccon/trivy/pkg/fanal/artifact/image" 18 "github.com/devseccon/trivy/pkg/fanal/cache" 19 "github.com/devseccon/trivy/pkg/fanal/types" 20 "github.com/devseccon/trivy/pkg/log" 21 "github.com/devseccon/trivy/pkg/rekortest" 22 ) 23 24 func TestMain(m *testing.M) { 25 log.InitLogger(false, true) 26 os.Exit(m.Run()) 27 } 28 29 type fakeImage struct { 30 name string 31 repoDigests []string 32 *fakei.FakeImage 33 types.ImageExtension 34 } 35 36 func (f fakeImage) ID() (string, error) { 37 return "", nil 38 } 39 40 func (f fakeImage) Name() string { 41 return f.name 42 } 43 44 func (f fakeImage) RepoDigests() []string { 45 return f.repoDigests 46 } 47 48 func TestArtifact_InspectRekorAttestation(t *testing.T) { 49 type fields struct { 50 imageName string 51 repoDigests []string 52 } 53 tests := []struct { 54 name string 55 fields fields 56 artifactOpt artifact.Option 57 putBlobExpectations []cache.ArtifactCachePutBlobExpectation 58 want types.ArtifactReference 59 wantErr string 60 }{ 61 { 62 name: "happy path", 63 fields: fields{ 64 imageName: "test/image:10", 65 repoDigests: []string{ 66 "test/image@sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", 67 }, 68 }, 69 putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ 70 { 71 Args: cache.ArtifactCachePutBlobArgs{ 72 BlobID: "sha256:9c23872047046e145f49fb5533b63ace0cbf819f5b68e33f69f4e9bbab4c517e", 73 BlobInfo: types.BlobInfo{ 74 SchemaVersion: types.BlobJSONSchemaVersion, 75 OS: types.OS{ 76 Family: "alpine", 77 Name: "3.16.2", 78 }, 79 PackageInfos: []types.PackageInfo{ 80 { 81 Packages: types.Packages{ 82 { 83 Name: "musl", 84 Version: "1.2.3-r0", 85 SrcName: "musl", 86 SrcVersion: "1.2.3-r0", 87 Licenses: []string{"MIT"}, 88 Ref: "pkg:apk/alpine/musl@1.2.3-r0?distro=3.16.2", 89 Layer: types.Layer{ 90 DiffID: "sha256:994393dc58e7931862558d06e46aa2bb17487044f670f310dffe1d24e4d1eec7", 91 }, 92 }, 93 }, 94 }, 95 }, 96 }, 97 }, 98 Returns: cache.ArtifactCachePutBlobReturns{}, 99 }, 100 }, 101 artifactOpt: artifact.Option{ 102 SBOMSources: []string{"rekor"}, 103 }, 104 want: types.ArtifactReference{ 105 Name: "test/image:10", 106 Type: types.ArtifactCycloneDX, 107 ID: "sha256:9c23872047046e145f49fb5533b63ace0cbf819f5b68e33f69f4e9bbab4c517e", 108 BlobIDs: []string{ 109 "sha256:9c23872047046e145f49fb5533b63ace0cbf819f5b68e33f69f4e9bbab4c517e", 110 }, 111 }, 112 }, 113 { 114 name: "error", 115 fields: fields{ 116 imageName: "test/image:10", 117 repoDigests: []string{ 118 "test/image@sha256:123456e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", 119 }, 120 }, 121 artifactOpt: artifact.Option{ 122 SBOMSources: []string{"rekor"}, 123 }, 124 wantErr: "remote SBOM fetching error", 125 }, 126 } 127 128 require.NoError(t, log.InitLogger(false, true)) 129 for _, tt := range tests { 130 t.Run(tt.name, func(t *testing.T) { 131 ts := rekortest.NewServer(t) 132 defer ts.Close() 133 134 // Set the testing URL 135 tt.artifactOpt.RekorURL = ts.URL() 136 137 mockCache := new(cache.MockArtifactCache) 138 mockCache.ApplyPutBlobExpectations(tt.putBlobExpectations) 139 140 fi := &fakei.FakeImage{} 141 fi.ConfigFileReturns(&v1.ConfigFile{}, nil) 142 143 img := &fakeImage{ 144 name: tt.fields.imageName, 145 repoDigests: tt.fields.repoDigests, 146 FakeImage: fi, 147 } 148 a, err := image2.NewArtifact(img, mockCache, tt.artifactOpt) 149 require.NoError(t, err) 150 151 got, err := a.Inspect(context.Background()) 152 if tt.wantErr != "" { 153 assert.ErrorContains(t, err, tt.wantErr) 154 return 155 } 156 require.NoError(t, err, tt.name) 157 got.CycloneDX = nil 158 assert.Equal(t, tt.want, got) 159 }) 160 } 161 } 162 163 func TestArtifact_inspectOCIReferrerSBOM(t *testing.T) { 164 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 165 switch r.URL.Path { 166 case "/v2": 167 _, err := w.Write([]byte("ok")) 168 require.NoError(t, err) 169 case "/v2/test/image/referrers/sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02": 170 http.ServeFile(w, r, "testdata/index.json") 171 case "/v2/test/image/manifests/sha256:37c89af4907fa0af078aeba12d6f18dc0c63937c010030baaaa88e958f0719a5": 172 http.ServeFile(w, r, "testdata/manifest.json") 173 case "/v2/test/image/blobs/sha256:9e05dda2a2dcdd526c9204be8645ae48742861c27f093bf496a6397834acecf2": 174 http.ServeFile(w, r, "testdata/cyclonedx.json") 175 } 176 return 177 })) 178 defer ts.Close() 179 180 u, err := url.Parse(ts.URL) 181 require.NoError(t, err) 182 registry := u.Host 183 184 type fields struct { 185 imageName string 186 repoDigests []string 187 } 188 189 tests := []struct { 190 name string 191 fields fields 192 artifactOpt artifact.Option 193 putBlobExpectations []cache.ArtifactCachePutBlobExpectation 194 want types.ArtifactReference 195 wantErr string 196 }{ 197 { 198 name: "happy path", 199 fields: fields{ 200 imageName: registry + "/test/image:10", 201 repoDigests: []string{ 202 registry + "/test/image@sha256:782143e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", 203 }, 204 }, 205 artifactOpt: artifact.Option{ 206 SBOMSources: []string{"oci"}, 207 }, 208 putBlobExpectations: []cache.ArtifactCachePutBlobExpectation{ 209 { 210 Args: cache.ArtifactCachePutBlobArgs{ 211 BlobID: "sha256:d07a1894bfd283b4ac26682ab48f12ad22cdc4fef9cf8b4c09056f631d3667a5", 212 BlobInfo: types.BlobInfo{ 213 SchemaVersion: types.BlobJSONSchemaVersion, 214 Applications: []types.Application{ 215 { 216 Type: types.GoBinary, 217 Libraries: types.Packages{ 218 { 219 Name: "github.com/opencontainers/go-digest", 220 Version: "v1.0.0", 221 Ref: "pkg:golang/github.com/opencontainers/go-digest@v1.0.0", 222 }, 223 { 224 Name: "golang.org/x/sync", 225 Version: "v0.1.0", 226 Ref: "pkg:golang/golang.org/x/sync@v0.1.0", 227 }, 228 }, 229 }, 230 }, 231 }, 232 }, 233 }, 234 }, 235 want: types.ArtifactReference{ 236 Name: registry + "/test/image:10", 237 Type: types.ArtifactCycloneDX, 238 ID: "sha256:d07a1894bfd283b4ac26682ab48f12ad22cdc4fef9cf8b4c09056f631d3667a5", 239 BlobIDs: []string{ 240 "sha256:d07a1894bfd283b4ac26682ab48f12ad22cdc4fef9cf8b4c09056f631d3667a5", 241 }, 242 }, 243 }, 244 { 245 name: "404", 246 fields: fields{ 247 imageName: registry + "/test/image:unknown", 248 repoDigests: []string{ 249 registry + "/test/image@sha256:123456e39f1e7a04e3f6da2d88b1c057e5657363c4f90679f3e8a071b7619e02", 250 }, 251 }, 252 artifactOpt: artifact.Option{ 253 SBOMSources: []string{"oci"}, 254 }, 255 wantErr: "unable to get manifest", 256 }, 257 } 258 259 for _, tt := range tests { 260 t.Run(tt.name, func(t *testing.T) { 261 mockCache := new(cache.MockArtifactCache) 262 mockCache.ApplyPutBlobExpectations(tt.putBlobExpectations) 263 264 fi := &fakei.FakeImage{} 265 fi.ConfigFileReturns(&v1.ConfigFile{}, nil) 266 267 img := &fakeImage{ 268 name: tt.fields.imageName, 269 repoDigests: tt.fields.repoDigests, 270 FakeImage: fi, 271 } 272 a, err := image2.NewArtifact(img, mockCache, tt.artifactOpt) 273 require.NoError(t, err) 274 275 got, err := a.Inspect(context.Background()) 276 if tt.wantErr != "" { 277 assert.ErrorContains(t, err, tt.wantErr) 278 return 279 } 280 281 require.NoError(t, err, tt.name) 282 got.CycloneDX = nil 283 assert.Equal(t, tt.want, got) 284 }) 285 } 286 }