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