github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/scanner/local/scan.go (about) 1 package local 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 "strings" 9 10 "github.com/google/wire" 11 "github.com/samber/lo" 12 "golang.org/x/exp/slices" 13 "golang.org/x/xerrors" 14 15 dbTypes "github.com/aquasecurity/trivy-db/pkg/types" 16 "github.com/devseccon/trivy/pkg/fanal/analyzer" 17 "github.com/devseccon/trivy/pkg/fanal/applier" 18 ftypes "github.com/devseccon/trivy/pkg/fanal/types" 19 "github.com/devseccon/trivy/pkg/licensing" 20 "github.com/devseccon/trivy/pkg/log" 21 "github.com/devseccon/trivy/pkg/scanner/langpkg" 22 "github.com/devseccon/trivy/pkg/scanner/ospkg" 23 "github.com/devseccon/trivy/pkg/scanner/post" 24 "github.com/devseccon/trivy/pkg/types" 25 "github.com/devseccon/trivy/pkg/vulnerability" 26 27 _ "github.com/devseccon/trivy/pkg/fanal/analyzer/all" 28 _ "github.com/devseccon/trivy/pkg/fanal/handler/all" 29 ) 30 31 // SuperSet binds dependencies for Local scan 32 var SuperSet = wire.NewSet( 33 vulnerability.SuperSet, 34 applier.NewApplier, 35 ospkg.NewScanner, 36 langpkg.NewScanner, 37 NewScanner, 38 ) 39 40 // Scanner implements the OspkgDetector and LibraryDetector 41 type Scanner struct { 42 applier applier.Applier 43 osPkgScanner ospkg.Scanner 44 langPkgScanner langpkg.Scanner 45 vulnClient vulnerability.Client 46 } 47 48 // NewScanner is the factory method for Scanner 49 func NewScanner(a applier.Applier, osPkgScanner ospkg.Scanner, langPkgScanner langpkg.Scanner, 50 vulnClient vulnerability.Client) Scanner { 51 return Scanner{ 52 applier: a, 53 osPkgScanner: osPkgScanner, 54 langPkgScanner: langPkgScanner, 55 vulnClient: vulnClient, 56 } 57 } 58 59 // Scan scans the artifact and return results. 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) 63 switch { 64 case errors.Is(err, analyzer.ErrUnknownOS): 65 log.Logger.Debug("OS is not detected.") 66 67 // Packages may contain OS-independent binary information even though OS is not detected. 68 if len(detail.Packages) != 0 { 69 detail.OS = ftypes.OS{Family: "none"} 70 } 71 72 // If OS is not detected and repositories are detected, we'll try to use repositories as OS. 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, 79 } 80 } 81 case errors.Is(err, analyzer.ErrNoPkgsDetected): 82 log.Logger.Warn("No OS package is detected. Make sure you haven't deleted any files that contain information about the installed packages.") 83 log.Logger.Warn(`e.g. files under "/lib/apk/db/", "/var/lib/dpkg/" and "/var/lib/rpm"`) 84 case err != nil: 85 return nil, ftypes.OS{}, xerrors.Errorf("failed to apply layers: %w", err) 86 } 87 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) { 104 var eosl bool 105 var results, pkgResults types.Results 106 var err error 107 108 // By default, we need to remove dev dependencies from the result 109 // IncludeDevDeps option allows you not to remove them 110 excludeDevDeps(target.Applications, options.IncludeDevDeps) 111 112 // Fill OS packages and language-specific packages 113 if options.ListAllPackages { 114 if res := s.osPkgScanner.Packages(target, options); len(res.Packages) != 0 { 115 pkgResults = append(pkgResults, res) 116 } 117 pkgResults = append(pkgResults, s.langPkgScanner.Packages(target, options)...) 118 } 119 120 // Scan packages for vulnerabilities 121 if options.Scanners.Enabled(types.VulnerabilityScanner) { 122 var vulnResults types.Results 123 vulnResults, eosl, err = s.scanVulnerabilities(target, options) 124 if err != nil { 125 return nil, ftypes.OS{}, xerrors.Errorf("failed to detect vulnerabilities: %w", err) 126 } 127 target.OS.Eosl = eosl 128 129 // Merge package results into vulnerability results 130 mergedResults := s.fillPkgsInVulns(pkgResults, vulnResults) 131 132 results = append(results, mergedResults...) 133 } else { 134 // If vulnerability scanning is not enabled, it just adds package results. 135 results = append(results, pkgResults...) 136 } 137 138 // Store misconfigurations 139 results = append(results, s.misconfsToResults(target.Misconfigurations, options)...) 140 141 // Store secrets 142 results = append(results, s.secretsToResults(target.Secrets, options)...) 143 144 // Scan licenses 145 results = append(results, s.scanLicenses(target, options)...) 146 147 // For WASM plugins and custom analyzers 148 if len(target.CustomResources) != 0 { 149 results = append(results, types.Result{ 150 Class: types.ClassCustom, 151 CustomResources: target.CustomResources, 152 }) 153 } 154 155 for i := range results { 156 // Fill vulnerability details 157 s.vulnClient.FillInfo(results[i].Vulnerabilities) 158 } 159 160 // Post scanning 161 results, err = post.Scan(ctx, results) 162 if err != nil { 163 return nil, ftypes.OS{}, xerrors.Errorf("post scan error: %w", err) 164 } 165 166 return results, target.OS, nil 167 } 168 169 func (s Scanner) scanVulnerabilities(target types.ScanTarget, options types.ScanOptions) ( 170 types.Results, bool, error) { 171 var eosl bool 172 var results types.Results 173 174 if slices.Contains(options.VulnType, types.VulnTypeOS) { 175 vuln, detectedEOSL, err := s.osPkgScanner.Scan(target, options) 176 if err != nil { 177 return nil, false, xerrors.Errorf("unable to scan OS packages: %w", err) 178 } else if vuln.Target != "" { 179 results = append(results, vuln) 180 } 181 eosl = detectedEOSL 182 } 183 184 if slices.Contains(options.VulnType, types.VulnTypeLibrary) { 185 vulns, err := s.langPkgScanner.Scan(target, options) 186 if err != nil { 187 return nil, false, xerrors.Errorf("failed to scan application libraries: %w", err) 188 } 189 results = append(results, vulns...) 190 } 191 192 return results, eosl, nil 193 } 194 195 func (s Scanner) fillPkgsInVulns(pkgResults, vulnResults types.Results) types.Results { 196 var results types.Results 197 if len(pkgResults) == 0 { // '--list-all-pkgs' == false or packages not found 198 return vulnResults 199 } 200 for _, result := range pkgResults { 201 if r, found := lo.Find(vulnResults, func(r types.Result) bool { 202 return r.Class == result.Class && r.Target == result.Target && r.Type == result.Type 203 }); found { 204 r.Packages = result.Packages 205 results = append(results, r) 206 } else { // when package result has no vulnerabilities we still need to add it to result(for 'list-all-pkgs') 207 results = append(results, result) 208 } 209 } 210 return results 211 } 212 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 221 // MisconfsToResults is exported for trivy-plugin-aqua purposes only 222 func (s Scanner) MisconfsToResults(misconfs []ftypes.Misconfiguration) types.Results { 223 log.Logger.Infof("Detected config files: %d", len(misconfs)) 224 var results types.Results 225 for _, misconf := range misconfs { 226 log.Logger.Debugf("Scanned config file: %s", misconf.FilePath) 227 228 var detected []types.DetectedMisconfiguration 229 230 for _, f := range misconf.Failures { 231 detected = append(detected, toDetectedMisconfiguration(f, dbTypes.SeverityCritical, types.StatusFailure, misconf.Layer)) 232 } 233 for _, w := range misconf.Warnings { 234 detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityMedium, types.StatusFailure, misconf.Layer)) 235 } 236 for _, w := range misconf.Successes { 237 detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityUnknown, types.StatusPassed, misconf.Layer)) 238 } 239 for _, w := range misconf.Exceptions { 240 detected = append(detected, toDetectedMisconfiguration(w, dbTypes.SeverityUnknown, types.StatusException, misconf.Layer)) 241 } 242 243 results = append(results, types.Result{ 244 Target: misconf.FilePath, 245 Class: types.ClassConfig, 246 Type: misconf.FileType, 247 Misconfigurations: detected, 248 }) 249 } 250 251 sort.Slice(results, func(i, j int) bool { 252 return results[i].Target < results[j].Target 253 }) 254 255 return results 256 } 257 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 263 var results types.Results 264 for _, secret := range secrets { 265 log.Logger.Debugf("Secret file: %s", secret.FilePath) 266 267 results = append(results, types.Result{ 268 Target: secret.FilePath, 269 Class: types.ClassSecret, 270 Secrets: secret.Findings, 271 }) 272 } 273 return results 274 } 275 276 func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions) types.Results { 277 if !options.Scanners.Enabled(types.LicenseScanner) { 278 return nil 279 } 280 281 var results types.Results 282 scanner := licensing.NewScanner(options.LicenseCategories) 283 284 // License - OS packages 285 var osPkgLicenses []types.DetectedLicense 286 for _, pkg := range target.Packages { 287 for _, license := range pkg.Licenses { 288 category, severity := scanner.Scan(license) 289 osPkgLicenses = append(osPkgLicenses, types.DetectedLicense{ 290 Severity: severity, 291 Category: category, 292 PkgName: pkg.Name, 293 Name: license, 294 Confidence: 1.0, 295 }) 296 } 297 298 } 299 results = append(results, types.Result{ 300 Target: "OS Packages", 301 Class: types.ClassLicense, 302 Licenses: osPkgLicenses, 303 }) 304 305 // License - language-specific packages 306 for _, app := range target.Applications { 307 var langLicenses []types.DetectedLicense 308 for _, lib := range app.Libraries { 309 for _, license := range lib.Licenses { 310 category, severity := scanner.Scan(license) 311 langLicenses = append(langLicenses, types.DetectedLicense{ 312 Severity: severity, 313 Category: category, 314 PkgName: lib.Name, 315 Name: license, 316 Confidence: 1.0, 317 }) 318 } 319 } 320 321 targetName := app.FilePath 322 if t, ok := langpkg.PkgTargets[app.Type]; ok && targetName == "" { 323 // When the file path is empty, we will overwrite it with the pre-defined value. 324 targetName = t 325 } 326 results = append(results, types.Result{ 327 Target: targetName, 328 Class: types.ClassLicense, 329 Licenses: langLicenses, 330 }) 331 } 332 333 // License - file header or license file 334 var fileLicenses []types.DetectedLicense 335 for _, license := range target.Licenses { 336 for _, finding := range license.Findings { 337 category, severity := scanner.Scan(finding.Name) 338 fileLicenses = append(fileLicenses, types.DetectedLicense{ 339 Severity: severity, 340 Category: category, 341 FilePath: license.FilePath, 342 Name: finding.Name, 343 Confidence: finding.Confidence, 344 Link: finding.Link, 345 }) 346 347 } 348 } 349 results = append(results, types.Result{ 350 Target: "Loose File License(s)", 351 Class: types.ClassLicenseFile, 352 Licenses: fileLicenses, 353 }) 354 355 return results 356 } 357 358 func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity, 359 status types.MisconfStatus, layer ftypes.Layer) types.DetectedMisconfiguration { 360 361 severity := defaultSeverity 362 sev, err := dbTypes.NewSeverity(res.Severity) 363 if err != nil { 364 log.Logger.Warnf("severity must be %s, but %s", dbTypes.SeverityNames, res.Severity) 365 } else { 366 severity = sev 367 } 368 369 msg := strings.TrimSpace(res.Message) 370 if msg == "" { 371 msg = "No issues found" 372 } 373 374 var primaryURL string 375 376 // empty namespace implies a go rule from defsec, "builtin" refers to a built-in rego rule 377 // this ensures we don't generate bad links for custom policies 378 if res.Namespace == "" || strings.HasPrefix(res.Namespace, "builtin.") { 379 primaryURL = fmt.Sprintf("https://avd.aquasec.com/misconfig/%s", strings.ToLower(res.ID)) 380 res.References = append(res.References, primaryURL) 381 } 382 383 if primaryURL == "" && len(res.References) > 0 { 384 primaryURL = res.References[0] 385 } 386 387 return types.DetectedMisconfiguration{ 388 ID: res.ID, 389 AVDID: res.AVDID, 390 Type: res.Type, 391 Title: res.Title, 392 Description: res.Description, 393 Message: msg, 394 Resolution: res.RecommendedActions, 395 Namespace: res.Namespace, 396 Query: res.Query, 397 Severity: severity.String(), 398 PrimaryURL: primaryURL, 399 References: res.References, 400 Status: status, 401 Layer: layer, 402 Traces: res.Traces, 403 CauseMetadata: ftypes.CauseMetadata{ 404 Resource: res.Resource, 405 Provider: res.Provider, 406 Service: res.Service, 407 StartLine: res.StartLine, 408 EndLine: res.EndLine, 409 Code: res.Code, 410 Occurrences: res.Occurrences, 411 }, 412 } 413 } 414 415 func ShouldScanMisconfigOrRbac(scanners types.Scanners) bool { 416 return scanners.AnyEnabled(types.MisconfigScanner, types.RBACScanner) 417 } 418 419 // excludeDevDeps removes development dependencies from the list of applications 420 func excludeDevDeps(apps []ftypes.Application, include bool) { 421 if include { 422 return 423 } 424 for i := range apps { 425 apps[i].Libraries = lo.Filter(apps[i].Libraries, func(lib ftypes.Package, index int) bool { 426 return !lib.Dev 427 }) 428 } 429 } 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 }