package local import ( "context" "errors" "testing" "time" "github.com/samber/lo" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy-db/pkg/db" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" "github.com/aquasecurity/trivy/pkg/dbtest" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/scanner/ospkg" "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/vulnerability" ) func TestScanner_Scan(t *testing.T) { type args struct { target string layerIDs []string options types.ScanOptions } tests := []struct { name string args args fixtures []string applyLayersExpectation ApplierApplyLayersExpectation wantResults types.Results wantOS ftypes.OS wantErr string }{ { name: "happy path", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, Applications: []ftypes.Application{ { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "alpine:latest (alpine 3.11)", Class: types.ClassOSPkg, Type: ftypes.Alpine, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2020-9999", PkgName: "musl", InstalledVersion: "1.2.3", FixedVersion: "1.2.4", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", Vulnerability: dbTypes.Vulnerability{ Title: "dos", Description: "dos vulnerability", Severity: "HIGH", }, }, }, }, { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: true, }, }, { name: "happy path license scanner", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ Scanners: types.Scanners{types.LicenseScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Licenses: []string{"MIT"}, Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, Applications: []ftypes.Application{ { Type: ftypes.GoModule, FilePath: "/app/go.mod", Packages: []ftypes.Package{ { Name: "github.com/google/uuid", Version: "1.6.0", FilePath: "", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, Licenses: []string{"LGPL"}, }, }, }, { Type: ftypes.PythonPkg, FilePath: "", Packages: []ftypes.Package{ { Name: "urllib3", Version: "3.2.1", FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, Licenses: []string{"MIT"}, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "OS Packages", Class: types.ClassLicense, Licenses: []types.DetectedLicense{ { Severity: "UNKNOWN", Category: "unknown", PkgName: "musl", Name: "MIT", Confidence: 1, }, }, }, { Target: "/app/go.mod", Class: types.ClassLicense, Licenses: []types.DetectedLicense{ { Severity: "UNKNOWN", Category: "unknown", PkgName: "github.com/google/uuid", FilePath: "/app/go.mod", Name: "LGPL", Confidence: 1, Link: "", }, }, }, { Target: "Python", Class: types.ClassLicense, Licenses: []types.DetectedLicense{ { Severity: "UNKNOWN", Category: "unknown", PkgName: "urllib3", FilePath: "/usr/lib/python/site-packages/urllib3-3.2.1/METADATA", Name: "MIT", Confidence: 1, }, }, }, { Target: "Loose File License(s)", Class: types.ClassLicenseFile, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: false, }, }, { name: "happy path with list all packages", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, ListAllPackages: true, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "alpine", Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, { Name: "ausl", Version: "1.2.3", SrcName: "ausl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "alpine:latest (alpine 3.11)", Class: types.ClassOSPkg, Type: ftypes.Alpine, Packages: []ftypes.Package{ { Name: "ausl", Version: "1.2.3", SrcName: "ausl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, // For backward compatibility, will be removed Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2020-9999", PkgName: "musl", InstalledVersion: "1.2.3", FixedVersion: "1.2.4", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-9999", Vulnerability: dbTypes.Vulnerability{ Title: "dos", Description: "dos vulnerability", Severity: "HIGH", }, }, }, }, { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, // For backward compatibility, will be removed Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: true, }, }, { name: "happy path with list all packages and without vulnerabilities", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, ListAllPackages: true, }, }, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "alpine", Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, { Name: "ausl", Version: "1.2.3", SrcName: "ausl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "alpine:latest (alpine 3.11)", Class: types.ClassOSPkg, Type: ftypes.Alpine, Packages: []ftypes.Package{ { Name: "ausl", Version: "1.2.3", SrcName: "ausl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:bbf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, }, { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: true, }, }, { name: "happy path with empty os", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{}, Applications: []ftypes.Application{ { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "innocent", // no vulnerability Version: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, { Type: ftypes.Bundler, FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", // one vulnerability Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: "bundler", Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, }, wantOS: ftypes.OS{}, }, { name: "happy path. Empty filePaths (e.g. Scanned SBOM)", args: args{ target: "./result.cdx", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{types.VulnTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, ListAllPackages: true, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ Applications: []ftypes.Application{ { Type: "bundler", FilePath: "", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", }, }, }, { Type: "composer", FilePath: "", Packages: []ftypes.Package{ { Name: "laravel/framework", Version: "6.0.0", }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "", Class: types.ClassLangPkg, Type: ftypes.Bundler, Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", }, }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, { Target: "", Class: types.ClassLangPkg, Type: ftypes.Composer, Packages: []ftypes.Package{ { Name: "laravel/framework", Version: "6.0.0", }, }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2021-21263", PkgName: "laravel/framework", InstalledVersion: "6.0.0", FixedVersion: "8.22.1, 7.30.3, 6.20.12", Status: dbTypes.StatusFixed, }, }, }, }, }, { name: "happy path with no package", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "alpine", Name: "3.11", }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, }, }, }, }, }, Err: analyzer.ErrNoPkgsDetected, }, }, wantResults: types.Results{ { Target: "alpine:latest (alpine 3.11)", Class: types.ClassOSPkg, Type: ftypes.Alpine, }, { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:0ea33a93585cf1917ba522b2304634c3073654062d5282c1346322967790ef33", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: true, }, }, { name: "happy path with unsupported os", args: args{ target: "fedora:27", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "fedora", Name: "27", }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, }, wantOS: ftypes.OS{ Family: "fedora", Name: "27", }, }, { name: "happy path with a scratch image", args: args{ target: "busybox:latest", layerIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:a6d503001157aedc826853f9b67f26d35966221b158bff03849868ae4a821116"}, }, Returns: ApplierApplyLayersReturns{ Err: analyzer.ErrUnknownOS, }, }, wantResults: nil, }, { name: "happy path with only language-specific package detection", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{types.VulnTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "alpine", Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", SrcName: "musl", SrcVersion: "1.2.3", }, }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "4.0.2", Layer: ftypes.Layer{ DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", }, }, }, }, { Type: "composer", FilePath: "/app/composer-lock.json", Packages: []ftypes.Package{ { Name: "laravel/framework", Version: "6.0.0", Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "/app/Gemfile.lock", Class: types.ClassLangPkg, Type: ftypes.Bundler, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2014-0081", PkgName: "rails", InstalledVersion: "4.0.2", FixedVersion: "4.0.3, 3.2.17", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:5cb2a5009179b1e78ecfef81a19756328bb266456cf9a9dbbcf9af8b83b735f0", }, PrimaryURL: "https://avd.aquasec.com/nvd/cve-2014-0081", Vulnerability: dbTypes.Vulnerability{ Title: "xss", Description: "xss vulnerability", Severity: "MEDIUM", References: []string{ "http://example.com", }, LastModifiedDate: lo.ToPtr(time.Date(2020, 2, 1, 1, 1, 0, 0, time.UTC)), PublishedDate: lo.ToPtr(time.Date(2020, 1, 1, 1, 1, 0, 0, time.UTC)), }, }, }, }, { Target: "/app/composer-lock.json", Class: types.ClassLangPkg, Type: ftypes.Composer, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2021-21263", PkgName: "laravel/framework", InstalledVersion: "6.0.0", FixedVersion: "8.22.1, 7.30.3, 6.20.12", Status: dbTypes.StatusFixed, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", }, }, { name: "happy path with misconfigurations", args: args{ target: "/app/configs", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ Scanners: types.Scanners{types.MisconfigScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ Misconfigurations: []ftypes.Misconfiguration{ { FileType: ftypes.Kubernetes, FilePath: "/app/configs/pod.yaml", Warnings: []ftypes.MisconfResult{ { Namespace: "main.kubernetes.id300", PolicyMetadata: ftypes.PolicyMetadata{ ID: "ID300", Type: "Kubernetes Security Check", Title: "Bad Deployment", Severity: "DUMMY", }, }, }, Exceptions: ftypes.MisconfResults{ { Namespace: "main.kubernetes.id100", PolicyMetadata: ftypes.PolicyMetadata{ ID: "ID100", Type: "Kubernetes Security Check", Title: "Bad Deployment", Severity: "HIGH", }, }, }, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, { FileType: ftypes.Kubernetes, FilePath: "/app/configs/deployment.yaml", Successes: []ftypes.MisconfResult{ { Namespace: "builtin.kubernetes.id200", PolicyMetadata: ftypes.PolicyMetadata{ ID: "ID200", Type: "Kubernetes Security Check", Title: "Bad Deployment", Severity: "MEDIUM", }, }, }, Failures: ftypes.MisconfResults{ { Namespace: "main.kubernetes.id100", Message: "something bad", PolicyMetadata: ftypes.PolicyMetadata{ ID: "ID100", Type: "Kubernetes Security Check", Title: "Bad Deployment", Severity: "HIGH", }, }, }, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, }, wantResults: types.Results{ { Target: "/app/configs/deployment.yaml", Class: types.ClassConfig, Type: ftypes.Kubernetes, Misconfigurations: []types.DetectedMisconfiguration{ { Type: "Kubernetes Security Check", ID: "ID100", Title: "Bad Deployment", Message: "something bad", Namespace: "main.kubernetes.id100", Severity: "HIGH", Status: types.MisconfStatusFailure, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, { Type: "Kubernetes Security Check", ID: "ID200", Title: "Bad Deployment", Message: "No issues found", Namespace: "builtin.kubernetes.id200", Severity: "MEDIUM", PrimaryURL: "https://avd.aquasec.com/misconfig/id200", References: []string{ "https://avd.aquasec.com/misconfig/id200", }, Status: types.MisconfStatusPassed, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, { Target: "/app/configs/pod.yaml", Class: types.ClassConfig, Type: ftypes.Kubernetes, Misconfigurations: []types.DetectedMisconfiguration{ { Type: "Kubernetes Security Check", ID: "ID300", Title: "Bad Deployment", Message: "No issues found", Namespace: "main.kubernetes.id300", Severity: "MEDIUM", Status: types.MisconfStatusFailure, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, { Type: "Kubernetes Security Check", ID: "ID100", Title: "Bad Deployment", Message: "No issues found", Namespace: "main.kubernetes.id100", Severity: "HIGH", Status: types.MisconfStatusException, Layer: ftypes.Layer{ DiffID: "sha256:9922bc15eeefe1637b803ef2106f178152ce19a391f24aec838cbe2e48e73303", }, }, }, }, }, }, { name: "sad path: ApplyLayers returns an error", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{ types.VulnTypeOS, types.VulnTypeLibrary, }, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Err: errors.New("error"), }, }, wantErr: "failed to apply layers", }, { name: "sad path: library.Detect returns an error", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ VulnType: []string{types.VulnTypeLibrary}, Scanners: types.Scanners{types.VulnerabilityScanner}, }, }, fixtures: []string{"testdata/fixtures/sad.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: "alpine", Name: "3.11", }, Packages: []ftypes.Package{ { Name: "musl", Version: "1.2.3", Layer: ftypes.Layer{ DiffID: "sha256:ebf12965380b39889c99a9c02e82ba465f887b45975b6e389d42e9e6a3857888", }, }, }, Applications: []ftypes.Application{ { Type: "bundler", FilePath: "/app/Gemfile.lock", Packages: []ftypes.Package{ { Name: "rails", Version: "6.0", Layer: ftypes.Layer{ DiffID: "sha256:9bdb2c849099a99c8ab35f6fd7469c623635e8f4479a0a5a3df61e22bae509f6", }, }, }, }, }, }, }, }, wantErr: "failed to scan application libraries", }, { name: "scan image history", args: args{ target: "alpine:latest", layerIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, options: types.ScanOptions{ ImageConfigScanners: types.Scanners{types.MisconfigScanner}, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, applyLayersExpectation: ApplierApplyLayersExpectation{ Args: ApplierApplyLayersArgs{ BlobIDs: []string{"sha256:5216338b40a7b96416b8b9858974bbe4acc3096ee60acbc4dfb1ee02aecceb10"}, }, Returns: ApplierApplyLayersReturns{ Detail: ftypes.ArtifactDetail{ OS: ftypes.OS{ Family: ftypes.Alpine, Name: "3.11", }, Misconfigurations: []ftypes.Misconfiguration{ { FileType: ftypes.Dockerfile, FilePath: "Dockerfile", Successes: ftypes.MisconfResults{ { Namespace: "builtin.dockerfile.DS001", Query: "data.builtin.dockerfile.DS001.deny", Message: "", PolicyMetadata: ftypes.PolicyMetadata{ ID: "DS001", AVDID: "AVD-DS-0001", Type: "Dockerfile Security Check", Title: "':latest' tag used", Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", Severity: "MEDIUM", RecommendedActions: "Add a tag to the image in the 'FROM' statement", }, CauseMetadata: ftypes.CauseMetadata{ Provider: "Dockerfile", Service: "general", Code: ftypes.Code{}, }, }, }, Failures: ftypes.MisconfResults{ { Namespace: "builtin.dockerfile.DS002", Query: "data.builtin.dockerfile.DS002.deny", Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", PolicyMetadata: ftypes.PolicyMetadata{ ID: "DS002", AVDID: "AVD-DS-0002", Type: "Dockerfile Security Check", Title: "Image user should not be 'root'", Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", Severity: "HIGH", RecommendedActions: "Add 'USER ' line to the Dockerfile", }, CauseMetadata: ftypes.CauseMetadata{ Provider: "Dockerfile", Service: "general", Code: ftypes.Code{}, }, }, }, }, }, }, }, }, wantResults: types.Results{ { Target: "Dockerfile", Class: types.ClassConfig, Type: ftypes.Dockerfile, Misconfigurations: []types.DetectedMisconfiguration{ { Namespace: "builtin.dockerfile.DS002", Query: "data.builtin.dockerfile.DS002.deny", Message: "Specify at least 1 USER command in Dockerfile with non-root user as argument", Type: "Dockerfile Security Check", ID: "DS002", AVDID: "AVD-DS-0002", Title: "Image user should not be 'root'", Description: "Running containers with 'root' user can lead to a container escape situation. It is a best practice to run containers as non-root users, which can be done by adding a 'USER' statement to the Dockerfile.", Severity: "HIGH", Resolution: "Add 'USER ' line to the Dockerfile", Status: types.MisconfStatusFailure, PrimaryURL: "https://avd.aquasec.com/misconfig/ds002", References: []string{"https://avd.aquasec.com/misconfig/ds002"}, CauseMetadata: ftypes.CauseMetadata{ Provider: "Dockerfile", Service: "general", Code: ftypes.Code{}, }, }, { Namespace: "builtin.dockerfile.DS001", Query: "data.builtin.dockerfile.DS001.deny", Message: "No issues found", Type: "Dockerfile Security Check", ID: "DS001", AVDID: "AVD-DS-0001", Title: "':latest' tag used", Description: "When using a 'FROM' statement you should use a specific tag to avoid uncontrolled behavior when the image is updated.", Severity: "MEDIUM", Resolution: "Add a tag to the image in the 'FROM' statement", Status: types.MisconfStatusPassed, CauseMetadata: ftypes.CauseMetadata{ Provider: "Dockerfile", Service: "general", Code: ftypes.Code{}, }, PrimaryURL: "https://avd.aquasec.com/misconfig/ds001", References: []string{"https://avd.aquasec.com/misconfig/ds001"}, }, }, }, }, wantOS: ftypes.OS{ Family: "alpine", Name: "3.11", Eosl: false, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { _ = dbtest.InitDB(t, tt.fixtures) defer db.Close() applier := new(MockApplier) applier.ApplyApplyLayersExpectation(tt.applyLayersExpectation) s := NewScanner(applier, ospkg.NewScanner(), langpkg.NewScanner(), vulnerability.NewClient(db.Config{})) gotResults, gotOS, err := s.Scan(context.Background(), tt.args.target, "", tt.args.layerIDs, tt.args.options) if tt.wantErr != "" { require.Error(t, err, tt.name) require.Contains(t, err.Error(), tt.wantErr, tt.name) return } require.NoError(t, err, tt.name) assert.Equal(t, tt.wantResults, gotResults) assert.Equal(t, tt.wantOS, gotOS) applier.AssertExpectations(t) }) } }