diff --git a/pkg/fanal/applier/docker.go b/pkg/fanal/applier/docker.go index 706da683066d..d4f88bc851a7 100644 --- a/pkg/fanal/applier/docker.go +++ b/pkg/fanal/applier/docker.go @@ -30,24 +30,24 @@ type History struct { CreatedBy string `json:"created_by"` } -func containsPackage(e ftypes.Package, s []ftypes.Package) bool { +func findPackage(e ftypes.Package, s []ftypes.Package) *ftypes.Package { for _, a := range s { if a.Name == e.Name && a.Version == e.Version && a.Release == e.Release { - return true + return &a } } - return false + return nil } -func lookupOriginLayerForPkg(pkg ftypes.Package, layers []ftypes.BlobInfo) (string, string, *ftypes.BuildInfo) { +func lookupOriginLayerForPkg(pkg ftypes.Package, layers []ftypes.BlobInfo) (string, string, []string, *ftypes.BuildInfo) { for i, layer := range layers { for _, info := range layer.PackageInfos { - if containsPackage(pkg, info.Packages) { - return layer.Digest, layer.DiffID, lookupBuildInfo(i, layers) + if p := findPackage(pkg, info.Packages); p != nil { + return layer.Digest, layer.DiffID, p.InstalledFiles, lookupBuildInfo(i, layers) } } } - return "", "", nil + return "", "", nil, nil } // lookupBuildInfo looks up Red Hat content sets from all layers @@ -81,7 +81,7 @@ func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftype if filePath != layerApp.FilePath { continue } - if containsPackage(lib, layerApp.Packages) { + if findPackage(lib, layerApp.Packages) != nil { return layer.Digest, layer.DiffID } } @@ -210,12 +210,14 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail { for i, pkg := range mergedLayer.Packages { // Skip lookup for SBOM if lo.IsEmpty(pkg.Layer) { - originLayerDigest, originLayerDiffID, buildInfo := lookupOriginLayerForPkg(pkg, layers) + originLayerDigest, originLayerDiffID, installedFiles, buildInfo := lookupOriginLayerForPkg(pkg, layers) mergedLayer.Packages[i].Layer = ftypes.Layer{ Digest: originLayerDigest, DiffID: originLayerDiffID, } mergedLayer.Packages[i].BuildInfo = buildInfo + // Debian/Ubuntu has the installed files only in the first layer where the package is installed. + mergedLayer.Packages[i].InstalledFiles = installedFiles } if mergedLayer.OS.Family != "" { diff --git a/pkg/fanal/applier/docker_test.go b/pkg/fanal/applier/docker_test.go index f5bccfa86bf6..93facbd39186 100644 --- a/pkg/fanal/applier/docker_test.go +++ b/pkg/fanal/applier/docker_test.go @@ -843,6 +843,188 @@ func TestApplyLayers(t *testing.T) { }, }, }, + { + name: "happy path with filling system files for debian packages", + inputLayers: []types.BlobInfo{ + { + SchemaVersion: 2, + DiffID: "sha256:cdd7c73923174e45ea648d66996665c288e1b17a0f45efdbeca860f6dafdf731", + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.9", + Name: "apt", + Version: "2.4.9", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.9", + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + }, + }, + }, + }, + }, + }, + // Install `curl` + { + SchemaVersion: 2, + DiffID: "sha256:faf30fa9c41c10f93b3b134d7b2c16e07753320393e020c481f0c97d10db067d", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.9", + Name: "apt", + Version: "2.4.9", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.9", + }, + { + ID: "curl@8.5.0-2ubuntu10.1", + Name: "curl", + Version: "8.5.0", + Release: "2ubuntu10.1", + Arch: "arm64", + SrcName: "curl", + SrcVersion: "8.5.0", + SrcRelease: "2ubuntu10.1", + InstalledFiles: []string{ + "/usr/bin/curl", + "/usr/share/doc/curl/README.Debian", + "/usr/share/doc/curl/changelog.Debian.gz", + "/usr/share/doc/curl/copyright", + "/usr/share/man/man1/curl.1.gz", + "/usr/share/zsh/vendor-completions/_curl", + }, + }, + }, + }, + }, + }, + // Upgrade `apt` + { + SchemaVersion: 2, + DiffID: "sha256:440e26edc0eb9b4fee6e1d40d8af9eb59500d38e25edfc5d5302c55f59394c1e", + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + "/usr/share/man/it/man5/sources.list.5.gz", + }, + }, + { + ID: "curl@8.5.0-2ubuntu10.1", + Name: "curl", + Version: "8.5.0", + Release: "2ubuntu10.1", + Arch: "arm64", + SrcName: "curl", + SrcVersion: "8.5.0", + SrcRelease: "2ubuntu10.1", + }, + }, + }, + }, + }, + // Remove curl + { + SchemaVersion: 2, + DiffID: "sha256:cb04e1d437de723d8d04bc7df89dc42271530c5f8ea1724c6072e3f0e7d6d38a", + WhiteoutFiles: []string{ + "usr/bin/curl", + "usr/share/doc/curl", + "usr/share/zsh", + "var/lib/dpkg/info/curl.list", + "var/lib/dpkg/info/curl.md5sums", + }, + PackageInfos: []types.PackageInfo{ + { + FilePath: "var/lib/dpkg/status", + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + }, + }, + }, + }, + }, + }, + want: types.ArtifactDetail{ + OS: types.OS{ + Family: "ubuntu", + Name: "24.04", + }, + Packages: types.Packages{ + { + ID: "apt@2.4.12", + Name: "apt", + Version: "2.4.12", + Arch: "amd64", + SrcName: "apt", + SrcVersion: "2.4.12", + + Identifier: types.PkgIdentifier{ + UID: "80bc98a8f3159db9", + PURL: &packageurl.PackageURL{ + Type: packageurl.TypeDebian, + Namespace: "ubuntu", + Name: "apt", + Version: "2.4.12", + Qualifiers: packageurl.Qualifiers{ + { + Key: "arch", + Value: "amd64", + }, + { + Key: "distro", + Value: "ubuntu-24.04", + }, + }, + }, + }, + Layer: types.Layer{ + DiffID: "sha256:440e26edc0eb9b4fee6e1d40d8af9eb59500d38e25edfc5d5302c55f59394c1e", + }, + InstalledFiles: []string{ + "/etc/apt/apt.conf.d/01-vendor-ubuntu", + "/etc/apt/apt.conf.d/01autoremove", + "/etc/apt/auth.conf.d", + "/etc/apt/keyrings", + "/usr/share/man/it/man5/sources.list.5.gz", + }, + }, + }, + }, + }, { name: "happy path, opaque dirs with the trailing slash", inputLayers: []types.BlobInfo{