github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/fanal/test/integration/containerd_test.go (about) 1 //go:build integration && linux 2 3 package integration 4 5 import ( 6 "compress/gzip" 7 "context" 8 "encoding/json" 9 "errors" 10 "fmt" 11 "os" 12 "path/filepath" 13 "runtime" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/samber/lo" 19 20 "github.com/containerd/containerd" 21 "github.com/containerd/containerd/images" 22 "github.com/containerd/containerd/namespaces" 23 dockercontainer "github.com/docker/docker/api/types/container" 24 v1 "github.com/google/go-containerregistry/pkg/v1" 25 "github.com/stretchr/testify/assert" 26 "github.com/stretchr/testify/require" 27 "github.com/testcontainers/testcontainers-go" 28 "github.com/testcontainers/testcontainers-go/wait" 29 30 "github.com/devseccon/trivy/pkg/fanal/analyzer" 31 "github.com/devseccon/trivy/pkg/fanal/applier" 32 "github.com/devseccon/trivy/pkg/fanal/artifact" 33 aimage "github.com/devseccon/trivy/pkg/fanal/artifact/image" 34 "github.com/devseccon/trivy/pkg/fanal/cache" 35 "github.com/devseccon/trivy/pkg/fanal/image" 36 "github.com/devseccon/trivy/pkg/fanal/types" 37 ) 38 39 func setupContainerd(t *testing.T, ctx context.Context, namespace string) *containerd.Client { 40 t.Helper() 41 tmpDir := t.TempDir() 42 43 containerdDir := filepath.Join(tmpDir, "containerd") 44 err := os.MkdirAll(containerdDir, os.ModePerm) 45 require.NoError(t, err) 46 47 socketPath := filepath.Join(containerdDir, "containerd.sock") 48 49 // Set a containerd socket 50 t.Setenv("CONTAINERD_ADDRESS", socketPath) 51 t.Setenv("CONTAINERD_NAMESPACE", namespace) 52 53 startContainerd(t, ctx, tmpDir) 54 55 // Retry up to 3 times until containerd is ready 56 var client *containerd.Client 57 iteration, _, err := lo.AttemptWhileWithDelay(3, 1*time.Second, func(int, time.Duration) (error, bool) { 58 client, err = containerd.New(socketPath) 59 if err != nil { 60 if !errors.Is(err, os.ErrPermission) { 61 return err, false // unexpected error 62 } 63 return err, true 64 } 65 t.Cleanup(func() { 66 assert.NoError(t, client.Close()) 67 }) 68 return nil, false 69 }) 70 require.NoErrorf(t, err, "attempted %d times ", iteration) 71 72 return client 73 } 74 75 func startContainerd(t *testing.T, ctx context.Context, hostPath string) { 76 t.Helper() 77 t.Setenv("TESTCONTAINERS_RYUK_DISABLED", "true") 78 req := testcontainers.ContainerRequest{ 79 Name: "containerd", 80 Image: "ghcr.io/devseccon/trivy-test-images/containerd:latest", 81 Entrypoint: []string{ 82 "/bin/sh", 83 "-c", 84 "/usr/local/bin/containerd", 85 }, 86 Mounts: testcontainers.Mounts( 87 testcontainers.BindMount(hostPath, "/run"), 88 ), 89 HostConfigModifier: func(hostConfig *dockercontainer.HostConfig) { 90 hostConfig.AutoRemove = true 91 }, 92 WaitingFor: wait.ForLog("containerd successfully booted"), 93 } 94 containerdC, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{ 95 ContainerRequest: req, 96 Started: true, 97 }) 98 require.NoError(t, err) 99 100 _, _, err = containerdC.Exec(ctx, []string{ 101 "chmod", 102 "666", 103 "/run/containerd/containerd.sock", 104 }) 105 require.NoError(t, err) 106 107 t.Cleanup(func() { 108 assert.NoError(t, containerdC.Terminate(ctx)) 109 }) 110 } 111 112 // Each of these tests imports an image and tags it with the name found in the 113 // `imageName` field. Then, the containerd store is searched by the reference 114 // provided in the `searchName` field. 115 func TestContainerd_SearchLocalStoreByNameOrDigest(t *testing.T) { 116 // Each architecture needs different images and test cases. 117 // Currently only amd64 architecture is supported 118 if runtime.GOARCH != "amd64" { 119 t.Skip("'Containerd' test only supports amd64 architecture") 120 } 121 122 digest := "sha256:f12582b2f2190f350e3904462c1c23aaf366b4f76705e97b199f9bbded1d816a" 123 basename := "hello" 124 tag := "world" 125 importedImageOriginalName := "ghcr.io/devseccon/trivy-test-images:alpine-310" 126 127 tests := []struct { 128 name string 129 imageName string 130 searchName string 131 wantErr bool 132 }{ 133 { 134 name: "familiarName:tag", 135 imageName: fmt.Sprintf("%s:%s", basename, tag), 136 searchName: fmt.Sprintf("%s:%s", basename, tag), 137 }, 138 { 139 name: "compound/name:tag", 140 imageName: fmt.Sprintf("say/%s:%s", basename, tag), 141 searchName: fmt.Sprintf("say/%s:%s", basename, tag), 142 }, 143 { 144 name: "docker.io/library/name:tag", 145 imageName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), 146 searchName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), 147 }, 148 { 149 name: "other-registry.io/library/name:tag", 150 imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), 151 searchName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), 152 }, 153 { 154 name: "other-registry.io/library/name:wrongTag should fail", 155 imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), 156 searchName: fmt.Sprintf("other-registry.io/library/%s:badtag", basename), 157 wantErr: true, 158 }, 159 { 160 name: "other-registry.io/library/wrongName:tag should fail", 161 imageName: fmt.Sprintf("other-registry.io/library/%s:%s", basename, tag), 162 searchName: fmt.Sprintf("other-registry.io/library/badname:%s", tag), 163 wantErr: true, 164 }, 165 { 166 name: "digest should succeed", 167 imageName: "", 168 searchName: digest, 169 }, 170 { 171 name: "wrong digest should fail", 172 imageName: "", 173 searchName: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 174 wantErr: true, 175 }, 176 { 177 name: "name@digest", 178 imageName: fmt.Sprintf("%s:%s", basename, tag), 179 searchName: fmt.Sprintf("hello@%s", digest), 180 }, 181 { 182 name: "compound/name@digest", 183 imageName: fmt.Sprintf("compound/%s:%s", basename, tag), 184 searchName: fmt.Sprintf("compound/%s@%s", basename, digest), 185 }, 186 { 187 name: "docker.io/library/name@digest", 188 imageName: fmt.Sprintf("docker.io/library/%s:%s", basename, tag), 189 searchName: fmt.Sprintf("docker.io/library/%s@%s", basename, digest), 190 }, 191 { 192 name: "otherdomain.io/name@digest", 193 imageName: fmt.Sprintf("otherdomain.io/%s:%s", basename, tag), 194 searchName: fmt.Sprintf("otherdomain.io/%s@%s", basename, digest), 195 }, 196 { 197 name: "wrongName@digest should fail", 198 imageName: fmt.Sprintf("%s:%s", basename, tag), 199 searchName: fmt.Sprintf("badname@%s", digest), 200 wantErr: true, 201 }, 202 { 203 name: "compound/wrongName@digest should fail", 204 imageName: fmt.Sprintf("compound/%s:%s", basename, tag), 205 searchName: fmt.Sprintf("compound/badname@%s", digest), 206 wantErr: true, 207 }, 208 } 209 210 namespace := "default" 211 ctx := namespaces.WithNamespace(context.Background(), namespace) 212 client := setupContainerd(t, ctx, namespace) 213 214 for _, tt := range tests { 215 t.Run(tt.name, func(t *testing.T) { 216 archive, err := os.Open("../../../../integration/testdata/fixtures/images/alpine-310.tar.gz") 217 require.NoError(t, err) 218 219 uncompressedArchive, err := gzip.NewReader(archive) 220 require.NoError(t, err) 221 defer archive.Close() 222 223 cacheDir := t.TempDir() 224 c, err := cache.NewFSCache(cacheDir) 225 require.NoError(t, err) 226 227 imgs, err := client.Import(ctx, uncompressedArchive) 228 require.NoError(t, err) 229 _ = imgs 230 231 digestTest := tt.imageName == "" 232 233 if !digestTest { 234 // Tag the image, taken from the code in `ctr image tag...` 235 imgs[0].Name = tt.imageName 236 _, err = client.ImageService().Create(ctx, imgs[0]) 237 require.NoError(t, err) 238 239 // Remove the image by its original name, to ensure the image 240 // is known only by the tag we have given it. 241 err = client.ImageService().Delete(ctx, importedImageOriginalName, images.SynchronousDelete()) 242 require.NoError(t, err) 243 } 244 245 t.Cleanup(func() { 246 for _, img := range imgs { 247 err = client.ImageService().Delete(ctx, img.Name, images.SynchronousDelete()) 248 require.NoError(t, err) 249 } 250 }) 251 252 // enable only containerd 253 img, cleanup, err := image.NewContainerImage(ctx, tt.searchName, 254 types.ImageOptions{ImageSources: types.ImageSources{types.ContainerdImageSource}}) 255 defer cleanup() 256 if tt.wantErr { 257 require.Error(t, err) 258 return 259 } 260 require.NoError(t, err) 261 262 ar, err := aimage.NewArtifact(img, c, artifact.Option{}) 263 require.NoError(t, err) 264 265 ref, err := ar.Inspect(ctx) 266 require.NoError(t, err) 267 268 if digestTest { 269 actualDigest := strings.Split(ref.ImageMetadata.RepoDigests[0], "@")[1] 270 require.Equal(t, tt.searchName, actualDigest) 271 return 272 } 273 274 require.Equal(t, tt.searchName, ref.Name) 275 }) 276 } 277 } 278 279 func TestContainerd_LocalImage(t *testing.T) { 280 localImageTestWithNamespace(t, "default") 281 } 282 283 func TestContainerd_LocalImage_Alternative_Namespace(t *testing.T) { 284 localImageTestWithNamespace(t, "test") 285 } 286 287 func localImageTestWithNamespace(t *testing.T, namespace string) { 288 // Each architecture needs different images and test cases. 289 // Currently only amd64 architecture is supported 290 if runtime.GOARCH != "amd64" { 291 t.Skip("'Containerd' test only supports amd64 architecture") 292 } 293 294 tests := []struct { 295 name string 296 imageName string 297 tarArchive string 298 wantMetadata types.ImageMetadata 299 }{ 300 { 301 name: "alpine 3.10", 302 imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310", 303 tarArchive: "../../../../integration/testdata/fixtures/images/alpine-310.tar.gz", 304 wantMetadata: types.ImageMetadata{ 305 ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 306 DiffIDs: []string{ 307 "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 308 }, 309 RepoTags: []string{"ghcr.io/devseccon/trivy-test-images:alpine-310"}, 310 RepoDigests: []string{"ghcr.io/devseccon/trivy-test-images@sha256:f12582b2f2190f350e3904462c1c23aaf366b4f76705e97b199f9bbded1d816a"}, 311 ConfigFile: v1.ConfigFile{ 312 Architecture: "amd64", 313 Created: v1.Time{ 314 Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC), 315 }, 316 OS: "linux", 317 RootFS: v1.RootFS{ 318 Type: "layers", 319 DiffIDs: []v1.Hash{ 320 { 321 Algorithm: "sha256", 322 Hex: "03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 323 }, 324 }, 325 }, 326 Config: v1.Config{ 327 Cmd: []string{ 328 "/bin/sh", 329 }, 330 Env: []string{ 331 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 332 }, 333 }, 334 History: []v1.History{ 335 { 336 Created: v1.Time{Time: time.Date(2019, 8, 20, 20, 19, 55, 62606894, time.UTC)}, 337 CreatedBy: "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / ", 338 }, 339 { 340 Created: v1.Time{Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC)}, 341 CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", 342 EmptyLayer: true, 343 }, 344 }, 345 }, 346 }, 347 }, 348 { 349 name: "vulnimage", 350 imageName: "ghcr.io/devseccon/trivy-test-images:vulnimage", 351 tarArchive: "../../../../integration/testdata/fixtures/images/vulnimage.tar.gz", 352 wantMetadata: types.ImageMetadata{ 353 ID: "sha256:c17083664da903e13e9092fa3a3a1aeee2431aa2728298e3dbcec72f26369c41", 354 DiffIDs: []string{ 355 "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", 356 "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", 357 "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", 358 "sha256:dc00fbef458ad3204bbb548e2d766813f593d857b845a940a0de76aed94c94d1", 359 "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", 360 "sha256:9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", 361 "sha256:6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013", 362 "sha256:83abef706f5ae199af65d1c13d737d0eb36219f0d18e36c6d8ff06159df39a63", 363 "sha256:c03283c257abd289a30b4f5e9e1345da0e9bfdc6ca398ee7e8fac6d2c1456227", 364 "sha256:2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4", 365 "sha256:82c59ac8ee582542648e634ca5aff9a464c68ff8a054f105a58689fb52209e34", 366 "sha256:2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708", 367 "sha256:6ca56f561e677ae06c3bc87a70792642d671a4416becb9a101577c1a6e090e36", 368 "sha256:154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", 369 "sha256:b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", 370 "sha256:4d116f47cb2cc77a88d609b9805f2b011a5d42339b67300166654b3922685ac9", 371 "sha256:9b1326af1cf81505fd8e596b7f622b679ae5d290e46b25214ba26e4f7c661d60", 372 "sha256:a66245f885f2a210071e415f0f8ac4f21f5e4eab6c0435b4082e5c3637c411cb", 373 "sha256:ba17950e91742d6ac7055ea3a053fe764486658ca1ce8188f1e427b1fe2bc4da", 374 "sha256:6ef42db7800507577383edf1937cb203b9b85f619feed6046594208748ceb52c", 375 }, 376 RepoTags: []string{"ghcr.io/devseccon/trivy-test-images:vulnimage"}, 377 RepoDigests: []string{"ghcr.io/devseccon/trivy-test-images@sha256:e74abbfd81e00baaf464cf9e09f8b24926e5255171e3150a60aa341ce064688f"}, 378 ConfigFile: v1.ConfigFile{ 379 Architecture: "amd64", 380 Created: v1.Time{ 381 Time: time.Date(2019, 8, 7, 7, 25, 58, 651649800, time.UTC), 382 }, 383 OS: "linux", 384 RootFS: v1.RootFS{ 385 Type: "layers", 386 DiffIDs: []v1.Hash{ 387 { 388 Algorithm: "sha256", 389 Hex: "ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", 390 }, 391 { 392 Algorithm: "sha256", 393 Hex: "0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", 394 }, 395 { 396 Algorithm: "sha256", 397 Hex: "9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", 398 }, 399 { 400 Algorithm: "sha256", 401 Hex: "dc00fbef458ad3204bbb548e2d766813f593d857b845a940a0de76aed94c94d1", 402 }, 403 { 404 Algorithm: "sha256", 405 Hex: "5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", 406 }, 407 { 408 Algorithm: "sha256", 409 Hex: "9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", 410 }, 411 { 412 Algorithm: "sha256", 413 Hex: "6408527580eade39c2692dbb6b0f6a9321448d06ea1c2eef06bb7f37da9c5013", 414 }, 415 { 416 Algorithm: "sha256", 417 Hex: "83abef706f5ae199af65d1c13d737d0eb36219f0d18e36c6d8ff06159df39a63", 418 }, 419 { 420 Algorithm: "sha256", 421 Hex: "c03283c257abd289a30b4f5e9e1345da0e9bfdc6ca398ee7e8fac6d2c1456227", 422 }, 423 { 424 Algorithm: "sha256", 425 Hex: "2da3602d664dd3f71fae83cbc566d4e80b432c6ee8bb4efd94c8e85122f503d4", 426 }, 427 { 428 Algorithm: "sha256", 429 Hex: "82c59ac8ee582542648e634ca5aff9a464c68ff8a054f105a58689fb52209e34", 430 }, 431 { 432 Algorithm: "sha256", 433 Hex: "2f4a5c9187c249834ebc28783bd3c65bdcbacaa8baa6620ddaa27846dd3ef708", 434 }, 435 { 436 Algorithm: "sha256", 437 Hex: "6ca56f561e677ae06c3bc87a70792642d671a4416becb9a101577c1a6e090e36", 438 }, 439 { 440 Algorithm: "sha256", 441 Hex: "154ad0735c360b212b167f424d33a62305770a1fcfb6363882f5c436cfbd9812", 442 }, 443 { 444 Algorithm: "sha256", 445 Hex: "b2a1a2d80bf0c747a4f6b0ca6af5eef23f043fcdb1ed4f3a3e750aef2dc68079", 446 }, 447 { 448 Algorithm: "sha256", 449 Hex: "4d116f47cb2cc77a88d609b9805f2b011a5d42339b67300166654b3922685ac9", 450 }, 451 { 452 Algorithm: "sha256", 453 Hex: "9b1326af1cf81505fd8e596b7f622b679ae5d290e46b25214ba26e4f7c661d60", 454 }, 455 { 456 Algorithm: "sha256", 457 Hex: "a66245f885f2a210071e415f0f8ac4f21f5e4eab6c0435b4082e5c3637c411cb", 458 }, 459 { 460 Algorithm: "sha256", 461 Hex: "ba17950e91742d6ac7055ea3a053fe764486658ca1ce8188f1e427b1fe2bc4da", 462 }, 463 { 464 Algorithm: "sha256", 465 Hex: "6ef42db7800507577383edf1937cb203b9b85f619feed6046594208748ceb52c", 466 }, 467 }, 468 }, 469 Config: v1.Config{ 470 Cmd: []string{"composer"}, 471 Env: []string{ 472 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 473 "PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", 474 "PHP_INI_DIR=/usr/local/etc/php", 475 "PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", 476 "PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", 477 "PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", 478 "GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", 479 "PHP_VERSION=7.2.11", 480 "PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror", 481 "PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", 482 "PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985", 483 "PHP_MD5=", 484 "COMPOSER_ALLOW_SUPERUSER=1", 485 "COMPOSER_HOME=/tmp", 486 "COMPOSER_VERSION=1.7.2", 487 }, 488 WorkingDir: "/app", 489 Entrypoint: []string{ 490 "/bin/sh", 491 "/docker-entrypoint.sh", 492 }, 493 }, 494 History: []v1.History{ 495 { 496 Created: v1.Time{Time: time.Date(2018, 9, 11, 22, 19, 38, 885299940, time.UTC)}, 497 CreatedBy: "/bin/sh -c #(nop) ADD file:49f9e47e678d868d5b023482aa8dded71276a241a665c4f8b55ca77269321b34 in / ", 498 }, 499 { 500 Created: v1.Time{Time: time.Date(2018, 9, 11, 22, 19, 39, 58628442, time.UTC)}, 501 CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", 502 EmptyLayer: true, 503 }, 504 { 505 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 26, 59, 951316015, time.UTC)}, 506 CreatedBy: "/bin/sh -c #(nop) ENV PHPIZE_DEPS=autoconf \t\tdpkg-dev dpkg \t\tfile \t\tg++ \t\tgcc \t\tlibc-dev \t\tmake \t\tpkgconf \t\tre2c", 507 EmptyLayer: true, 508 }, 509 { 510 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 1, 470388635, time.UTC)}, 511 CreatedBy: "/bin/sh -c apk add --no-cache --virtual .persistent-deps \t\tca-certificates \t\tcurl \t\ttar \t\txz \t\tlibressl", 512 }, 513 { 514 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 2, 432381785, time.UTC)}, 515 CreatedBy: "/bin/sh -c set -x \t&& addgroup -g 82 -S www-data \t&& adduser -u 82 -D -S -G www-data www-data", 516 }, 517 { 518 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 2, 715120309, time.UTC)}, 519 CreatedBy: "/bin/sh -c #(nop) ENV PHP_INI_DIR=/usr/local/etc/php", 520 EmptyLayer: true, 521 }, 522 { 523 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 3, 655421341, time.UTC)}, 524 CreatedBy: "/bin/sh -c mkdir -p $PHP_INI_DIR/conf.d", 525 }, 526 { 527 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 3, 931799562, time.UTC)}, 528 CreatedBy: "/bin/sh -c #(nop) ENV PHP_CFLAGS=-fstack-protector-strong -fpic -fpie -O2", 529 EmptyLayer: true, 530 }, 531 { 532 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 210945499, time.UTC)}, 533 CreatedBy: "/bin/sh -c #(nop) ENV PHP_CPPFLAGS=-fstack-protector-strong -fpic -fpie -O2", 534 EmptyLayer: true, 535 }, 536 { 537 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 523116501, time.UTC)}, 538 CreatedBy: "/bin/sh -c #(nop) ENV PHP_LDFLAGS=-Wl,-O1 -Wl,--hash-style=both -pie", 539 EmptyLayer: true, 540 }, 541 { 542 Created: v1.Time{Time: time.Date(2018, 9, 12, 1, 27, 4, 795176159, time.UTC)}, 543 CreatedBy: "/bin/sh -c #(nop) ENV GPG_KEYS=1729F83938DA44E27BA0F4D3DBDB397470D12172 B1B44D8F021E4E2D6021E995DC9FF8D3EE5AF27F", 544 EmptyLayer: true, 545 }, 546 { 547 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 415761689, time.UTC)}, 548 CreatedBy: "/bin/sh -c #(nop) ENV PHP_VERSION=7.2.11", 549 EmptyLayer: true, 550 }, 551 { 552 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 599097853, time.UTC)}, 553 CreatedBy: "/bin/sh -c #(nop) ENV PHP_URL=https://secure.php.net/get/php-7.2.11.tar.xz/from/this/mirror PHP_ASC_URL=https://secure.php.net/get/php-7.2.11.tar.xz.asc/from/this/mirror", 554 EmptyLayer: true, 555 }, 556 { 557 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 18, 782890412, time.UTC)}, 558 CreatedBy: "/bin/sh -c #(nop) ENV PHP_SHA256=da1a705c0bc46410e330fc6baa967666c8cd2985378fb9707c01a8e33b01d985 PHP_MD5=", 559 EmptyLayer: true, 560 }, 561 { 562 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 22, 795846753, time.UTC)}, 563 CreatedBy: "/bin/sh -c set -xe; \t\tapk add --no-cache --virtual .fetch-deps \t\tgnupg \t\twget \t; \t\tmkdir -p /usr/src; \tcd /usr/src; \t\twget -O php.tar.xz \"$PHP_URL\"; \t\tif [ -n \"$PHP_SHA256\" ]; then \t\techo \"$PHP_SHA256 *php.tar.xz\" | sha256sum -c -; \tfi; \tif [ -n \"$PHP_MD5\" ]; then \t\techo \"$PHP_MD5 *php.tar.xz\" | md5sum -c -; \tfi; \t\tif [ -n \"$PHP_ASC_URL\" ]; then \t\twget -O php.tar.xz.asc \"$PHP_ASC_URL\"; \t\texport GNUPGHOME=\"$(mktemp -d)\"; \t\tfor key in $GPG_KEYS; do \t\t\tgpg --keyserver ha.pool.sks-keyservers.net --recv-keys \"$key\"; \t\tdone; \t\tgpg --batch --verify php.tar.xz.asc php.tar.xz; \t\tcommand -v gpgconf > /dev/null && gpgconf --kill all; \t\trm -rf \"$GNUPGHOME\"; \tfi; \t\tapk del .fetch-deps", 564 }, 565 { 566 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 2, 23, 71406376, time.UTC)}, 567 CreatedBy: "/bin/sh -c #(nop) COPY file:207c686e3fed4f71f8a7b245d8dcae9c9048d276a326d82b553c12a90af0c0ca in /usr/local/bin/ ", 568 }, 569 { 570 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 13, 93396680, time.UTC)}, 571 CreatedBy: "/bin/sh -c set -xe \t&& apk add --no-cache --virtual .build-deps \t\t$PHPIZE_DEPS \t\tcoreutils \t\tcurl-dev \t\tlibedit-dev \t\tlibressl-dev \t\tlibsodium-dev \t\tlibxml2-dev \t\tsqlite-dev \t\t&& export CFLAGS=\"$PHP_CFLAGS\" \t\tCPPFLAGS=\"$PHP_CPPFLAGS\" \t\tLDFLAGS=\"$PHP_LDFLAGS\" \t&& docker-php-source extract \t&& cd /usr/src/php \t&& gnuArch=\"$(dpkg-architecture --query DEB_BUILD_GNU_TYPE)\" \t&& ./configure \t\t--build=\"$gnuArch\" \t\t--with-config-file-path=\"$PHP_INI_DIR\" \t\t--with-config-file-scan-dir=\"$PHP_INI_DIR/conf.d\" \t\t\t\t--enable-option-checking=fatal \t\t\t\t--with-mhash \t\t\t\t--enable-ftp \t\t--enable-mbstring \t\t--enable-mysqlnd \t\t--with-sodium=shared \t\t\t\t--with-curl \t\t--with-libedit \t\t--with-openssl \t\t--with-zlib \t\t\t\t$(test \"$gnuArch\" = 's390x-linux-gnu' && echo '--without-pcre-jit') \t\t\t\t$PHP_EXTRA_CONFIGURE_ARGS \t&& make -j \"$(nproc)\" \t&& make install \t&& { find /usr/local/bin /usr/local/sbin -type f -perm +0111 -exec strip --strip-all '{}' + || true; } \t&& make clean \t\t&& cp -v php.ini-* \"$PHP_INI_DIR/\" \t\t&& cd / \t&& docker-php-source delete \t\t&& runDeps=\"$( \t\tscanelf --needed --nobanner --format '%n#p' --recursive /usr/local \t\t\t| tr ',' '\\n' \t\t\t| sort -u \t\t\t| awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' \t)\" \t&& apk add --no-cache --virtual .php-rundeps $runDeps \t\t&& apk del .build-deps \t\t&& pecl update-channels \t&& rm -rf /tmp/pear ~/.pearrc", 572 }, 573 { 574 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 13, 722586262, time.UTC)}, 575 CreatedBy: "/bin/sh -c #(nop) COPY multi:2cdcedabcf5a3b9ae610fab7848e94bc2f64b4d85710d55fd6f79e44dacf73d8 in /usr/local/bin/ ", 576 }, 577 { 578 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 14, 618087104, time.UTC)}, 579 CreatedBy: "/bin/sh -c docker-php-ext-enable sodium", 580 }, 581 { 582 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 14, 826981756, time.UTC)}, 583 CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"docker-php-entrypoint\"]", 584 EmptyLayer: true, 585 }, 586 { 587 Created: v1.Time{Time: time.Date(2018, 10, 15, 19, 7, 15, 10831572, time.UTC)}, 588 CreatedBy: "/bin/sh -c #(nop) CMD [\"php\" \"-a\"]", 589 EmptyLayer: true, 590 }, 591 { 592 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 21, 919735971, time.UTC)}, 593 CreatedBy: "/bin/sh -c apk --no-cache add git subversion openssh mercurial tini bash patch", 594 }, 595 { 596 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 22, 611763893, time.UTC)}, 597 CreatedBy: "/bin/sh -c echo \"memory_limit=-1\" > \"$PHP_INI_DIR/conf.d/memory-limit.ini\" && echo \"date.timezone=${PHP_TIMEZONE:-UTC}\" > \"$PHP_INI_DIR/conf.d/date_timezone.ini\"", 598 }, 599 { 600 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 224278478, time.UTC)}, 601 CreatedBy: "/bin/sh -c apk add --no-cache --virtual .build-deps zlib-dev && docker-php-ext-install zip && runDeps=\"$( scanelf --needed --nobanner --format '%n#p' --recursive /usr/local/lib/php/extensions | tr ',' '\\n' | sort -u | awk 'system(\"[ -e /usr/local/lib/\" $1 \" ]\") == 0 { next } { print \"so:\" $1 }' )\" && apk add --virtual .composer-phpext-rundeps $runDeps && apk del .build-deps", 602 }, 603 { 604 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 503010161, time.UTC)}, 605 CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_ALLOW_SUPERUSER=1", 606 EmptyLayer: true, 607 }, 608 { 609 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 50, 775378559, time.UTC)}, 610 CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_HOME=/tmp", 611 EmptyLayer: true, 612 }, 613 { 614 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 51, 35012363, time.UTC)}, 615 CreatedBy: "/bin/sh -c #(nop) ENV COMPOSER_VERSION=1.7.2", 616 EmptyLayer: true, 617 }, 618 { 619 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 52, 491402624, time.UTC)}, 620 CreatedBy: "/bin/sh -c curl --silent --fail --location --retry 3 --output /tmp/installer.php --url https://raw.githubusercontent.com/composer/getcomposer.org/b107d959a5924af895807021fcef4ffec5a76aa9/web/installer && php -r \" \\$signature = '544e09ee996cdf60ece3804abc52599c22b1f40f4323403c44d44fdfdd586475ca9813a858088ffbc1f233e9b180f061'; \\$hash = hash('SHA384', file_get_contents('/tmp/installer.php')); if (!hash_equals(\\$signature, \\$hash)) { unlink('/tmp/installer.php'); echo 'Integrity check failed, installer is either corrupt or worse.' . PHP_EOL; exit(1); }\" && php /tmp/installer.php --no-ansi --install-dir=/usr/bin --filename=composer --version=${COMPOSER_VERSION} && composer --ansi --version --no-interaction && rm -rf /tmp/* /tmp/.htaccess", 621 }, 622 { 623 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 52, 948859545, time.UTC)}, 624 CreatedBy: "/bin/sh -c #(nop) COPY file:295943a303e8f27de4302b6aa3687bce4b1d1392335efaaab9ecd37bec5ab4c5 in /docker-entrypoint.sh ", 625 }, 626 { 627 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 295399872, time.UTC)}, 628 CreatedBy: "/bin/sh -c #(nop) WORKDIR /app", 629 }, 630 { 631 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 582920705, time.UTC)}, 632 CreatedBy: "/bin/sh -c #(nop) ENTRYPOINT [\"/bin/sh\" \"/docker-entrypoint.sh\"]", 633 EmptyLayer: true, 634 }, 635 { 636 Created: v1.Time{Time: time.Date(2018, 10, 15, 21, 28, 53, 798628678, time.UTC)}, 637 CreatedBy: "/bin/sh -c #(nop) CMD [\"composer\"]", 638 EmptyLayer: true, 639 }, 640 { 641 Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 211142800, time.UTC)}, 642 CreatedBy: "/bin/sh -c #(nop) ADD file:842584685f26edb24dc305d76894f51cfda2bad0c24a05e727f9d4905d184a70 in /php-app/composer.lock ", 643 }, 644 { 645 Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 583779000, time.UTC)}, 646 CreatedBy: "/bin/sh -c #(nop) ADD file:c6d0373d380252b91829a5bb3c81d5b1afa574c91cef7752d18170a231c31f6d in /ruby-app/Gemfile.lock ", 647 }, 648 { 649 Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 57, 921730100, time.UTC)}, 650 CreatedBy: "/bin/sh -c #(nop) ADD file:54a1c52556a5ebe98fd124f51c25d071f9e29e2714c72c80d6d3d254b9e83386 in /node-app/package-lock.json ", 651 }, 652 { 653 Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 58, 311593100, time.UTC)}, 654 CreatedBy: "/bin/sh -c #(nop) ADD file:097d32f46acde76c4da9e55f17110d69d02cc6d16c86da907980da335fc0fc5f in /python-app/Pipfile.lock ", 655 }, 656 { 657 Created: v1.Time{Time: time.Date(2019, 8, 7, 7, 25, 58, 651649800, time.UTC)}, 658 CreatedBy: "/bin/sh -c #(nop) ADD file:7f147d85de19bfb905c260a0c175f227a433259022c163017b96d0efacdcd105 in /rust-app/Cargo.lock ", 659 }, 660 }, 661 }, 662 }, 663 }, 664 } 665 666 t.Helper() 667 ctx := namespaces.WithNamespace(context.Background(), namespace) 668 client := setupContainerd(t, ctx, namespace) 669 670 for _, tt := range tests { 671 t.Run(tt.name, func(t *testing.T) { 672 cacheDir := t.TempDir() 673 c, err := cache.NewFSCache(cacheDir) 674 require.NoError(t, err) 675 676 defer func() { 677 c.Clear() 678 c.Close() 679 }() 680 681 archive, err := os.Open(tt.tarArchive) 682 require.NoError(t, err) 683 684 uncompressedArchive, err := gzip.NewReader(archive) 685 require.NoError(t, err) 686 defer archive.Close() 687 688 _, err = client.Import(ctx, uncompressedArchive) 689 require.NoError(t, err) 690 691 // Enable only containerd 692 img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{ 693 ImageSources: types.ImageSources{types.ContainerdImageSource}, 694 }) 695 require.NoError(t, err) 696 defer cleanup() 697 698 ar, err := aimage.NewArtifact(img, c, artifact.Option{ 699 DisabledAnalyzers: []analyzer.Type{ 700 analyzer.TypeExecutable, 701 analyzer.TypeLicenseFile, 702 }, 703 }) 704 require.NoError(t, err) 705 706 ref, err := ar.Inspect(ctx) 707 require.NoError(t, err) 708 require.Equal(t, tt.wantMetadata, ref.ImageMetadata) 709 710 a := applier.NewApplier(c) 711 got, err := a.ApplyLayers(ref.ID, ref.BlobIDs) 712 require.NoError(t, err) 713 714 tag := strings.Split(tt.imageName, ":")[1] 715 goldenFile := fmt.Sprintf("testdata/goldens/packages/%s.json.golden", tag) 716 717 if *update { 718 b, err := json.MarshalIndent(got.Packages, "", " ") 719 require.NoError(t, err) 720 err = os.WriteFile(goldenFile, b, 0666) 721 require.NoError(t, err) 722 } 723 724 // Parse a golden file 725 golden, err := os.Open(goldenFile) 726 require.NoError(t, err) 727 defer golden.Close() 728 729 var wantPkgs types.Packages 730 err = json.NewDecoder(golden).Decode(&wantPkgs) 731 require.NoError(t, err) 732 733 // Assert 734 assert.Equal(t, wantPkgs, got.Packages) 735 }) 736 } 737 } 738 739 func TestContainerd_PullImage(t *testing.T) { 740 // Each architecture needs different images and test cases. 741 // Currently only amd64 architecture is supported 742 if runtime.GOARCH != "amd64" { 743 t.Skip("'Containerd' test only supports amd64 architecture") 744 } 745 746 tests := []struct { 747 name string 748 imageName string 749 wantMetadata types.ImageMetadata 750 }{ 751 { 752 name: "remote alpine 3.10", 753 imageName: "ghcr.io/devseccon/trivy-test-images:alpine-310", 754 wantMetadata: types.ImageMetadata{ 755 ID: "sha256:961769676411f082461f9ef46626dd7a2d1e2b2a38e6a44364bcbecf51e66dd4", 756 DiffIDs: []string{ 757 "sha256:03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 758 }, 759 RepoTags: []string{"ghcr.io/devseccon/trivy-test-images:alpine-310"}, 760 RepoDigests: []string{"ghcr.io/devseccon/trivy-test-images@sha256:72c42ed48c3a2db31b7dafe17d275b634664a708d901ec9fd57b1529280f01fb"}, 761 ConfigFile: v1.ConfigFile{ 762 Architecture: "amd64", 763 Created: v1.Time{ 764 Time: time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC), 765 }, 766 OS: "linux", 767 RootFS: v1.RootFS{ 768 Type: "layers", 769 DiffIDs: []v1.Hash{ 770 { 771 Algorithm: "sha256", 772 Hex: "03901b4a2ea88eeaad62dbe59b072b28b6efa00491962b8741081c5df50c65e0", 773 }, 774 }, 775 }, 776 Config: v1.Config{ 777 Cmd: []string{ 778 "/bin/sh", 779 }, 780 Env: []string{ 781 "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", 782 }, 783 ArgsEscaped: false, 784 }, 785 History: []v1.History{ 786 { 787 Created: v1.Time{time.Date(2019, 8, 20, 20, 19, 55, 62606894, time.UTC)}, 788 CreatedBy: "/bin/sh -c #(nop) ADD file:fe64057fbb83dccb960efabbf1cd8777920ef279a7fa8dbca0a8801c651bdf7c in / ", 789 }, 790 { 791 Created: v1.Time{time.Date(2019, 8, 20, 20, 19, 55, 211423266, time.UTC)}, 792 CreatedBy: "/bin/sh -c #(nop) CMD [\"/bin/sh\"]", 793 EmptyLayer: true, 794 }, 795 }, 796 }, 797 }, 798 }, 799 } 800 801 namespace := "default" 802 ctx := namespaces.WithNamespace(context.Background(), namespace) 803 client := setupContainerd(t, ctx, namespace) 804 805 for _, tt := range tests { 806 t.Run(tt.name, func(t *testing.T) { 807 cacheDir := t.TempDir() 808 c, err := cache.NewFSCache(cacheDir) 809 require.NoError(t, err) 810 811 defer func() { 812 c.Clear() 813 c.Close() 814 }() 815 816 _, err = client.Pull(ctx, tt.imageName) 817 require.NoError(t, err) 818 819 // Enable only containerd 820 img, cleanup, err := image.NewContainerImage(ctx, tt.imageName, types.ImageOptions{ 821 ImageSources: types.ImageSources{types.ContainerdImageSource}, 822 }) 823 require.NoError(t, err) 824 defer cleanup() 825 826 art, err := aimage.NewArtifact(img, c, artifact.Option{ 827 DisabledAnalyzers: []analyzer.Type{ 828 analyzer.TypeExecutable, 829 analyzer.TypeLicenseFile, 830 }, 831 }) 832 require.NoError(t, err) 833 require.NotNil(t, art) 834 835 ref, err := art.Inspect(context.Background()) 836 require.NoError(t, err) 837 require.Equal(t, tt.wantMetadata, ref.ImageMetadata) 838 839 a := applier.NewApplier(c) 840 got, err := a.ApplyLayers(ref.ID, ref.BlobIDs) 841 require.NoError(t, err) 842 843 // Parse a golden file 844 tag := strings.Split(tt.imageName, ":")[1] 845 golden, err := os.Open(fmt.Sprintf("testdata/goldens/packages/%s.json.golden", tag)) 846 require.NoError(t, err) 847 848 var wantPkgs types.Packages 849 err = json.NewDecoder(golden).Decode(&wantPkgs) 850 require.NoError(t, err) 851 852 // Assert 853 assert.Equal(t, wantPkgs, got.Packages) 854 }) 855 } 856 }