diff --git a/syft/pkg/cataloger/deb/cataloger.go b/syft/pkg/cataloger/deb/cataloger.go index 34c3df3ac..2e54f8698 100644 --- a/syft/pkg/cataloger/deb/cataloger.go +++ b/syft/pkg/cataloger/deb/cataloger.go @@ -8,14 +8,17 @@ import ( "io" "path" "path/filepath" + "sort" + "github.com/anchore/syft/internal/log" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" ) const ( - md5sumsExt = ".md5sums" - docsPath = "/usr/share/doc" + md5sumsExt = ".md5sums" + conffilesExt = ".conffiles" + docsPath = "/usr/share/doc" ) type Cataloger struct{} @@ -56,44 +59,13 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) p.FoundBy = c.Name() p.Locations = []source.Location{dbLocation} - metadata := p.Metadata.(pkg.DpkgMetadata) + // the current entry only has what may have been listed in the status file, however, there are additional + // files that are listed in multiple other locations. We should retrieve them all and merge the file lists + // together. + mergeFileListing(resolver, dbLocation, p) - md5Reader, md5Location, err := fetchMd5Contents(resolver, dbLocation, p) - if err != nil { - return nil, fmt.Errorf("unable to find dpkg md5 contents: %w", err) - } - - if md5Reader != nil { - // attach the file list - metadata.Files = parseDpkgMD5Info(md5Reader) - - // keep a record of the file where this was discovered - if md5Location != nil { - p.Locations = append(p.Locations, *md5Location) - } - } else { - // ensure the file list is an empty collection (not nil) - metadata.Files = make([]pkg.DpkgFileRecord, 0) - } - - // persist alterations - p.Metadata = metadata - - // get license information from the copyright file - copyrightReader, copyrightLocation, err := fetchCopyrightContents(resolver, dbLocation, p) - if err != nil { - return nil, fmt.Errorf("unable to find dpkg copyright contents: %w", err) - } - - if copyrightReader != nil { - // attach the licenses - p.Licenses = parseLicensesFromCopyright(copyrightReader) - - // keep a record of the file where this was discovered - if copyrightLocation != nil { - p.Locations = append(p.Locations, *copyrightLocation) - } - } + // fetch additional data from the copyright file to derive the license information + addLicenses(resolver, dbLocation, p) } results = append(results, pkgs...) @@ -101,48 +73,152 @@ func (c *Cataloger) Catalog(resolver source.FileResolver) ([]pkg.Package, error) return results, nil } -func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) (io.Reader, *source.Location, error) { +func addLicenses(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { + // get license information from the copyright file + copyrightReader, copyrightLocation := fetchCopyrightContents(resolver, dbLocation, p) + + if copyrightReader != nil { + // attach the licenses + p.Licenses = parseLicensesFromCopyright(copyrightReader) + + // keep a record of the file where this was discovered + if copyrightLocation != nil { + p.Locations = append(p.Locations, *copyrightLocation) + } + } +} + +func mergeFileListing(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) { + metadata := p.Metadata.(pkg.DpkgMetadata) + + // get file listing (package files + additional config files) + files, infoLocations := getAdditionalFileListing(resolver, dbLocation, p) +loopNewFiles: + for _, newFile := range files { + for _, existingFile := range metadata.Files { + if existingFile.Path == newFile.Path { + // skip adding this file since it already exists + continue loopNewFiles + } + } + metadata.Files = append(metadata.Files, newFile) + } + + // sort files by path + sort.SliceStable(metadata.Files, func(i, j int) bool { + return metadata.Files[i].Path < metadata.Files[j].Path + }) + + // persist alterations + p.Metadata = metadata + + // persist location information from each new source of information + p.Locations = append(p.Locations, infoLocations...) +} + +func getAdditionalFileListing(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) ([]pkg.DpkgFileRecord, []source.Location) { + // ensure the file list is an empty collection (not nil) + var files = make([]pkg.DpkgFileRecord, 0) + var locations []source.Location + + md5Reader, md5Location := fetchMd5Contents(resolver, dbLocation, p) + + if md5Reader != nil { + // attach the file list + files = append(files, parseDpkgMD5Info(md5Reader)...) + + // keep a record of the file where this was discovered + if md5Location != nil { + locations = append(locations, *md5Location) + } + } + + conffilesReader, conffilesLocation := fetchConffileContents(resolver, dbLocation, p) + + if conffilesReader != nil { + // attach the file list + files = append(files, parseDpkgConffileInfo(md5Reader)...) + + // keep a record of the file where this was discovered + if md5Location != nil { + locations = append(locations, *conffilesLocation) + } + } + + return files, locations +} + +func fetchMd5Contents(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) (io.ReadCloser, *source.Location) { + var md5Reader io.ReadCloser + var err error + parentPath := filepath.Dir(dbLocation.RealPath) // look for /var/lib/dpkg/info/NAME:ARCH.md5sums name := md5Key(p) - md5SumLocation := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", name+md5sumsExt)) + location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", name+md5sumsExt)) - if md5SumLocation == nil { + if location == nil { // the most specific key did not work, fallback to just the name // look for /var/lib/dpkg/info/NAME.md5sums - md5SumLocation = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", p.Name+md5sumsExt)) + location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", p.Name+md5sumsExt)) } // this is unexpected, but not a show-stopper - if md5SumLocation == nil { - return nil, nil, nil + if location != nil { + md5Reader, err = resolver.FileContentsByLocation(*location) + if err != nil { + log.Warnf("failed to fetch deb md5 contents (package=%s): %+v", p.Name, err) + } } - reader, err := resolver.FileContentsByLocation(*md5SumLocation) - if err != nil { - return nil, nil, fmt.Errorf("failed to fetch deb md5 contents (%+v): %w", p, err) - } - return reader, md5SumLocation, nil + return md5Reader, location } -func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) (io.Reader, *source.Location, error) { +func fetchConffileContents(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) (io.ReadCloser, *source.Location) { + var reader io.ReadCloser + var err error + + parentPath := filepath.Dir(dbLocation.RealPath) + + // look for /var/lib/dpkg/info/NAME:ARCH.conffiles + name := md5Key(p) + location := resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", name+conffilesExt)) + + if location == nil { + // the most specific key did not work, fallback to just the name + // look for /var/lib/dpkg/info/NAME.conffiles + location = resolver.RelativeFileByPath(dbLocation, path.Join(parentPath, "info", p.Name+conffilesExt)) + } + + // this is unexpected, but not a show-stopper + if location != nil { + reader, err = resolver.FileContentsByLocation(*location) + if err != nil { + log.Warnf("failed to fetch deb conffiles contents (package=%s): %+v", p.Name, err) + } + } + + return reader, location +} + +func fetchCopyrightContents(resolver source.FileResolver, dbLocation source.Location, p *pkg.Package) (io.ReadCloser, *source.Location) { // look for /usr/share/docs/NAME/copyright files name := p.Name copyrightPath := path.Join(docsPath, name, "copyright") - copyrightLocation := resolver.RelativeFileByPath(dbLocation, copyrightPath) + location := resolver.RelativeFileByPath(dbLocation, copyrightPath) // we may not have a copyright file for each package, ignore missing files - if copyrightLocation == nil { - return nil, nil, nil + if location == nil { + return nil, nil } - reader, err := resolver.FileContentsByLocation(*copyrightLocation) + reader, err := resolver.FileContentsByLocation(*location) if err != nil { - return nil, nil, fmt.Errorf("failed to fetch deb copyright contents (%+v): %w", p, err) + log.Warnf("failed to fetch deb copyright contents (package=%s): %w", p.Name, err) } - return reader, copyrightLocation, nil + return reader, location } func md5Key(p *pkg.Package) string { diff --git a/syft/pkg/cataloger/deb/cataloger_test.go b/syft/pkg/cataloger/deb/cataloger_test.go index b856a0cb9..84bef7c11 100644 --- a/syft/pkg/cataloger/deb/cataloger_test.go +++ b/syft/pkg/cataloger/deb/cataloger_test.go @@ -3,6 +3,8 @@ package deb import ( "testing" + "github.com/anchore/syft/syft/file" + "github.com/anchore/stereoscope/pkg/imagetest" "github.com/anchore/syft/syft/pkg" "github.com/anchore/syft/syft/source" @@ -19,7 +21,12 @@ func TestDpkgCataloger(t *testing.T) { { name: "go-case", sources: map[string][]string{ - "libpam-runtime": {"/var/lib/dpkg/status", "/var/lib/dpkg/info/libpam-runtime.md5sums", "/usr/share/doc/libpam-runtime/copyright"}, + "libpam-runtime": { + "/var/lib/dpkg/status", + "/var/lib/dpkg/info/libpam-runtime.md5sums", + "/var/lib/dpkg/info/libpam-runtime.conffiles", + "/usr/share/doc/libpam-runtime/copyright", + }, }, expected: []pkg.Package{ { @@ -37,10 +44,38 @@ func TestDpkgCataloger(t *testing.T) { Maintainer: "Steve Langasek ", InstalledSize: 1016, Files: []pkg.DpkgFileRecord{ - {Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"}, - {Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"}, - {Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"}, - {Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"}, + { + Path: "/etc/pam.conf", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "87fc76f18e98ee7d3848f6b81b3391e5", + }, + IsConfigFile: true, + }, + { + Path: "/etc/pam.d/other", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "31aa7f2181889ffb00b87df4126d1701", + }, + IsConfigFile: true, + }, + {Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", Digest: &file.Digest{ + Algorithm: "md5", + Value: "55f905631797551d4d936a34c7e73474", + }}, + {Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", Digest: &file.Digest{ + Algorithm: "md5", + Value: "cede84bda30d2380217f97753c8ccf3a", + }}, + {Path: "/usr/share/doc/zlib1g/changelog.gz", Digest: &file.Digest{ + Algorithm: "md5", + Value: "f3c9dafa6da7992c47328b4464f6d122", + }}, + {Path: "/usr/share/doc/zlib1g/copyright", Digest: &file.Digest{ + Algorithm: "md5", + Value: "a4fae96070439a5209a62ae5b8017ab2", + }}, }, }, }, diff --git a/syft/pkg/cataloger/deb/parse_dpkg_info_files.go b/syft/pkg/cataloger/deb/parse_dpkg_info_files.go index 0561732c9..105c47da9 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_info_files.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_info_files.go @@ -5,12 +5,11 @@ import ( "io" "strings" + "github.com/anchore/syft/syft/file" "github.com/anchore/syft/syft/pkg" ) -func parseDpkgMD5Info(reader io.Reader) []pkg.DpkgFileRecord { - // we must preallocate to ensure the resulting struct does not have null - var findings = make([]pkg.DpkgFileRecord, 0) +func parseDpkgMD5Info(reader io.Reader) (findings []pkg.DpkgFileRecord) { scanner := bufio.NewScanner(reader) for scanner.Scan() { @@ -23,9 +22,53 @@ func parseDpkgMD5Info(reader io.Reader) []pkg.DpkgFileRecord { } findings = append(findings, pkg.DpkgFileRecord{ Path: path, - MD5: strings.TrimSpace(fields[0]), + Digest: &file.Digest{ + Algorithm: "md5", + Value: strings.TrimSpace(fields[0]), + }, }) } } return findings } + +func parseDpkgConffileInfo(reader io.Reader) (findings []pkg.DpkgFileRecord) { + scanner := bufio.NewScanner(reader) + + for scanner.Scan() { + line := strings.Trim(scanner.Text(), " \n") + fields := strings.SplitN(line, " ", 2) + + if line == "" { + continue + } + + var path string + if len(fields) >= 1 { + path = strings.TrimSpace(fields[0]) + if !strings.HasPrefix(path, "/") { + path = "/" + path + } + } + + var digest *file.Digest + if len(fields) >= 2 { + digest = &file.Digest{ + Algorithm: "md5", + Value: strings.TrimSpace(fields[1]), + } + } + + if path != "" { + record := pkg.DpkgFileRecord{ + Path: path, + IsConfigFile: true, + } + if digest != nil { + record.Digest = digest + } + findings = append(findings, record) + } + } + return findings +} diff --git a/syft/pkg/cataloger/deb/parse_dpkg_info_files_test.go b/syft/pkg/cataloger/deb/parse_dpkg_info_files_test.go index df252ddc4..c345c714e 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_info_files_test.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_info_files_test.go @@ -4,6 +4,8 @@ import ( "os" "testing" + "github.com/anchore/syft/syft/file" + "github.com/go-test/deep" "github.com/anchore/syft/syft/pkg" @@ -17,10 +19,22 @@ func TestMD5SumInfoParsing(t *testing.T) { { fixture: "test-fixtures/info/zlib1g.md5sums", expected: []pkg.DpkgFileRecord{ - {Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", MD5: "55f905631797551d4d936a34c7e73474"}, - {Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", MD5: "cede84bda30d2380217f97753c8ccf3a"}, - {Path: "/usr/share/doc/zlib1g/changelog.gz", MD5: "f3c9dafa6da7992c47328b4464f6d122"}, - {Path: "/usr/share/doc/zlib1g/copyright", MD5: "a4fae96070439a5209a62ae5b8017ab2"}, + {Path: "/lib/x86_64-linux-gnu/libz.so.1.2.11", Digest: &file.Digest{ + Algorithm: "md5", + Value: "55f905631797551d4d936a34c7e73474", + }}, + {Path: "/usr/share/doc/zlib1g/changelog.Debian.gz", Digest: &file.Digest{ + Algorithm: "md5", + Value: "cede84bda30d2380217f97753c8ccf3a", + }}, + {Path: "/usr/share/doc/zlib1g/changelog.gz", Digest: &file.Digest{ + Algorithm: "md5", + Value: "f3c9dafa6da7992c47328b4464f6d122", + }}, + {Path: "/usr/share/doc/zlib1g/copyright", Digest: &file.Digest{ + Algorithm: "md5", + Value: "a4fae96070439a5209a62ae5b8017ab2", + }}, }, }, } @@ -55,3 +69,52 @@ func TestMD5SumInfoParsing(t *testing.T) { }) } } + +func TestConffileInfoParsing(t *testing.T) { + tests := []struct { + fixture string + expected []pkg.DpkgFileRecord + }{ + { + fixture: "test-fixtures/info/util-linux.conffiles", + expected: []pkg.DpkgFileRecord{ + {Path: "/etc/default/hwclock", IsConfigFile: true}, + {Path: "/etc/init.d/hwclock.sh", IsConfigFile: true}, + {Path: "/etc/pam.d/runuser", IsConfigFile: true}, + {Path: "/etc/pam.d/runuser-l", IsConfigFile: true}, + {Path: "/etc/pam.d/su", IsConfigFile: true}, + {Path: "/etc/pam.d/su-l", IsConfigFile: true}, + }, + }, + } + + for _, test := range tests { + t.Run(test.fixture, func(t *testing.T) { + file, err := os.Open(test.fixture) + if err != nil { + t.Fatal("Unable to read: ", err) + } + defer func() { + err := file.Close() + if err != nil { + t.Fatal("closing file failed:", err) + } + }() + + actual := parseDpkgConffileInfo(file) + + if len(actual) != len(test.expected) { + for _, a := range actual { + t.Logf(" %+v", a) + } + t.Fatalf("unexpected package count: %d!=%d", len(actual), len(test.expected)) + } + + diffs := deep.Equal(actual, test.expected) + for _, d := range diffs { + t.Errorf("diff: %+v", d) + } + + }) + } +} diff --git a/syft/pkg/cataloger/deb/parse_dpkg_status.go b/syft/pkg/cataloger/deb/parse_dpkg_status.go index 2c14ac14a..fb61b5937 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_status.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_status.go @@ -15,7 +15,10 @@ import ( "github.com/mitchellh/mapstructure" ) -var errEndOfPackages = fmt.Errorf("no more packages to read") +var ( + errEndOfPackages = fmt.Errorf("no more packages to read") + sourceRegexp = regexp.MustCompile(`(?P\S+)( \((?P.*)\))?`) +) // parseDpkgStatus is a parser function for Debian DB status contents, returning all Debian packages listed. func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) { @@ -48,20 +51,49 @@ func parseDpkgStatus(reader io.Reader) ([]pkg.Package, error) { } // parseDpkgStatusEntry returns an individual Dpkg entry, or returns errEndOfPackages if there are no more packages to parse from the reader. -// nolint:funlen -func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err error) { - dpkgFields := make(map[string]interface{}) +func parseDpkgStatusEntry(reader *bufio.Reader) (pkg.DpkgMetadata, error) { var retErr error + dpkgFields, err := extractAllFields(reader) + if err != nil { + if !errors.Is(err, errEndOfPackages) { + return pkg.DpkgMetadata{}, err + } + retErr = err + } + + var entry pkg.DpkgMetadata + err = mapstructure.Decode(dpkgFields, &entry) + if err != nil { + return pkg.DpkgMetadata{}, err + } + + name, version := extractSourceVersion(entry.Source) + if version != "" { + entry.SourceVersion = version + entry.Source = name + } + + // there may be an optional conffiles section that we should persist as files + if conffilesSection, exists := dpkgFields["Conffiles"]; exists && conffilesSection != nil { + if sectionStr, ok := conffilesSection.(string); ok { + entry.Files = parseDpkgConffileInfo(strings.NewReader(sectionStr)) + } + } + + return entry, retErr +} + +func extractAllFields(reader *bufio.Reader) (map[string]interface{}, error) { + dpkgFields := make(map[string]interface{}) var key string for { line, err := reader.ReadString('\n') if err != nil { if err == io.EOF { - retErr = errEndOfPackages - break + return dpkgFields, errEndOfPackages } - return pkg.DpkgMetadata{}, err + return nil, err } line = strings.TrimRight(line, "\n") @@ -79,12 +111,12 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err err case strings.HasPrefix(line, " "): // a field-body continuation if len(key) == 0 { - return pkg.DpkgMetadata{}, fmt.Errorf("no match for continuation: line: '%s'", line) + return nil, fmt.Errorf("no match for continuation: line: '%s'", line) } val, ok := dpkgFields[key] if !ok { - return pkg.DpkgMetadata{}, fmt.Errorf("no previous key exists, expecting: %s", key) + return nil, fmt.Errorf("no previous key exists, expecting: %s", key) } // concatenate onto previous value val = fmt.Sprintf("%s\n %s", val, strings.TrimSpace(line)) @@ -94,36 +126,18 @@ func parseDpkgStatusEntry(reader *bufio.Reader) (entry pkg.DpkgMetadata, err err var val interface{} key, val, err = handleNewKeyValue(line) if err != nil { - return pkg.DpkgMetadata{}, err + return nil, err } if _, ok := dpkgFields[key]; ok { - return pkg.DpkgMetadata{}, fmt.Errorf("duplicate key discovered: %s", key) + return nil, fmt.Errorf("duplicate key discovered: %s", key) } dpkgFields[key] = val } } - - err = mapstructure.Decode(dpkgFields, &entry) - if err != nil { - return pkg.DpkgMetadata{}, err - } - - name, version := extractSourceVersion(entry.Source) - if version != "" { - entry.SourceVersion = version - entry.Source = name - } - - return entry, retErr + return dpkgFields, nil } -// match examples: -// "a-thing (1.2.3)" name="a-thing" version="1.2.3" -// "a-thing" name="a-thing" version="" -// "" name="" version="" -var sourceRegexp = regexp.MustCompile(`(?P\S+)( \((?P.*)\))?`) - // If the source entry string is of the form " ()" then parse and return the components, if // of the "" form, then return name and nil func extractSourceVersion(source string) (string, string) { diff --git a/syft/pkg/cataloger/deb/parse_dpkg_status_test.go b/syft/pkg/cataloger/deb/parse_dpkg_status_test.go index 0c4b07202..e0b53d6ee 100644 --- a/syft/pkg/cataloger/deb/parse_dpkg_status_test.go +++ b/syft/pkg/cataloger/deb/parse_dpkg_status_test.go @@ -5,6 +5,8 @@ import ( "os" "testing" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/pkg" "github.com/go-test/deep" ) @@ -30,6 +32,40 @@ func TestSinglePackage(t *testing.T) { Architecture: "amd64", InstalledSize: 4064, Maintainer: "APT Development Team ", + Files: []pkg.DpkgFileRecord{ + { + Path: "/etc/apt/apt.conf.d/01autoremove", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "76120d358bc9037bb6358e737b3050b5", + }, + IsConfigFile: true, + }, + { + Path: "/etc/cron.daily/apt-compat", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "49e9b2cfa17849700d4db735d04244f3", + }, + IsConfigFile: true, + }, + { + Path: "/etc/kernel/postinst.d/apt-auto-removal", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "4ad976a68f045517cf4696cec7b8aa3a", + }, + IsConfigFile: true, + }, + { + Path: "/etc/logrotate.d/apt", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "179f2ed4f85cbaca12fa3d69c2a4a1c3", + }, + IsConfigFile: true, + }, + }, }, }, } @@ -81,6 +117,56 @@ func TestMultiplePackages(t *testing.T) { Architecture: "amd64", InstalledSize: 4327, Maintainer: "LaMont Jones ", + Files: []pkg.DpkgFileRecord{ + { + Path: "/etc/default/hwclock", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "3916544450533eca69131f894db0ca12", + }, + IsConfigFile: true, + }, + { + Path: "/etc/init.d/hwclock.sh", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "1ca5c0743fa797ffa364db95bb8d8d8e", + }, + IsConfigFile: true, + }, + { + Path: "/etc/pam.d/runuser", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "b8b44b045259525e0fae9e38fdb2aeeb", + }, + IsConfigFile: true, + }, + { + Path: "/etc/pam.d/runuser-l", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "2106ea05877e8913f34b2c77fa02be45", + }, + IsConfigFile: true, + }, + { + Path: "/etc/pam.d/su", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "ce6dcfda3b190a27a455bb38a45ff34a", + }, + IsConfigFile: true, + }, + { + Path: "/etc/pam.d/su-l", + Digest: &file.Digest{ + Algorithm: "md5", + Value: "756fef5687fecc0d986e5951427b0c4f", + }, + IsConfigFile: true, + }, + }, }, }, }, diff --git a/syft/pkg/cataloger/deb/test-fixtures/image-dpkg/var/lib/dpkg/info/libpam-runtime.conffiles b/syft/pkg/cataloger/deb/test-fixtures/image-dpkg/var/lib/dpkg/info/libpam-runtime.conffiles new file mode 100644 index 000000000..1fe9bc1cd --- /dev/null +++ b/syft/pkg/cataloger/deb/test-fixtures/image-dpkg/var/lib/dpkg/info/libpam-runtime.conffiles @@ -0,0 +1,2 @@ +/etc/pam.conf +/etc/pam.d/other \ No newline at end of file diff --git a/syft/pkg/cataloger/deb/test-fixtures/info/util-linux.conffiles b/syft/pkg/cataloger/deb/test-fixtures/info/util-linux.conffiles new file mode 100644 index 000000000..173ae853c --- /dev/null +++ b/syft/pkg/cataloger/deb/test-fixtures/info/util-linux.conffiles @@ -0,0 +1,6 @@ +/etc/default/hwclock +/etc/init.d/hwclock.sh +/etc/pam.d/runuser +/etc/pam.d/runuser-l +/etc/pam.d/su +/etc/pam.d/su-l \ No newline at end of file diff --git a/syft/pkg/dpkg_metadata.go b/syft/pkg/dpkg_metadata.go index b50ac49aa..8f2245c55 100644 --- a/syft/pkg/dpkg_metadata.go +++ b/syft/pkg/dpkg_metadata.go @@ -3,6 +3,8 @@ package pkg import ( "sort" + "github.com/anchore/syft/syft/file" + "github.com/anchore/syft/syft/distro" "github.com/package-url/packageurl-go" "github.com/scylladb/go-set/strset" @@ -27,8 +29,9 @@ type DpkgMetadata struct { // DpkgFileRecord represents a single file attributed to a debian package. type DpkgFileRecord struct { - Path string `json:"path"` - MD5 string `json:"md5"` + Path string `json:"path"` + Digest *file.Digest `json:"digest,omitempty"` + IsConfigFile bool `json:"isConfigFile"` } // PackageURL returns the PURL for the specific Debian package (see https://github.com/package-url/purl-spec)