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