github.com/noqcks/syft@v0.0.0-20230920222752-a9e2c4e288e5/syft/pkg/cataloger/apkdb/parse_apk_db_test.go (about) 1 package apkdb 2 3 import ( 4 "io" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 10 "github.com/google/go-cmp/cmp" 11 "github.com/google/go-cmp/cmp/cmpopts" 12 "github.com/stretchr/testify/assert" 13 "github.com/stretchr/testify/require" 14 15 "github.com/anchore/syft/syft/artifact" 16 "github.com/anchore/syft/syft/file" 17 "github.com/anchore/syft/syft/linux" 18 "github.com/anchore/syft/syft/pkg" 19 "github.com/anchore/syft/syft/pkg/cataloger/generic" 20 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 21 ) 22 23 func TestExtraFileAttributes(t *testing.T) { 24 tests := []struct { 25 name string 26 expected pkg.ApkMetadata 27 }{ 28 { 29 name: "test extra file attributes (checksum) are ignored", 30 expected: pkg.ApkMetadata{ 31 Files: []pkg.ApkFileRecord{ 32 { 33 Path: "/usr", 34 }, 35 { 36 Path: "/usr/lib", 37 }, 38 { 39 Path: "/usr/lib/jvm", 40 }, 41 { 42 Path: "/usr/lib/jvm/java-1.8-openjdk", 43 }, 44 { 45 Path: "/usr/lib/jvm/java-1.8-openjdk/bin", 46 }, 47 { 48 Path: "/usr/lib/jvm/java-1.8-openjdk/bin/policytool", 49 OwnerUID: "0", 50 OwnerGID: "0", 51 Permissions: "755", 52 Digest: &file.Digest{ 53 Algorithm: "'Q1'+base64(sha1)", 54 Value: "Q1M0C9qfC/+kdRiOodeihG2GMRtkE=", 55 }, 56 }, 57 }, 58 }, 59 }, 60 } 61 62 for _, test := range tests { 63 t.Run(test.name, func(t *testing.T) { 64 fixturePath := "test-fixtures/extra-file-attributes" 65 lrc := newLocationReadCloser(t, fixturePath) 66 67 pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc) 68 assert.NoError(t, err) 69 require.Len(t, pkgs, 1) 70 metadata := pkgs[0].Metadata.(pkg.ApkMetadata) 71 72 if diff := cmp.Diff(test.expected.Files, metadata.Files); diff != "" { 73 t.Errorf("Files mismatch (-want +got):\n%s", diff) 74 } 75 }) 76 } 77 } 78 79 func TestSinglePackageDetails(t *testing.T) { 80 tests := []struct { 81 fixture string 82 expected pkg.Package 83 }{ 84 { 85 fixture: "test-fixtures/single", 86 expected: pkg.Package{ 87 Name: "musl-utils", 88 Version: "1.1.24-r2", 89 Licenses: pkg.NewLicenseSet( 90 pkg.NewLicense("MIT"), 91 pkg.NewLicense("BSD"), 92 pkg.NewLicense("GPL2+"), 93 ), 94 Type: pkg.ApkPkg, 95 MetadataType: pkg.ApkMetadataType, 96 Metadata: pkg.ApkMetadata{ 97 Package: "musl-utils", 98 OriginPackage: "musl", 99 Version: "1.1.24-r2", 100 Description: "the musl c library (libc) implementation", 101 Maintainer: "Timo Teräs <timo.teras@iki.fi>", 102 Architecture: "x86_64", 103 URL: "https://musl.libc.org/", 104 Size: 37944, 105 InstalledSize: 151552, 106 Dependencies: []string{"scanelf", "so:libc.musl-x86_64.so.1"}, 107 Provides: []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"}, 108 Checksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=", 109 GitCommit: "4024cc3b29ad4c65544ad068b8f59172b5494306", 110 Files: []pkg.ApkFileRecord{ 111 { 112 Path: "/sbin", 113 }, 114 { 115 Path: "/sbin/ldconfig", 116 OwnerUID: "0", 117 OwnerGID: "0", 118 Permissions: "755", 119 Digest: &file.Digest{ 120 Algorithm: "'Q1'+base64(sha1)", 121 Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=", 122 }, 123 }, 124 { 125 Path: "/usr", 126 }, 127 { 128 Path: "/usr/bin", 129 }, 130 { 131 Path: "/usr/bin/iconv", 132 OwnerUID: "0", 133 OwnerGID: "0", 134 Permissions: "755", 135 Digest: &file.Digest{ 136 Algorithm: "'Q1'+base64(sha1)", 137 Value: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=", 138 }, 139 }, 140 { 141 Path: "/usr/bin/ldd", 142 OwnerUID: "0", 143 OwnerGID: "0", 144 Permissions: "755", 145 Digest: &file.Digest{ 146 Algorithm: "'Q1'+base64(sha1)", 147 Value: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=", 148 }, 149 }, 150 { 151 Path: "/usr/bin/getconf", 152 OwnerUID: "0", 153 OwnerGID: "0", 154 Permissions: "755", 155 Digest: &file.Digest{ 156 Algorithm: "'Q1'+base64(sha1)", 157 Value: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=", 158 }, 159 }, 160 { 161 Path: "/usr/bin/getent", 162 OwnerUID: "0", 163 OwnerGID: "0", 164 Permissions: "755", 165 Digest: &file.Digest{ 166 Algorithm: "'Q1'+base64(sha1)", 167 Value: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=", 168 }, 169 }, 170 }, 171 }, 172 }, 173 }, 174 { 175 fixture: "test-fixtures/empty-deps-and-provides", 176 expected: pkg.Package{ 177 Name: "alpine-baselayout-data", 178 Version: "3.4.0-r0", 179 Licenses: pkg.NewLicenseSet( 180 pkg.NewLicense("GPL-2.0-only"), 181 ), 182 Type: pkg.ApkPkg, 183 MetadataType: pkg.ApkMetadataType, 184 Metadata: pkg.ApkMetadata{ 185 Package: "alpine-baselayout-data", 186 OriginPackage: "alpine-baselayout", 187 Version: "3.4.0-r0", 188 Description: "Alpine base dir structure and init scripts", 189 Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", 190 Architecture: "x86_64", 191 URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout", 192 Size: 11664, 193 InstalledSize: 77824, 194 Dependencies: []string{}, 195 Provides: []string{}, 196 Checksum: "Q15ffjKT28lB7iSXjzpI/eDdYRCwM=", 197 GitCommit: "bd965a7ebf7fd8f07d7a0cc0d7375bf3e4eb9b24", 198 Files: []pkg.ApkFileRecord{ 199 {Path: "/etc"}, 200 {Path: "/etc/fstab"}, 201 {Path: "/etc/group"}, 202 {Path: "/etc/hostname"}, 203 {Path: "/etc/hosts"}, 204 {Path: "/etc/inittab"}, 205 {Path: "/etc/modules"}, 206 {Path: "/etc/mtab", OwnerUID: "0", OwnerGID: "0", Permissions: "0777"}, 207 {Path: "/etc/nsswitch.conf"}, 208 {Path: "/etc/passwd"}, 209 {Path: "/etc/profile"}, 210 {Path: "/etc/protocols"}, 211 {Path: "/etc/services"}, 212 {Path: "/etc/shadow", OwnerUID: "0", OwnerGID: "148", Permissions: "0640"}, 213 {Path: "/etc/shells"}, 214 {Path: "/etc/sysctl.conf"}, 215 }, 216 }, 217 }, 218 }, 219 { 220 fixture: "test-fixtures/base", 221 expected: pkg.Package{ 222 Name: "alpine-baselayout", 223 Version: "3.2.0-r6", 224 Licenses: pkg.NewLicenseSet( 225 pkg.NewLicense("GPL-2.0-only"), 226 ), 227 Type: pkg.ApkPkg, 228 PURL: "", 229 MetadataType: pkg.ApkMetadataType, 230 Metadata: pkg.ApkMetadata{ 231 Package: "alpine-baselayout", 232 OriginPackage: "alpine-baselayout", 233 Version: "3.2.0-r6", 234 Description: "Alpine base dir structure and init scripts", 235 Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", 236 Architecture: "x86_64", 237 URL: "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout", 238 Size: 19917, 239 InstalledSize: 409600, 240 Dependencies: []string{"/bin/sh", "so:libc.musl-x86_64.so.1"}, 241 Provides: []string{"cmd:mkmntdirs"}, 242 Checksum: "Q1myMNfd7u5v5UTgNHeq1e31qTjZU=", 243 GitCommit: "e1c51734fa96fa4bac92e9f14a474324c67916fc", 244 Files: []pkg.ApkFileRecord{ 245 { 246 Path: "/dev", 247 }, 248 { 249 Path: "/dev/pts", 250 }, 251 { 252 Path: "/dev/shm", 253 }, 254 { 255 Path: "/etc", 256 }, 257 { 258 Path: "/etc/fstab", 259 Digest: &file.Digest{ 260 Algorithm: "'Q1'+base64(sha1)", 261 Value: "Q11Q7hNe8QpDS531guqCdrXBzoA/o=", 262 }, 263 }, 264 { 265 Path: "/etc/group", 266 Digest: &file.Digest{ 267 Algorithm: "'Q1'+base64(sha1)", 268 Value: "Q1oJ16xWudgKOrXIEquEDzlF2Lsm4=", 269 }, 270 }, 271 { 272 Path: "/etc/hostname", 273 Digest: &file.Digest{ 274 Algorithm: "'Q1'+base64(sha1)", 275 Value: "Q16nVwYVXP/tChvUPdukVD2ifXOmc=", 276 }, 277 }, 278 { 279 Path: "/etc/hosts", 280 Digest: &file.Digest{ 281 Algorithm: "'Q1'+base64(sha1)", 282 Value: "Q1BD6zJKZTRWyqGnPi4tSfd3krsMU=", 283 }, 284 }, 285 { 286 Path: "/etc/inittab", 287 Digest: &file.Digest{ 288 Algorithm: "'Q1'+base64(sha1)", 289 Value: "Q1TsthbhW7QzWRe1E/NKwTOuD4pHc=", 290 }, 291 }, 292 { 293 Path: "/etc/modules", 294 Digest: &file.Digest{ 295 Algorithm: "'Q1'+base64(sha1)", 296 Value: "Q1toogjUipHGcMgECgPJX64SwUT1M=", 297 }, 298 }, 299 { 300 Path: "/etc/motd", 301 Digest: &file.Digest{ 302 Algorithm: "'Q1'+base64(sha1)", 303 Value: "Q1XmduVVNURHQ27TvYp1Lr5TMtFcA=", 304 }, 305 }, 306 { 307 Path: "/etc/mtab", 308 OwnerUID: "0", 309 OwnerGID: "0", 310 Permissions: "777", 311 Digest: &file.Digest{ 312 Algorithm: "'Q1'+base64(sha1)", 313 Value: "Q1kiljhXXH1LlQroHsEJIkPZg2eiw=", 314 }, 315 }, 316 { 317 Path: "/etc/passwd", 318 Digest: &file.Digest{ 319 Algorithm: "'Q1'+base64(sha1)", 320 Value: "Q1TchuuLUfur0izvfZQZxgN/LJhB8=", 321 }, 322 }, 323 { 324 Path: "/etc/profile", 325 Digest: &file.Digest{ 326 Algorithm: "'Q1'+base64(sha1)", 327 Value: "Q1KpFb8kl5LvwXWlY3e58FNsjrI34=", 328 }, 329 }, 330 { 331 Path: "/etc/protocols", 332 Digest: &file.Digest{ 333 Algorithm: "'Q1'+base64(sha1)", 334 Value: "Q13FqXUnvuOpMDrH/6rehxuYAEE34=", 335 }, 336 }, 337 { 338 Path: "/etc/services", 339 Digest: &file.Digest{ 340 Algorithm: "'Q1'+base64(sha1)", 341 Value: "Q1C6HJNgQvLWqt5VY+n7MZJ1rsDuY=", 342 }, 343 }, 344 { 345 Path: "/etc/shadow", 346 OwnerUID: "0", 347 OwnerGID: "42", 348 Permissions: "640", 349 Digest: &file.Digest{ 350 Algorithm: "'Q1'+base64(sha1)", 351 Value: "Q1ltrPIAW2zHeDiajsex2Bdmq3uqA=", 352 }, 353 }, 354 { 355 Path: "/etc/shells", 356 Digest: &file.Digest{ 357 Algorithm: "'Q1'+base64(sha1)", 358 Value: "Q1ojm2YdpCJ6B/apGDaZ/Sdb2xJkA=", 359 }, 360 }, 361 { 362 Path: "/etc/sysctl.conf", 363 Digest: &file.Digest{ 364 Algorithm: "'Q1'+base64(sha1)", 365 Value: "Q14upz3tfnNxZkIEsUhWn7Xoiw96g=", 366 }, 367 }, 368 { 369 Path: "/etc/apk", 370 }, 371 { 372 Path: "/etc/conf.d", 373 }, 374 { 375 Path: "/etc/crontabs", 376 }, 377 { 378 Path: "/etc/crontabs/root", 379 OwnerUID: "0", 380 OwnerGID: "0", 381 Permissions: "600", 382 Digest: &file.Digest{ 383 Algorithm: "'Q1'+base64(sha1)", 384 Value: "Q1vfk1apUWI4yLJGhhNRd0kJixfvY=", 385 }, 386 }, 387 { 388 Path: "/etc/init.d", 389 }, 390 { 391 Path: "/etc/modprobe.d", 392 }, 393 { 394 Path: "/etc/modprobe.d/aliases.conf", 395 Digest: &file.Digest{ 396 Algorithm: "'Q1'+base64(sha1)", 397 Value: "Q1WUbh6TBYNVK7e4Y+uUvLs/7viqk=", 398 }, 399 }, 400 { 401 Path: "/etc/modprobe.d/blacklist.conf", 402 Digest: &file.Digest{ 403 Algorithm: "'Q1'+base64(sha1)", 404 Value: "Q1xxYGU6S6TLQvb7ervPrWWwAWqMg=", 405 }, 406 }, 407 { 408 Path: "/etc/modprobe.d/i386.conf", 409 Digest: &file.Digest{ 410 Algorithm: "'Q1'+base64(sha1)", 411 Value: "Q1pnay/njn6ol9cCssL7KiZZ8etlc=", 412 }, 413 }, 414 { 415 Path: "/etc/modprobe.d/kms.conf", 416 Digest: &file.Digest{ 417 Algorithm: "'Q1'+base64(sha1)", 418 Value: "Q1ynbLn3GYDpvajba/ldp1niayeog=", 419 }, 420 }, 421 { 422 Path: "/etc/modules-load.d", 423 }, 424 { 425 Path: "/etc/network", 426 }, 427 { 428 Path: "/etc/network/if-down.d", 429 }, 430 { 431 Path: "/etc/network/if-post-down.d", 432 }, 433 { 434 Path: "/etc/network/if-pre-up.d", 435 }, 436 { 437 Path: "/etc/network/if-up.d", 438 }, 439 { 440 Path: "/etc/opt", 441 }, 442 { 443 Path: "/etc/periodic", 444 }, 445 { 446 Path: "/etc/periodic/15min", 447 }, 448 { 449 Path: "/etc/periodic/daily", 450 }, 451 { 452 Path: "/etc/periodic/hourly", 453 }, 454 { 455 Path: "/etc/periodic/monthly", 456 }, 457 { 458 Path: "/etc/periodic/weekly", 459 }, 460 { 461 Path: "/etc/profile.d", 462 }, 463 { 464 Path: "/etc/profile.d/color_prompt", 465 Digest: &file.Digest{ 466 Algorithm: "'Q1'+base64(sha1)", 467 Value: "Q10wL23GuSCVfumMRgakabUI6EsSk=", 468 }, 469 }, 470 { 471 Path: "/etc/profile.d/locale", 472 Digest: &file.Digest{ 473 Algorithm: "'Q1'+base64(sha1)", 474 Value: "Q1R4bIEpnKxxOSrlnZy9AoawqZ5DU=", 475 }, 476 }, 477 { 478 Path: "/etc/sysctl.d", 479 }, 480 { 481 Path: "/home", 482 }, 483 { 484 Path: "/lib", 485 }, 486 { 487 Path: "/lib/firmware", 488 }, 489 { 490 Path: "/lib/mdev", 491 }, 492 { 493 Path: "/lib/modules-load.d", 494 }, 495 { 496 Path: "/lib/sysctl.d", 497 }, 498 { 499 Path: "/lib/sysctl.d/00-alpine.conf", 500 Digest: &file.Digest{ 501 Algorithm: "'Q1'+base64(sha1)", 502 Value: "Q1HpElzW1xEgmKfERtTy7oommnq6c=", 503 }, 504 }, 505 { 506 Path: "/media", 507 }, 508 { 509 Path: "/media/cdrom", 510 }, 511 { 512 Path: "/media/floppy", 513 }, 514 { 515 Path: "/media/usb", 516 }, 517 { 518 Path: "/mnt", 519 }, 520 { 521 Path: "/opt", 522 }, 523 { 524 Path: "/proc", 525 }, 526 { 527 Path: "/root", 528 OwnerUID: "0", 529 OwnerGID: "0", 530 Permissions: "700", 531 }, 532 { 533 Path: "/run", 534 }, 535 { 536 Path: "/sbin", 537 }, 538 { 539 Path: "/sbin/mkmntdirs", 540 OwnerUID: "0", 541 OwnerGID: "0", 542 Permissions: "755", 543 Digest: &file.Digest{ 544 Algorithm: "'Q1'+base64(sha1)", 545 Value: "Q1YeuSmC7iDbEWrusPzA/zUQF6YSg=", 546 }, 547 }, 548 { 549 Path: "/srv", 550 }, 551 { 552 Path: "/sys", 553 }, 554 { 555 Path: "/tmp", 556 OwnerUID: "0", 557 OwnerGID: "0", 558 Permissions: "1777", 559 }, 560 { 561 Path: "/usr", 562 }, 563 { 564 Path: "/usr/lib", 565 }, 566 { 567 Path: "/usr/lib/modules-load.d", 568 }, 569 { 570 Path: "/usr/local", 571 }, 572 { 573 Path: "/usr/local/bin", 574 }, 575 { 576 Path: "/usr/local/lib", 577 }, 578 { 579 Path: "/usr/local/share", 580 }, 581 { 582 Path: "/usr/sbin", 583 }, 584 { 585 Path: "/usr/share", 586 }, 587 { 588 Path: "/usr/share/man", 589 }, 590 { 591 Path: "/usr/share/misc", 592 }, 593 { 594 Path: "/var", 595 }, 596 { 597 Path: "/var/run", 598 OwnerUID: "0", 599 OwnerGID: "0", 600 Permissions: "777", 601 Digest: &file.Digest{ 602 Algorithm: "'Q1'+base64(sha1)", 603 Value: "Q11/SNZz/8cK2dSKK+cJpVrZIuF4Q=", 604 }, 605 }, 606 { 607 Path: "/var/cache", 608 }, 609 { 610 Path: "/var/cache/misc", 611 }, 612 { 613 Path: "/var/empty", 614 OwnerUID: "0", 615 OwnerGID: "0", 616 Permissions: "555", 617 }, 618 { 619 Path: "/var/lib", 620 }, 621 { 622 Path: "/var/lib/misc", 623 }, 624 { 625 Path: "/var/local", 626 }, 627 { 628 Path: "/var/lock", 629 }, 630 { 631 Path: "/var/lock/subsys", 632 }, 633 { 634 Path: "/var/log", 635 }, 636 { 637 Path: "/var/mail", 638 }, 639 { 640 Path: "/var/opt", 641 }, 642 { 643 Path: "/var/spool", 644 }, 645 { 646 Path: "/var/spool/mail", 647 OwnerUID: "0", 648 OwnerGID: "0", 649 Permissions: "777", 650 Digest: &file.Digest{ 651 Algorithm: "'Q1'+base64(sha1)", 652 Value: "Q1dzbdazYZA2nTzSIG3YyNw7d4Juc=", 653 }, 654 }, 655 { 656 Path: "/var/spool/cron", 657 }, 658 { 659 Path: "/var/spool/cron/crontabs", 660 OwnerUID: "0", 661 OwnerGID: "0", 662 Permissions: "777", 663 Digest: &file.Digest{ 664 Algorithm: "'Q1'+base64(sha1)", 665 Value: "Q1OFZt+ZMp7j0Gny0rqSKuWJyqYmA=", 666 }, 667 }, 668 { 669 Path: "/var/tmp", 670 OwnerUID: "0", 671 OwnerGID: "0", 672 Permissions: "1777", 673 }, 674 }, 675 }, 676 }, 677 }, 678 } 679 680 for _, test := range tests { 681 t.Run(test.fixture, func(t *testing.T) { 682 fixtureLocation := file.NewLocation(test.fixture) 683 test.expected.Locations = file.NewLocationSet(fixtureLocation) 684 licenses := test.expected.Licenses.ToSlice() 685 for i := range licenses { 686 licenses[i].Locations.Add(fixtureLocation) 687 } 688 test.expected.Licenses = pkg.NewLicenseSet(licenses...) 689 pkgtest.TestFileParser(t, test.fixture, parseApkDB, []pkg.Package{test.expected}, nil) 690 }) 691 } 692 } 693 694 func TestMultiplePackages(t *testing.T) { 695 fixture := "test-fixtures/multiple" 696 location := file.NewLocation(fixture) 697 fixtureLocationSet := file.NewLocationSet(location) 698 expectedPkgs := []pkg.Package{ 699 { 700 Name: "libc-utils", 701 Version: "0.7.2-r0", 702 Licenses: pkg.NewLicenseSet( 703 pkg.NewLicenseFromLocations("MPL-2.0 AND MIT", location), 704 ), 705 Type: pkg.ApkPkg, 706 PURL: "pkg:apk/alpine/libc-utils@0.7.2-r0?arch=x86_64&upstream=libc-dev&distro=alpine-3.12", 707 Locations: fixtureLocationSet, 708 MetadataType: pkg.ApkMetadataType, 709 Metadata: pkg.ApkMetadata{ 710 Package: "libc-utils", 711 OriginPackage: "libc-dev", 712 Maintainer: "Natanael Copa <ncopa@alpinelinux.org>", 713 Version: "0.7.2-r0", 714 Architecture: "x86_64", 715 URL: "http://alpinelinux.org", 716 Description: "Meta package to pull in correct libc", 717 Size: 1175, 718 InstalledSize: 4096, 719 Checksum: "Q1p78yvTLG094tHE1+dToJGbmYzQE=", 720 GitCommit: "97b1c2842faa3bfa30f5811ffbf16d5ff9f1a479", 721 Dependencies: []string{"musl-utils"}, 722 Provides: []string{}, 723 Files: []pkg.ApkFileRecord{}, 724 }, 725 }, 726 { 727 Name: "musl-utils", 728 Version: "1.1.24-r2", 729 Type: pkg.ApkPkg, 730 PURL: "pkg:apk/alpine/musl-utils@1.1.24-r2?arch=x86_64&upstream=musl&distro=alpine-3.12", 731 Locations: fixtureLocationSet, 732 Licenses: pkg.NewLicenseSet( 733 pkg.NewLicenseFromLocations("MIT", location), 734 pkg.NewLicenseFromLocations("BSD", location), 735 pkg.NewLicenseFromLocations("GPL2+", location), 736 ), 737 MetadataType: pkg.ApkMetadataType, 738 Metadata: pkg.ApkMetadata{ 739 Package: "musl-utils", 740 OriginPackage: "musl", 741 Version: "1.1.24-r2", 742 Description: "the musl c library (libc) implementation", 743 Maintainer: "Timo Teräs <timo.teras@iki.fi>", 744 Architecture: "x86_64", 745 URL: "https://musl.libc.org/", 746 Size: 37944, 747 InstalledSize: 151552, 748 GitCommit: "4024cc3b29ad4c65544ad068b8f59172b5494306", 749 Dependencies: []string{"scanelf", "so:libc.musl-x86_64.so.1"}, 750 Provides: []string{"cmd:getconf", "cmd:getent", "cmd:iconv", "cmd:ldconfig", "cmd:ldd"}, 751 Checksum: "Q1bTtF5526tETKfL+lnigzIDvm+2o=", 752 Files: []pkg.ApkFileRecord{ 753 { 754 Path: "/sbin", 755 }, 756 { 757 Path: "/sbin/ldconfig", 758 OwnerUID: "0", 759 OwnerGID: "0", 760 Permissions: "755", 761 Digest: &file.Digest{ 762 Algorithm: "'Q1'+base64(sha1)", 763 Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=", 764 }, 765 }, 766 { 767 Path: "/usr", 768 }, 769 { 770 Path: "/usr/bin", 771 }, 772 { 773 Path: "/usr/bin/iconv", 774 OwnerUID: "0", 775 OwnerGID: "0", 776 Permissions: "755", 777 Digest: &file.Digest{ 778 Algorithm: "'Q1'+base64(sha1)", 779 Value: "Q1CVmFbdY+Hv6/jAHl1gec2Kbx1EY=", 780 }, 781 }, 782 { 783 Path: "/usr/bin/ldd", 784 OwnerUID: "0", 785 OwnerGID: "0", 786 Permissions: "755", 787 Digest: &file.Digest{ 788 Algorithm: "'Q1'+base64(sha1)", 789 Value: "Q1yFAhGggmL7ERgbIA7KQxyTzf3ks=", 790 }, 791 }, 792 { 793 Path: "/usr/bin/getconf", 794 OwnerUID: "0", 795 OwnerGID: "0", 796 Permissions: "755", 797 Digest: &file.Digest{ 798 Algorithm: "'Q1'+base64(sha1)", 799 Value: "Q1dAdYK8M/INibRQF5B3Rw7cmNDDA=", 800 }, 801 }, 802 { 803 Path: "/usr/bin/getent", 804 OwnerUID: "0", 805 OwnerGID: "0", 806 Permissions: "755", 807 Digest: &file.Digest{ 808 Algorithm: "'Q1'+base64(sha1)", 809 Value: "Q1eR2Dz/WylabgbWMTkd2+hGmEya4=", 810 }, 811 }, 812 }, 813 }, 814 }, 815 } 816 817 expectedRelationships := []artifact.Relationship{ 818 { 819 From: expectedPkgs[1], // musl-utils 820 To: expectedPkgs[0], // libc-utils 821 Type: artifact.DependencyOfRelationship, 822 Data: nil, 823 }, 824 } 825 826 env := generic.Environment{LinuxRelease: &linux.Release{ 827 ID: "alpine", 828 VersionID: "3.12", 829 }} 830 831 pkgtest.TestFileParserWithEnv(t, fixture, parseApkDB, &env, expectedPkgs, expectedRelationships) 832 } 833 834 func Test_processChecksum(t *testing.T) { 835 tests := []struct { 836 name string 837 value string 838 want file.Digest 839 }{ 840 { 841 name: "md5", 842 value: "38870ede8700535d7382ff66a46fcc2f", 843 want: file.Digest{ 844 Algorithm: "md5", 845 Value: "38870ede8700535d7382ff66a46fcc2f", 846 }, 847 }, 848 { 849 name: "sha1", 850 value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=", 851 want: file.Digest{ 852 Algorithm: "'Q1'+base64(sha1)", 853 Value: "Q1Kja2+POZKxEkUOZqwSjC6kmaED4=", 854 }, 855 }, 856 } 857 858 for _, test := range tests { 859 t.Run(test.name, func(t *testing.T) { 860 assert.Equal(t, &test.want, processChecksum(test.value)) 861 }) 862 } 863 } 864 865 func Test_discoverPackageDependencies(t *testing.T) { 866 tests := []struct { 867 name string 868 genFn func() ([]pkg.Package, []artifact.Relationship) 869 }{ 870 { 871 name: "has no dependency", 872 genFn: func() ([]pkg.Package, []artifact.Relationship) { 873 a := pkg.Package{ 874 Name: "package-a", 875 Metadata: pkg.ApkMetadata{ 876 Provides: []string{"a-thing"}, 877 }, 878 } 879 a.SetID() 880 b := pkg.Package{ 881 Name: "package-b", 882 Metadata: pkg.ApkMetadata{ 883 Provides: []string{"b-thing"}, 884 }, 885 } 886 b.SetID() 887 888 return []pkg.Package{a, b}, nil 889 }, 890 }, 891 { 892 name: "has 1 dependency", 893 genFn: func() ([]pkg.Package, []artifact.Relationship) { 894 a := pkg.Package{ 895 Name: "package-a", 896 Metadata: pkg.ApkMetadata{ 897 Dependencies: []string{"b-thing"}, 898 }, 899 } 900 a.SetID() 901 b := pkg.Package{ 902 Name: "package-b", 903 Metadata: pkg.ApkMetadata{ 904 Provides: []string{"b-thing"}, 905 }, 906 } 907 b.SetID() 908 909 return []pkg.Package{a, b}, []artifact.Relationship{ 910 { 911 From: b, 912 To: a, 913 Type: artifact.DependencyOfRelationship, 914 }, 915 } 916 }, 917 }, 918 { 919 name: "strip version specifiers", 920 genFn: func() ([]pkg.Package, []artifact.Relationship) { 921 a := pkg.Package{ 922 Name: "package-a", 923 Metadata: pkg.ApkMetadata{ 924 Dependencies: []string{"so:libc.musl-x86_64.so.1"}, 925 }, 926 } 927 a.SetID() 928 b := pkg.Package{ 929 Name: "package-b", 930 Metadata: pkg.ApkMetadata{ 931 Provides: []string{"so:libc.musl-x86_64.so.1=1"}, 932 }, 933 } 934 b.SetID() 935 936 return []pkg.Package{a, b}, []artifact.Relationship{ 937 { 938 From: b, 939 To: a, 940 Type: artifact.DependencyOfRelationship, 941 }, 942 } 943 }, 944 }, 945 { 946 name: "strip version specifiers with empty provides value", 947 genFn: func() ([]pkg.Package, []artifact.Relationship) { 948 a := pkg.Package{ 949 Name: "package-a", 950 Metadata: pkg.ApkMetadata{ 951 Dependencies: []string{"so:libc.musl-x86_64.so.1"}, 952 }, 953 } 954 a.SetID() 955 b := pkg.Package{ 956 Name: "package-b", 957 Metadata: pkg.ApkMetadata{ 958 Provides: []string{""}, 959 }, 960 } 961 b.SetID() 962 963 return []pkg.Package{a, b}, nil 964 }, 965 }, 966 { 967 name: "depends on package name", 968 genFn: func() ([]pkg.Package, []artifact.Relationship) { 969 a := pkg.Package{ 970 Name: "package-a", 971 Metadata: pkg.ApkMetadata{ 972 Dependencies: []string{"musl>=1.2"}, 973 }, 974 } 975 a.SetID() 976 b := pkg.Package{ 977 Name: "musl", 978 Metadata: pkg.ApkMetadata{ 979 Provides: []string{"so:libc.musl-x86_64.so.1=1"}, 980 }, 981 } 982 b.SetID() 983 984 return []pkg.Package{a, b}, []artifact.Relationship{ 985 { 986 From: b, 987 To: a, 988 Type: artifact.DependencyOfRelationship, 989 }, 990 } 991 }, 992 }, 993 { 994 name: "depends on package file", 995 genFn: func() ([]pkg.Package, []artifact.Relationship) { 996 a := pkg.Package{ 997 Name: "alpine-baselayout", 998 Metadata: pkg.ApkMetadata{ 999 Dependencies: []string{"/bin/sh"}, 1000 }, 1001 } 1002 a.SetID() 1003 b := pkg.Package{ 1004 Name: "busybox", 1005 Metadata: pkg.ApkMetadata{ 1006 Provides: []string{"/bin/sh"}, 1007 }, 1008 } 1009 b.SetID() 1010 1011 return []pkg.Package{a, b}, []artifact.Relationship{ 1012 { 1013 From: b, 1014 To: a, 1015 Type: artifact.DependencyOfRelationship, 1016 }, 1017 } 1018 }, 1019 }, 1020 } 1021 1022 for _, test := range tests { 1023 t.Run(test.name, func(t *testing.T) { 1024 pkgs, wantRelationships := test.genFn() 1025 gotRelationships := discoverPackageDependencies(pkgs) 1026 d := cmp.Diff(wantRelationships, gotRelationships, cmpopts.IgnoreUnexported(pkg.Package{}, file.LocationSet{}, pkg.LicenseSet{})) 1027 if d != "" { 1028 t.Fail() 1029 t.Log(d) 1030 } 1031 }) 1032 } 1033 } 1034 1035 func TestPackageDbDependenciesByParse(t *testing.T) { 1036 tests := []struct { 1037 fixture string 1038 expected map[string][]string 1039 }{ 1040 { 1041 fixture: "test-fixtures/installed", 1042 expected: map[string][]string{ 1043 "alpine-baselayout": {"alpine-baselayout-data", "busybox", "musl"}, 1044 "apk-tools": {"musl", "ca-certificates-bundle", "musl", "libcrypto1.1", "libssl1.1", "zlib"}, 1045 "busybox": {"musl"}, 1046 "libc-utils": {"musl-utils"}, 1047 "libcrypto1.1": {"musl"}, 1048 "libssl1.1": {"musl", "libcrypto1.1"}, 1049 "musl-utils": {"scanelf", "musl"}, 1050 "scanelf": {"musl"}, 1051 "ssl_client": {"musl", "libcrypto1.1", "libssl1.1"}, 1052 "zlib": {"musl"}, 1053 }, 1054 }, 1055 } 1056 1057 for _, test := range tests { 1058 t.Run(test.fixture, func(t *testing.T) { 1059 f, err := os.Open(test.fixture) 1060 require.NoError(t, err) 1061 t.Cleanup(func() { require.NoError(t, f.Close()) }) 1062 1063 pkgs, relationships, err := parseApkDB(nil, nil, file.LocationReadCloser{ 1064 Location: file.NewLocation(test.fixture), 1065 ReadCloser: f, 1066 }) 1067 require.NoError(t, err) 1068 1069 pkgsByID := make(map[artifact.ID]pkg.Package) 1070 for _, p := range pkgs { 1071 p.SetID() 1072 pkgsByID[p.ID()] = p 1073 } 1074 1075 actualDependencies := make(map[string][]string) 1076 1077 for _, r := range relationships { 1078 switch r.Type { 1079 case artifact.DependencyOfRelationship: 1080 to := pkgsByID[r.To.ID()] 1081 from := pkgsByID[r.From.ID()] 1082 actualDependencies[to.Name] = append(actualDependencies[to.Name], from.Name) 1083 default: 1084 t.Fatalf("unexpected relationship type: %+v", r.Type) 1085 } 1086 } 1087 1088 if d := cmp.Diff(test.expected, actualDependencies); d != "" { 1089 t.Fail() 1090 t.Log(d) 1091 } 1092 }) 1093 } 1094 } 1095 1096 func Test_parseApkDB_expectedPkgNames(t *testing.T) { 1097 tests := []struct { 1098 fixture string 1099 wantPkgNames []string 1100 wantErr assert.ErrorAssertionFunc 1101 }{ 1102 { 1103 fixture: "very-large-entries", 1104 wantPkgNames: []string{ 1105 "ca-certificates-bundle", 1106 "glibc-locale-posix", 1107 "wolfi-baselayout", 1108 "glibc", 1109 "libcrypto3", 1110 "libssl3", 1111 "zlib", 1112 "apk-tools", 1113 "ncurses-terminfo-base", 1114 "ncurses", 1115 "bash", 1116 "libcap", 1117 "bubblewrap", 1118 "busybox", 1119 "libbrotlicommon1", 1120 "libbrotlidec1", 1121 "libnghttp2-14", 1122 "libcurl4", 1123 "curl", 1124 "expat", 1125 "libpcre2-8-0", 1126 "git", 1127 "binutils", 1128 "libstdc++-dev", 1129 "libgcc", 1130 "libstdc++", 1131 "gmp", 1132 "isl", 1133 "mpfr", 1134 "mpc", 1135 "gcc", 1136 "linux-headers", 1137 "glibc-dev", 1138 "make", 1139 "pkgconf", 1140 "build-base", 1141 "go", 1142 "tree", 1143 "sdk", 1144 }, 1145 wantErr: assert.NoError, 1146 }, 1147 } 1148 1149 for _, test := range tests { 1150 t.Run(test.fixture, func(t *testing.T) { 1151 fixturePath := filepath.Join("test-fixtures", test.fixture) 1152 lrc := newLocationReadCloser(t, fixturePath) 1153 1154 pkgs, _, err := parseApkDB(nil, new(generic.Environment), lrc) 1155 test.wantErr(t, err) 1156 1157 names := toPackageNames(pkgs) 1158 if diff := cmp.Diff(test.wantPkgNames, names); diff != "" { 1159 t.Errorf("Packages mismatch (-want +got):\n%s", diff) 1160 } 1161 }) 1162 } 1163 } 1164 1165 func toPackageNames(pkgs []pkg.Package) []string { 1166 names := make([]string, 0, len(pkgs)) 1167 for _, p := range pkgs { 1168 names = append(names, p.Name) 1169 } 1170 1171 return names 1172 } 1173 1174 func newLocationReadCloser(t *testing.T, path string) file.LocationReadCloser { 1175 f, err := os.Open(path) 1176 require.NoError(t, err) 1177 t.Cleanup(func() { f.Close() }) 1178 1179 return file.NewLocationReadCloser(file.NewLocation(path), f) 1180 } 1181 1182 func Test_stripVersionSpecifier(t *testing.T) { 1183 tests := []struct { 1184 name string 1185 version string 1186 want string 1187 }{ 1188 { 1189 name: "empty expression", 1190 version: "", 1191 want: "", 1192 }, 1193 { 1194 name: "no expression", 1195 version: "cmd:foo", 1196 want: "cmd:foo", 1197 }, 1198 { 1199 name: "=", 1200 version: "cmd:scanelf=1.3.4-r0", 1201 want: "cmd:scanelf", 1202 }, 1203 { 1204 name: ">=", 1205 version: "cmd:scanelf>=1.3.4-r0", 1206 want: "cmd:scanelf", 1207 }, 1208 { 1209 name: "<", 1210 version: "cmd:scanelf<1.3.4-r0", 1211 want: "cmd:scanelf", 1212 }, 1213 } 1214 for _, tt := range tests { 1215 t.Run(tt.name, func(t *testing.T) { 1216 assert.Equal(t, tt.want, stripVersionSpecifier(tt.version)) 1217 }) 1218 } 1219 } 1220 1221 func TestParseReleasesFromAPKRepository(t *testing.T) { 1222 tests := []struct { 1223 repos string 1224 want []linux.Release 1225 desc string 1226 }{ 1227 { 1228 "https://foo.alpinelinux.org/alpine/v3.14/main", 1229 []linux.Release{ 1230 {Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"}, 1231 }, 1232 "single repo", 1233 }, 1234 { 1235 `https://foo.alpinelinux.org/alpine/v3.14/main 1236 https://foo.alpinelinux.org/alpine/v3.14/community`, 1237 []linux.Release{ 1238 {Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"}, 1239 {Name: "Alpine Linux", ID: "alpine", VersionID: "3.14"}, 1240 }, 1241 "multiple repos", 1242 }, 1243 { 1244 ``, 1245 nil, 1246 "empty", 1247 }, 1248 { 1249 `https://foo.bar.org/alpine/v3.14/main 1250 https://foo.them.org/alpine/v3.14/community`, 1251 nil, 1252 "invalid repos", 1253 }, 1254 } 1255 for _, tt := range tests { 1256 t.Run(tt.desc, func(t *testing.T) { 1257 reposReader := io.NopCloser(strings.NewReader(tt.repos)) 1258 got := parseReleasesFromAPKRepository(file.LocationReadCloser{ 1259 Location: file.NewLocation("test"), 1260 ReadCloser: reposReader, 1261 }) 1262 assert.Equal(t, tt.want, got) 1263 }) 1264 } 1265 }