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  }