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  }