Skip to content

Commit

Permalink
fix: scan secrets only for changed files (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfyda authored Mar 22, 2024
1 parent 0929fb5 commit 7a8a2f7
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 68 deletions.
5 changes: 5 additions & 0 deletions docs/multiple-tests/all-patterns/patterns.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<module name="root">
<module name="secret" />
<module name="vulnerability" />
</module>
10 changes: 10 additions & 0 deletions docs/multiple-tests/all-patterns/results.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" ?>
<checkstyle version="1.5">
<file name="aws-config.txt">
<error source="secret" line="1" message="Possible hardcoded secret: AWS Secret Access Key" severity="error" />
<error source="secret" line="2" message="Possible hardcoded secret: AWS Access Key ID" severity="error" />
</file>
<file name="dart/pubspec.lock">
<error source="vulnerability" line="20" message="Insecure dependency [email protected] (CVE-2021-31402: dio vulnerable to CRLF injection with HTTP method string) (update to 5.0.0)" severity="error" />
</file>
</checkstyle>
4 changes: 4 additions & 0 deletions docs/multiple-tests/all-patterns/src/aws-config.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'AWS_secret_KEY'="12ASD34qwe56CXZ78tyH10Tna543VBokN85RHCas"
AWS_ACCESS_KEY_ID=AKIA0123456789ABCDEF
"aws_account_ID":'1234-5678-9123'
AWS_example=AKIAIOSFODNN7EXAMPLE%
85 changes: 85 additions & 0 deletions docs/multiple-tests/all-patterns/src/dart/pubspec.lock
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
async:
dependency: transitive
description:
name: async
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev"
source: hosted
version: "2.11.0"
collection:
dependency: transitive
description:
name: collection
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
url: "https://pub.dev"
source: hosted
version: "1.18.0"
dio:
dependency: "direct main"
description:
name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
url: "https://pub.dev"
source: hosted
version: "4.0.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
meta:
dependency: transitive
description:
name: meta
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
url: "https://pub.dev"
source: hosted
version: "1.11.0"
path:
dependency: transitive
description:
name: path
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev"
source: hosted
version: "1.8.3"
source_span:
dependency: transitive
description:
name: source_span
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev"
source: hosted
version: "1.10.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
term_glyph:
dependency: transitive
description:
name: term_glyph
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
url: "https://pub.dev"
source: hosted
version: "1.2.1"
typed_data:
dependency: transitive
description:
name: typed_data
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev"
source: hosted
version: "1.3.2"
sdks:
dart: ">=3.1.0 <4.0.0"
175 changes: 116 additions & 59 deletions internal/tool/tool.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package tool

import (
"bytes"
"context"
"fmt"
"os"
"path"

"github.com/aquasecurity/trivy/pkg/fanal/secret"
"github.com/aquasecurity/trivy/pkg/flag"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/types"
types "github.com/aquasecurity/trivy/pkg/types"
codacy "github.com/codacy/codacy-engine-golang-seed/v6"
"github.com/samber/lo"
)
Expand Down Expand Up @@ -38,21 +42,63 @@ func (t codacyTrivy) Run(ctx context.Context, toolExecution codacy.ToolExecution
return []codacy.Result{}, nil
}

config, err := newConfiguration(*toolExecution.Patterns, toolExecution.SourceDir)
err := newConfiguration(*toolExecution.Patterns)
if err != nil {
return nil, err
}

issues, err := t.run(ctx, *config)
vulnerabilityScanningIssues, err := t.runVulnerabilityScanning(ctx, toolExecution)
if err != nil {
return nil, err
}

// Filter only valid results
return mapIssuesWithoutLineNumber(filterIssuesFromKnownFiles(issues, *toolExecution.Files)), nil
secretScanningIssues, err := t.runSecretScanning(*toolExecution.Patterns, toolExecution.Files, toolExecution.SourceDir)
if err != nil {
return nil, err
}

allIssues := append(vulnerabilityScanningIssues, secretScanningIssues...)

return allIssues, nil
}

func (t codacyTrivy) run(ctx context.Context, config flag.Options) ([]codacy.Issue, error) {
func (t codacyTrivy) runVulnerabilityScanning(ctx context.Context, toolExecution codacy.ToolExecution) ([]codacy.Result, error) {
vulnerabilityScanningEnabled := lo.SomeBy(*toolExecution.Patterns, func(p codacy.Pattern) bool {
return p.ID == ruleIDVulnerability
})
if !vulnerabilityScanningEnabled {
return []codacy.Result{}, nil
}

config := flag.Options{
GlobalOptions: flag.GlobalOptions{
// CacheDir needs to be explicitly set and match the directory in the Dockerfile.
// The cache dir will contain the pre-downloaded vulnerability DBs.
CacheDir: cacheDir,
},
DBOptions: flag.DBOptions{
// Do not try to update vulnerability DBs.
SkipDBUpdate: true,
SkipJavaDBUpdate: true,
},
ReportOptions: flag.ReportOptions{
// Listing all packages will allow to obtain the line number of a vulnerability.
ListAllPkgs: true,
},
ScanOptions: flag.ScanOptions{
// Do not try to connect to the internet to download vulnerability DBs, for example.
OfflineScan: true,
Scanners: types.Scanners{types.VulnerabilityScanner},
// Instead of scanning files individually, scan the whole source directory since it's faster.
// Then filter issues from files that were not supposed to be analysed.
Target: toolExecution.SourceDir,
},
VulnerabilityOptions: flag.VulnerabilityOptions{
// Only scan libraries not OS packages.
VulnType: []types.VulnType{types.VulnTypeLibrary},
},
}

runner, err := t.runnerFactory.NewRunner(ctx, config)
if err != nil {
return nil, err
Expand All @@ -76,7 +122,6 @@ func (t codacyTrivy) run(ctx context.Context, config flag.Options) ([]codacy.Iss
lineNumberByPackageId[pkgID(pkg.ID, pkg.Name, pkg.Version)] = lineNumber
}

// Vulnerability scanning results
for _, vuln := range result.Vulnerabilities {
ID := pkgID(vuln.PkgID, vuln.PkgName, vuln.InstalledVersion)
issues = append(
Expand All @@ -90,69 +135,29 @@ func (t codacyTrivy) run(ctx context.Context, config flag.Options) ([]codacy.Iss
)
}

// Secret scanning results
for _, secret := range result.Secrets {
issues = append(
issues,
codacy.Issue{
File: result.Target,
Line: secret.StartLine,
Message: fmt.Sprintf("Possible hardcoded secret: %s", secret.Title),
PatternID: ruleIDSecret,
},
)
}
}
return issues, nil

return mapIssuesWithoutLineNumber(filterIssuesFromKnownFiles(issues, *toolExecution.Files)), nil
}

func newConfiguration(patterns []codacy.Pattern, sourceDir string) (*flag.Options, error) {
scanners := types.Scanners{}
for _, pattern := range patterns {
switch pattern.ID {
case ruleIDSecret:
scanners = append(scanners, types.SecretScanner)
case ruleIDVulnerability:
scanners = append(scanners, types.VulnerabilityScanner)
}
func newConfiguration(patterns []codacy.Pattern) error {
if len(patterns) == 0 {
return &ToolError{msg: "Failed to configure Codacy Trivy: no patterns configured"}
}

if len(scanners) == 0 {
return nil, &ToolError{msg: "Failed to configure Codacy Trivy: provided patterns don't match existing rules"}
noSupportedPatterns := lo.NoneBy(patterns, func(p codacy.Pattern) bool {
return p.ID == ruleIDSecret || p.ID == ruleIDVulnerability
})

if noSupportedPatterns {
return &ToolError{msg: "Failed to configure Codacy Trivy: provided patterns don't match existing rules"}
}

// The `quiet` field in global options is not used by the runner.
// This is the only way to suppress Trivy logs.
log.InitLogger(false, true)

return &flag.Options{
GlobalOptions: flag.GlobalOptions{
// CacheDir needs to be explicitly set and match the directory in the Dockerfile.
// The cache dir will contain the pre-downloaded vulnerability DBs.
CacheDir: cacheDir,
},
DBOptions: flag.DBOptions{
// Do not try to update vulnerability DBs.
SkipDBUpdate: true,
SkipJavaDBUpdate: true,
},
ReportOptions: flag.ReportOptions{
// Listing all packages will allow to obtain the line number of a vulnerability.
ListAllPkgs: true,
},
ScanOptions: flag.ScanOptions{
// Do not try to connect to the internet to download vulnerability DBs, for example.
OfflineScan: true,
Scanners: scanners,
// Instead of scanning files individually, scan the whole source directory since it's faster.
// Then filter issues from files that were not supposed to be analysed.
Target: sourceDir,
},
VulnerabilityOptions: flag.VulnerabilityOptions{
// Only scan libraries not OS packages.
VulnType: []types.VulnType{types.VulnTypeLibrary},
},
}, nil
return nil
}

// Results without a line number (0 is the empty value) can't be displayed by Codacy and are mapped to a `codacy.FileError`.
Expand Down Expand Up @@ -192,3 +197,55 @@ func pkgID(ID, name, version string) string {
}
return fmt.Sprintf("%s@%s", name, version)
}

// Running Trivy for secret scanning is not as efficient as running for vulnerability scanning.
// It's much more efficient to run the two scan separately, even though that results in more wrapper code.
func (t codacyTrivy) runSecretScanning(patterns []codacy.Pattern, files *[]string, sourceDir string) ([]codacy.Result, error) {
secretDetectionEnabled := lo.SomeBy(patterns, func(p codacy.Pattern) bool {
return p.ID == ruleIDSecret
})
if !secretDetectionEnabled {
return []codacy.Result{}, nil
}

if files == nil || len(*files) == 0 {
// TODO Run for all files in the source dir?
return []codacy.Result{}, nil
}

scanner := secret.NewScanner(&secret.Config{})

results := []codacy.Result{}

for _, f := range *files {

filePath := path.Join(sourceDir, f)
content, err := os.ReadFile(filePath)

if err != nil {
results = append(
results,
codacy.FileError{
File: f,
Message: "Failed to read source file",
},
)
}
content = bytes.ReplaceAll(content, []byte("\r"), []byte(""))

secrets := scanner.Scan(secret.ScanArgs{FilePath: filePath, Content: content})

for _, result := range secrets.Findings {
results = append(
results,
codacy.Issue{
File: f,
Message: fmt.Sprintf("Possible hardcoded secret: %s", result.Title),
PatternID: ruleIDSecret,
Line: result.StartLine,
},
)
}
}
return results, nil
}
21 changes: 12 additions & 9 deletions internal/tool/tool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func TestRun(t *testing.T) {
},
ScanOptions: flag.ScanOptions{
OfflineScan: true,
Scanners: types.Scanners{types.SecretScanner, types.VulnerabilityScanner},
Scanners: types.Scanners{types.VulnerabilityScanner},
Target: sourceDir,
},
VulnerabilityOptions: flag.VulnerabilityOptions{
Expand Down Expand Up @@ -174,12 +174,6 @@ func TestRun(t *testing.T) {
PatternID: ruleIDVulnerability,
Message: "Insecure dependency package-1 (vuln id: vuln title) (update to vuln fixed)",
},
codacy.Issue{
File: file2,
Line: 2,
PatternID: ruleIDSecret,
Message: "Possible hardcoded secret: secret title",
},
codacy.FileError{
File: file1,
Message: "Line numbers not supported",
Expand All @@ -188,6 +182,14 @@ func TestRun(t *testing.T) {
File: file2,
Message: "Line numbers not supported",
},
codacy.FileError{
File: file1,
Message: "Failed to read source file",
},
codacy.FileError{
File: file2,
Message: "Failed to read source file",
},
}
assert.ElementsMatch(t, expectedResults, results)
}
Expand Down Expand Up @@ -234,9 +236,10 @@ func TestRunNewRunnerError(t *testing.T) {
toolExecution := codacy.ToolExecution{
Patterns: &[]codacy.Pattern{
{
ID: ruleIDSecret,
ID: ruleIDVulnerability,
},
},
Files: &[]string{},
}

underTest := codacyTrivy{
Expand Down Expand Up @@ -284,7 +287,7 @@ func TestRunScanFilesystemError(t *testing.T) {
},
ScanOptions: flag.ScanOptions{
OfflineScan: true,
Scanners: types.Scanners{types.SecretScanner, types.VulnerabilityScanner},
Scanners: types.Scanners{types.VulnerabilityScanner},
Target: sourceDir,
},
VulnerabilityOptions: flag.VulnerabilityOptions{
Expand Down

0 comments on commit 7a8a2f7

Please sign in to comment.