github.com/anchore/syft@v1.38.2/syft/pkg/cataloger/redhat/parse_rpm_db_test.go (about) 1 package redhat 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "testing" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/anchore/syft/syft/file" 13 "github.com/anchore/syft/syft/pkg" 14 "github.com/anchore/syft/syft/pkg/cataloger/internal/pkgtest" 15 ) 16 17 var _ file.Resolver = (*rpmdbTestFileResolverMock)(nil) 18 19 type rpmdbTestFileResolverMock struct { 20 ignorePaths bool 21 } 22 23 func (r rpmdbTestFileResolverMock) FilesByExtension(extensions ...string) ([]file.Location, error) { 24 panic("not implemented") 25 } 26 27 func (r rpmdbTestFileResolverMock) FilesByBasename(filenames ...string) ([]file.Location, error) { 28 panic("not implemented") 29 } 30 31 func (r rpmdbTestFileResolverMock) FilesByBasenameGlob(globs ...string) ([]file.Location, error) { 32 panic("not implemented") 33 } 34 35 func (r rpmdbTestFileResolverMock) FileContentsByLocation(location file.Location) (io.ReadCloser, error) { 36 panic("not implemented") 37 } 38 39 func (r rpmdbTestFileResolverMock) AllLocations(_ context.Context) <-chan file.Location { 40 panic("not implemented") 41 } 42 43 func (r rpmdbTestFileResolverMock) FileMetadataByLocation(location file.Location) (file.Metadata, error) { 44 panic("not implemented") 45 } 46 47 func newTestFileResolver(ignorePaths bool) *rpmdbTestFileResolverMock { 48 return &rpmdbTestFileResolverMock{ 49 ignorePaths: ignorePaths, 50 } 51 } 52 53 func (r rpmdbTestFileResolverMock) HasPath(path string) bool { 54 return !r.ignorePaths 55 } 56 57 func (r *rpmdbTestFileResolverMock) FilesByPath(paths ...string) ([]file.Location, error) { 58 if r.ignorePaths { 59 // act as if no paths exist 60 return nil, nil 61 } 62 // act as if all files exist 63 var locations = make([]file.Location, len(paths)) 64 for i, p := range paths { 65 locations[i] = file.NewLocation(p) 66 } 67 return locations, nil 68 } 69 70 func (r *rpmdbTestFileResolverMock) FilesByGlob(...string) ([]file.Location, error) { 71 return nil, fmt.Errorf("not implemented") 72 } 73 74 func (r *rpmdbTestFileResolverMock) RelativeFileByPath(file.Location, string) *file.Location { 75 panic(fmt.Errorf("not implemented")) 76 return nil 77 } 78 79 func (r *rpmdbTestFileResolverMock) FilesByMIMEType(...string) ([]file.Location, error) { 80 return nil, fmt.Errorf("not implemented") 81 } 82 83 func TestParseRpmDB(t *testing.T) { 84 ctx := context.TODO() 85 packagesLocation := file.NewLocation("test-fixtures/Packages") 86 tests := []struct { 87 fixture string 88 expected []pkg.Package 89 ignorePaths bool 90 }{ 91 { 92 fixture: "test-fixtures/Packages", 93 // we only surface package paths for files that exist (here we DO NOT expect a path) 94 ignorePaths: true, 95 expected: []pkg.Package{ 96 { 97 Name: "dive", 98 Version: "0.9.2-1", 99 PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm", 100 Locations: file.NewLocationSet(file.NewLocation("test-fixtures/Packages")), 101 Type: pkg.RpmPkg, 102 Licenses: pkg.NewLicenseSet( 103 pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", packagesLocation), 104 ), 105 Metadata: pkg.RpmDBEntry{ 106 Name: "dive", 107 Epoch: nil, 108 Arch: "x86_64", 109 Release: "1", 110 Version: "0.9.2", 111 SourceRpm: "dive-0.9.2-1.src.rpm", 112 Size: 12406784, 113 Vendor: "", 114 ModularityLabel: strRef(""), 115 Provides: []string{"dive"}, 116 Files: []pkg.RpmFileRecord{}, 117 }, 118 }, 119 }, 120 }, 121 { 122 fixture: "test-fixtures/Packages", 123 // we only surface package paths for files that exist (here we expect a path) 124 ignorePaths: false, 125 expected: []pkg.Package{ 126 { 127 Name: "dive", 128 Version: "0.9.2-1", 129 PURL: "pkg:rpm/dive@0.9.2-1?arch=x86_64&upstream=dive-0.9.2-1.src.rpm", 130 Locations: file.NewLocationSet(packagesLocation), 131 Type: pkg.RpmPkg, 132 Licenses: pkg.NewLicenseSet( 133 pkg.NewLicenseFromLocationsWithContext(ctx, "MIT", packagesLocation), 134 ), 135 Metadata: pkg.RpmDBEntry{ 136 Name: "dive", 137 Epoch: nil, 138 Arch: "x86_64", 139 Release: "1", 140 Version: "0.9.2", 141 SourceRpm: "dive-0.9.2-1.src.rpm", 142 Size: 12406784, 143 Vendor: "", 144 ModularityLabel: strRef(""), 145 Provides: []string{"dive"}, 146 Files: []pkg.RpmFileRecord{ 147 { 148 Path: "/usr/local/bin/dive", 149 Mode: 33261, 150 Size: 12406784, 151 Digest: file.Digest{ 152 Algorithm: "sha256", 153 Value: "81d29f327ba23096b3c52ff6fe1c425641e618bc87b5c05ee377edc650afaa55", 154 }, 155 // note: there is no username, groupname, or flags for this RPM 156 }, 157 }, 158 }, 159 }, 160 }, 161 }, 162 } 163 164 for _, test := range tests { 165 t.Run(test.fixture, func(t *testing.T) { 166 pkgtest.NewCatalogTester(). 167 WithResolver(newTestFileResolver(test.ignorePaths)). 168 FromFile(t, test.fixture). 169 Expects(test.expected, nil). 170 TestParser(t, parseRpmDB) 171 }) 172 } 173 } 174 175 func TestToElVersion(t *testing.T) { 176 tests := []struct { 177 name string 178 entry pkg.RpmDBEntry 179 expected string 180 }{ 181 { 182 name: "no epoch", 183 entry: pkg.RpmDBEntry{ 184 Version: "1.2.3-4", 185 Release: "el7", 186 Arch: "x86-64", 187 }, 188 expected: "1.2.3-4-el7", 189 }, 190 { 191 name: "with 0 epoch", 192 entry: pkg.RpmDBEntry{ 193 Version: "1.2.3-4", 194 Release: "el7", 195 Arch: "x86-64", 196 Epoch: intRef(0), 197 }, 198 expected: "0:1.2.3-4-el7", 199 }, 200 { 201 name: "with non-zero epoch", 202 entry: pkg.RpmDBEntry{ 203 Version: "1.2.3-4", 204 Release: "el7", 205 Arch: "x86-64", 206 Epoch: intRef(12), 207 }, 208 expected: "12:1.2.3-4-el7", 209 }, 210 } 211 212 for _, test := range tests { 213 t.Run(test.name, func(t *testing.T) { 214 assert.Equal(t, test.expected, toELVersion(test.entry.Epoch, test.entry.Version, test.entry.Release)) 215 }) 216 } 217 } 218 219 func Test_corruptRpmDbEntry(t *testing.T) { 220 pkgtest.NewCatalogTester(). 221 FromFile(t, "test-fixtures/glob-paths/usr/lib/sysimage/rpm/Packages.db"). 222 WithError(). 223 TestParser(t, parseRpmDB) 224 } 225 226 func TestParseSignatures(t *testing.T) { 227 tests := []struct { 228 name string 229 sigs []string 230 expected []pkg.RpmSignature 231 expectedError require.ErrorAssertionFunc 232 }{ 233 { 234 name: "valid signature", 235 sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, 236 expected: []pkg.RpmSignature{ 237 { 238 PublicKeyAlgorithm: "RSA", 239 HashAlgorithm: "SHA256", 240 Created: "Mon May 16 12:32:55 2022", 241 IssuerKeyID: "702d426d350d275d", 242 }, 243 }, 244 }, 245 { 246 name: "multiple valid signatures", 247 sigs: []string{ 248 "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d", 249 "DSA/SHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789", 250 }, 251 expected: []pkg.RpmSignature{ 252 { 253 PublicKeyAlgorithm: "RSA", 254 HashAlgorithm: "SHA256", 255 Created: "Mon May 16 12:32:55 2022", 256 IssuerKeyID: "702d426d350d275d", 257 }, 258 { 259 PublicKeyAlgorithm: "DSA", 260 HashAlgorithm: "SHA1", 261 Created: "Tue Jun 14 09:45:12 2023", 262 IssuerKeyID: "123abc456def789", 263 }, 264 }, 265 }, 266 { 267 name: "no signatures", 268 sigs: []string{}, 269 expected: nil, 270 }, 271 { 272 name: "empty signatures", 273 sigs: []string{"", "", ""}, 274 expected: nil, 275 }, 276 { 277 name: "invalid parts count", 278 sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022"}, 279 expected: nil, 280 expectedError: require.Error, 281 }, 282 { 283 name: "invalid method format", 284 sigs: []string{"RSASHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, 285 expected: nil, 286 expectedError: require.Error, 287 }, 288 { 289 name: "empty method values", 290 sigs: []string{"/, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d"}, 291 expected: nil, 292 expectedError: require.Error, 293 }, 294 { 295 name: "empty created value", 296 sigs: []string{"RSA/SHA256, , Key ID 702d426d350d275d"}, 297 expected: nil, 298 expectedError: require.Error, 299 }, 300 { 301 name: "empty issuer value", 302 sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, Key ID "}, 303 expected: nil, 304 expectedError: require.Error, 305 }, 306 { 307 name: "issuer without prefix", 308 sigs: []string{"RSA/SHA256, Mon May 16 12:32:55 2022, 702d426d350d275d"}, 309 expected: []pkg.RpmSignature{ 310 { 311 PublicKeyAlgorithm: "RSA", 312 HashAlgorithm: "SHA256", 313 Created: "Mon May 16 12:32:55 2022", 314 IssuerKeyID: "702d426d350d275d", 315 }, 316 }, 317 }, 318 { 319 name: "mixed valid and invalid signatures", 320 sigs: []string{ 321 "RSA/SHA256, Mon May 16 12:32:55 2022, Key ID 702d426d350d275d", 322 "DSASHA1, Tue Jun 14 09:45:12 2023, Key ID 123abc456def789", 323 }, 324 expected: []pkg.RpmSignature{ 325 { 326 PublicKeyAlgorithm: "RSA", 327 HashAlgorithm: "SHA256", 328 Created: "Mon May 16 12:32:55 2022", 329 IssuerKeyID: "702d426d350d275d", 330 }, 331 }, 332 expectedError: require.Error, 333 }, 334 } 335 336 for _, tt := range tests { 337 t.Run(tt.name, func(t *testing.T) { 338 if tt.expectedError == nil { 339 tt.expectedError = require.NoError 340 } 341 got, err := parseSignatures(tt.sigs...) 342 tt.expectedError(t, err) 343 if err != nil { 344 return 345 } 346 347 require.Equal(t, tt.expected, got) 348 }) 349 } 350 } 351 352 func intRef(i int) *int { 353 return &i 354 } 355 356 func strRef(s string) *string { 357 return &s 358 }