Skip to content

Commit f2a12f5

Browse files
authored
refactor: define a new struct for scan targets (#5397)
1 parent 6040d9f commit f2a12f5

File tree

6 files changed

+179
-140
lines changed

6 files changed

+179
-140
lines changed

pkg/fanal/types/artifact.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ type BlobInfo struct {
264264
CustomResources []CustomResource `json:",omitempty"`
265265
}
266266

267-
// ArtifactDetail is generated by applying blobs
267+
// ArtifactDetail represents the analysis result.
268268
type ArtifactDetail struct {
269269
OS OS `json:",omitempty"`
270270
Repository *Repository `json:",omitempty"`

pkg/scanner/langpkg/scan.go

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@ var (
2323
)
2424

2525
type Scanner interface {
26-
Packages(detail ftypes.ArtifactDetail, options types.ScanOptions) types.Results
27-
Scan(detail ftypes.ArtifactDetail, options types.ScanOptions) (types.Results, error)
26+
Packages(target types.ScanTarget, options types.ScanOptions) types.Results
27+
Scan(target types.ScanTarget, options types.ScanOptions) (types.Results, error)
2828
}
2929

3030
type scanner struct{}
@@ -33,20 +33,15 @@ func NewScanner() Scanner {
3333
return &scanner{}
3434
}
3535

36-
func (s *scanner) Packages(detail ftypes.ArtifactDetail, _ types.ScanOptions) types.Results {
36+
func (s *scanner) Packages(target types.ScanTarget, _ types.ScanOptions) types.Results {
3737
var results types.Results
38-
for _, app := range detail.Applications {
38+
for _, app := range target.Applications {
3939
if len(app.Libraries) == 0 {
4040
continue
4141
}
42-
target := app.FilePath
43-
if t, ok := PkgTargets[app.Type]; ok && target == "" {
44-
// When the file path is empty, we will overwrite it with the pre-defined value.
45-
target = t
46-
}
4742

4843
results = append(results, types.Result{
49-
Target: target,
44+
Target: targetName(app.Type, app.FilePath),
5045
Class: types.ClassLangPkg,
5146
Type: app.Type,
5247
Packages: app.Libraries,
@@ -55,8 +50,8 @@ func (s *scanner) Packages(detail ftypes.ArtifactDetail, _ types.ScanOptions) ty
5550
return results
5651
}
5752

58-
func (s *scanner) Scan(detail ftypes.ArtifactDetail, _ types.ScanOptions) (types.Results, error) {
59-
apps := detail.Applications
53+
func (s *scanner) Scan(target types.ScanTarget, _ types.ScanOptions) (types.Results, error) {
54+
apps := target.Applications
6055
log.Logger.Infof("Number of language-specific files: %d", len(apps))
6156
if len(apps) == 0 {
6257
return nil, nil
@@ -83,14 +78,8 @@ func (s *scanner) Scan(detail ftypes.ArtifactDetail, _ types.ScanOptions) (types
8378
continue
8479
}
8580

86-
target := app.FilePath
87-
if t, ok := PkgTargets[app.Type]; ok && target == "" {
88-
// When the file path is empty, we will overwrite it with the pre-defined value.
89-
target = t
90-
}
91-
9281
results = append(results, types.Result{
93-
Target: target,
82+
Target: targetName(app.Type, app.FilePath),
9483
Vulnerabilities: vulns,
9584
Class: types.ClassLangPkg,
9685
Type: app.Type,
@@ -101,3 +90,11 @@ func (s *scanner) Scan(detail ftypes.ArtifactDetail, _ types.ScanOptions) (types
10190
})
10291
return results, nil
10392
}
93+
94+
func targetName(appType ftypes.LangType, filePath string) string {
95+
if t, ok := PkgTargets[appType]; ok && filePath == "" {
96+
// When the file path is empty, we will overwrite it with the pre-defined value.
97+
return t
98+
}
99+
return filePath
100+
}

pkg/scanner/local/scan.go

Lines changed: 112 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -57,24 +57,25 @@ func NewScanner(a applier.Applier, osPkgScanner ospkg.Scanner, langPkgScanner la
5757
}
5858

5959
// Scan scans the artifact and return results.
60-
func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys []string, options types.ScanOptions) (types.Results, ftypes.OS, error) {
61-
artifactDetail, err := s.applier.ApplyLayers(artifactKey, blobKeys)
60+
func (s Scanner) Scan(ctx context.Context, targetName, artifactKey string, blobKeys []string, options types.ScanOptions) (
61+
types.Results, ftypes.OS, error) {
62+
detail, err := s.applier.ApplyLayers(artifactKey, blobKeys)
6263
switch {
6364
case errors.Is(err, analyzer.ErrUnknownOS):
6465
log.Logger.Debug("OS is not detected.")
6566

6667
// Packages may contain OS-independent binary information even though OS is not detected.
67-
if len(artifactDetail.Packages) != 0 {
68-
artifactDetail.OS = ftypes.OS{Family: "none"}
68+
if len(detail.Packages) != 0 {
69+
detail.OS = ftypes.OS{Family: "none"}
6970
}
7071

7172
// If OS is not detected and repositories are detected, we'll try to use repositories as OS.
72-
if artifactDetail.Repository != nil {
73-
log.Logger.Debugf("Package repository: %s %s", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
74-
log.Logger.Debugf("Assuming OS is %s %s.", artifactDetail.Repository.Family, artifactDetail.Repository.Release)
75-
artifactDetail.OS = ftypes.OS{
76-
Family: artifactDetail.Repository.Family,
77-
Name: artifactDetail.Repository.Release,
73+
if detail.Repository != nil {
74+
log.Logger.Debugf("Package repository: %s %s", detail.Repository.Family, detail.Repository.Release)
75+
log.Logger.Debugf("Assuming OS is %s %s.", detail.Repository.Family, detail.Repository.Release)
76+
detail.OS = ftypes.OS{
77+
Family: detail.Repository.Family,
78+
Name: detail.Repository.Release,
7879
}
7980
}
8081
case errors.Is(err, analyzer.ErrNoPkgsDetected):
@@ -84,29 +85,46 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys
8485
return nil, ftypes.OS{}, xerrors.Errorf("failed to apply layers: %w", err)
8586
}
8687

88+
target := types.ScanTarget{
89+
Name: targetName,
90+
OS: detail.OS,
91+
Repository: detail.Repository,
92+
Packages: mergePkgs(detail.Packages, detail.ImageConfig.Packages, options),
93+
Applications: detail.Applications,
94+
Misconfigurations: mergeMisconfigurations(targetName, detail),
95+
Secrets: mergeSecrets(targetName, detail),
96+
Licenses: detail.Licenses,
97+
CustomResources: detail.CustomResources,
98+
}
99+
100+
return s.ScanTarget(ctx, target, options)
101+
}
102+
103+
func (s Scanner) ScanTarget(ctx context.Context, target types.ScanTarget, options types.ScanOptions) (types.Results, ftypes.OS, error) {
87104
var eosl bool
88105
var results, pkgResults types.Results
106+
var err error
89107

90108
// By default, we need to remove dev dependencies from the result
91109
// IncludeDevDeps option allows you not to remove them
92-
excludeDevDeps(artifactDetail.Applications, options.IncludeDevDeps)
110+
excludeDevDeps(target.Applications, options.IncludeDevDeps)
93111

94112
// Fill OS packages and language-specific packages
95113
if options.ListAllPackages {
96-
if res := s.osPkgScanner.Packages(target, artifactDetail, options); len(res.Packages) != 0 {
114+
if res := s.osPkgScanner.Packages(target, options); len(res.Packages) != 0 {
97115
pkgResults = append(pkgResults, res)
98116
}
99-
pkgResults = append(pkgResults, s.langPkgScanner.Packages(artifactDetail, options)...)
117+
pkgResults = append(pkgResults, s.langPkgScanner.Packages(target, options)...)
100118
}
101119

102120
// Scan packages for vulnerabilities
103121
if options.Scanners.Enabled(types.VulnerabilityScanner) {
104122
var vulnResults types.Results
105-
vulnResults, eosl, err = s.scanVulnerabilities(target, artifactDetail, options)
123+
vulnResults, eosl, err = s.scanVulnerabilities(target, options)
106124
if err != nil {
107125
return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities: %w", err)
108126
}
109-
artifactDetail.OS.Eosl = eosl
127+
target.OS.Eosl = eosl
110128

111129
// Merge package results into vulnerability results
112130
mergedResults := s.fillPkgsInVulns(pkgResults, vulnResults)
@@ -117,45 +135,20 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys
117135
results = append(results, pkgResults...)
118136
}
119137

120-
// Scan IaC config files
121-
if ShouldScanMisconfigOrRbac(options.Scanners) {
122-
configResults := s.MisconfsToResults(artifactDetail.Misconfigurations)
123-
results = append(results, configResults...)
124-
}
138+
// Store misconfigurations
139+
results = append(results, s.misconfsToResults(target.Misconfigurations, options)...)
125140

126-
// Scan secrets
127-
if options.Scanners.Enabled(types.SecretScanner) {
128-
secretResults := s.secretsToResults(artifactDetail.Secrets)
129-
results = append(results, secretResults...)
130-
}
141+
// Store secrets
142+
results = append(results, s.secretsToResults(target.Secrets, options)...)
131143

132144
// Scan licenses
133-
if options.Scanners.Enabled(types.LicenseScanner) {
134-
licenseResults := s.scanLicenses(artifactDetail, options.LicenseCategories)
135-
results = append(results, licenseResults...)
136-
}
137-
138-
// Scan misconfigurations on container image config
139-
if options.ImageConfigScanners.Enabled(types.MisconfigScanner) {
140-
if im := artifactDetail.ImageConfig.Misconfiguration; im != nil {
141-
im.FilePath = target // Set the target name to the file path as container image config is not a real file.
142-
results = append(results, s.MisconfsToResults([]ftypes.Misconfiguration{*im})...)
143-
}
144-
}
145-
146-
// Scan secrets on container image config
147-
if options.ImageConfigScanners.Enabled(types.SecretScanner) {
148-
if is := artifactDetail.ImageConfig.Secret; is != nil {
149-
is.FilePath = target // Set the target name to the file path as container image config is not a real file.
150-
results = append(results, s.secretsToResults([]ftypes.Secret{*is})...)
151-
}
152-
}
145+
results = append(results, s.scanLicenses(target, options)...)
153146

154147
// For WASM plugins and custom analyzers
155-
if len(artifactDetail.CustomResources) != 0 {
148+
if len(target.CustomResources) != 0 {
156149
results = append(results, types.Result{
157150
Class: types.ClassCustom,
158-
CustomResources: artifactDetail.CustomResources,
151+
CustomResources: target.CustomResources,
159152
})
160153
}
161154

@@ -170,16 +163,16 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys
170163
return nil, ftypes.OS{}, xerrors.Errorf("post scan error: %w", err)
171164
}
172165

173-
return results, artifactDetail.OS, nil
166+
return results, target.OS, nil
174167
}
175168

176-
func (s Scanner) scanVulnerabilities(target string, detail ftypes.ArtifactDetail, options types.ScanOptions) (
169+
func (s Scanner) scanVulnerabilities(target types.ScanTarget, options types.ScanOptions) (
177170
types.Results, bool, error) {
178171
var eosl bool
179172
var results types.Results
180173

181174
if slices.Contains(options.VulnType, types.VulnTypeOS) {
182-
vuln, detectedEOSL, err := s.osPkgScanner.Scan(target, detail, options)
175+
vuln, detectedEOSL, err := s.osPkgScanner.Scan(target, options)
183176
if err != nil {
184177
return nil, false, xerrors.Errorf("unable to scan OS packages: %w", err)
185178
} else if vuln.Target != "" {
@@ -189,7 +182,7 @@ func (s Scanner) scanVulnerabilities(target string, detail ftypes.ArtifactDetail
189182
}
190183

191184
if slices.Contains(options.VulnType, types.VulnTypeLibrary) {
192-
vulns, err := s.langPkgScanner.Scan(detail, options)
185+
vulns, err := s.langPkgScanner.Scan(target, options)
193186
if err != nil {
194187
return nil, false, xerrors.Errorf("failed to scan application libraries: %w", err)
195188
}
@@ -217,6 +210,14 @@ func (s Scanner) fillPkgsInVulns(pkgResults, vulnResults types.Results) types.Re
217210
return results
218211
}
219212

213+
func (s Scanner) misconfsToResults(misconfs []ftypes.Misconfiguration, options types.ScanOptions) types.Results {
214+
if !ShouldScanMisconfigOrRbac(options.Scanners) {
215+
return nil
216+
}
217+
218+
return s.MisconfsToResults(misconfs)
219+
}
220+
220221
// MisconfsToResults is exported for trivy-plugin-aqua purposes only
221222
func (s Scanner) MisconfsToResults(misconfs []ftypes.Misconfiguration) types.Results {
222223
log.Logger.Infof("Detected config files: %d", len(misconfs))
@@ -254,7 +255,11 @@ func (s Scanner) MisconfsToResults(misconfs []ftypes.Misconfiguration) types.Res
254255
return results
255256
}
256257

257-
func (s Scanner) secretsToResults(secrets []ftypes.Secret) types.Results {
258+
func (s Scanner) secretsToResults(secrets []ftypes.Secret, options types.ScanOptions) types.Results {
259+
if !options.Scanners.Enabled(types.SecretScanner) {
260+
return nil
261+
}
262+
258263
var results types.Results
259264
for _, secret := range secrets {
260265
log.Logger.Debugf("Secret file: %s", secret.FilePath)
@@ -268,15 +273,17 @@ func (s Scanner) secretsToResults(secrets []ftypes.Secret) types.Results {
268273
return results
269274
}
270275

271-
func (s Scanner) scanLicenses(detail ftypes.ArtifactDetail,
272-
categories map[ftypes.LicenseCategory][]string) types.Results {
273-
scanner := licensing.NewScanner(categories)
276+
func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions) types.Results {
277+
if !options.Scanners.Enabled(types.LicenseScanner) {
278+
return nil
279+
}
274280

275281
var results types.Results
282+
scanner := licensing.NewScanner(options.LicenseCategories)
276283

277284
// License - OS packages
278285
var osPkgLicenses []types.DetectedLicense
279-
for _, pkg := range detail.Packages {
286+
for _, pkg := range target.Packages {
280287
for _, license := range pkg.Licenses {
281288
category, severity := scanner.Scan(license)
282289
osPkgLicenses = append(osPkgLicenses, types.DetectedLicense{
@@ -296,7 +303,7 @@ func (s Scanner) scanLicenses(detail ftypes.ArtifactDetail,
296303
})
297304

298305
// License - language-specific packages
299-
for _, app := range detail.Applications {
306+
for _, app := range target.Applications {
300307
var langLicenses []types.DetectedLicense
301308
for _, lib := range app.Libraries {
302309
for _, license := range lib.Licenses {
@@ -311,21 +318,21 @@ func (s Scanner) scanLicenses(detail ftypes.ArtifactDetail,
311318
}
312319
}
313320

314-
target := app.FilePath
315-
if t, ok := langpkg.PkgTargets[app.Type]; ok && target == "" {
321+
targetName := app.FilePath
322+
if t, ok := langpkg.PkgTargets[app.Type]; ok && targetName == "" {
316323
// When the file path is empty, we will overwrite it with the pre-defined value.
317-
target = t
324+
targetName = t
318325
}
319326
results = append(results, types.Result{
320-
Target: target,
327+
Target: targetName,
321328
Class: types.ClassLicense,
322329
Licenses: langLicenses,
323330
})
324331
}
325332

326333
// License - file header or license file
327334
var fileLicenses []types.DetectedLicense
328-
for _, license := range detail.Licenses {
335+
for _, license := range target.Licenses {
329336
for _, finding := range license.Findings {
330337
category, severity := scanner.Scan(finding.Name)
331338
fileLicenses = append(fileLicenses, types.DetectedLicense{
@@ -420,3 +427,46 @@ func excludeDevDeps(apps []ftypes.Application, include bool) {
420427
})
421428
}
422429
}
430+
431+
func mergePkgs(pkgs, pkgsFromCommands []ftypes.Package, options types.ScanOptions) []ftypes.Package {
432+
if !options.ScanRemovedPackages || len(pkgsFromCommands) == 0 {
433+
return pkgs
434+
}
435+
436+
// pkg has priority over pkgsFromCommands
437+
uniqPkgs := make(map[string]struct{})
438+
for _, pkg := range pkgs {
439+
uniqPkgs[pkg.Name] = struct{}{}
440+
}
441+
for _, pkg := range pkgsFromCommands {
442+
if _, ok := uniqPkgs[pkg.Name]; ok {
443+
continue
444+
}
445+
pkgs = append(pkgs, pkg)
446+
}
447+
return pkgs
448+
}
449+
450+
// mergeMisconfigurations merges misconfigurations on container image config
451+
func mergeMisconfigurations(targetName string, detail ftypes.ArtifactDetail) []ftypes.Misconfiguration {
452+
if detail.ImageConfig.Misconfiguration == nil {
453+
return detail.Misconfigurations
454+
}
455+
456+
// Append misconfigurations on container image config
457+
misconf := detail.ImageConfig.Misconfiguration
458+
misconf.FilePath = targetName // Set the target name to the file path as container image config is not a real file.
459+
return append(detail.Misconfigurations, *misconf)
460+
}
461+
462+
// mergeSecrets merges secrets on container image config.
463+
func mergeSecrets(targetName string, detail ftypes.ArtifactDetail) []ftypes.Secret {
464+
if detail.ImageConfig.Secret == nil {
465+
return detail.Secrets
466+
}
467+
468+
// Append secrets on container image config
469+
secret := detail.ImageConfig.Secret
470+
secret.FilePath = targetName // Set the target name to the file path as container image config is not a real file.
471+
return append(detail.Secrets, *secret)
472+
}

0 commit comments

Comments
 (0)