From e78b7d3ac984f8e42bd30818321490419f4d437f Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Wed, 5 Jun 2024 16:57:31 +0100 Subject: [PATCH 01/15] Add parser for sbt-dependency-lock --- pkg/dependency/id.go | 2 +- pkg/dependency/id_test.go | 9 ++ pkg/dependency/parser/sbt/lockfile/parse.go | 75 ++++++++++++++++ .../parser/sbt/lockfile/parse_test.go | 87 +++++++++++++++++++ .../sbt/lockfile/testdata/empty.sbt.lock | 10 +++ .../sbt/lockfile/testdata/v1_happy.sbt.lock | 59 +++++++++++++ pkg/fanal/types/const.go | 1 + 7 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 pkg/dependency/parser/sbt/lockfile/parse.go create mode 100644 pkg/dependency/parser/sbt/lockfile/parse_test.go create mode 100644 pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock create mode 100644 pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock diff --git a/pkg/dependency/id.go b/pkg/dependency/id.go index d40289cedc6a..8f01bf23123a 100644 --- a/pkg/dependency/id.go +++ b/pkg/dependency/id.go @@ -25,7 +25,7 @@ func ID(ltype types.LangType, name, version string) string { if !strings.HasPrefix(version, "v") { version = "v" + version } - case types.Jar, types.Pom, types.Gradle: + case types.Jar, types.Pom, types.Gradle, types.Sbt: sep = ":" } return name + sep + version diff --git a/pkg/dependency/id_test.go b/pkg/dependency/id_test.go index 68e380e6c651..18359f771e7b 100644 --- a/pkg/dependency/id_test.go +++ b/pkg/dependency/id_test.go @@ -47,6 +47,15 @@ func TestID(t *testing.T) { }, want: "test:1.0.0", }, + { + name: "sbt", + args: args{ + ltype: types.Sbt, + name: "test", + version: "1.0.0", + }, + want: "test:1.0.0", + }, { name: "pip", args: args{ diff --git a/pkg/dependency/parser/sbt/lockfile/parse.go b/pkg/dependency/parser/sbt/lockfile/parse.go new file mode 100644 index 000000000000..87e111dd5423 --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/parse.go @@ -0,0 +1,75 @@ +package lockfile + +import ( + "github.com/aquasecurity/trivy/pkg/dependency" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + xio "github.com/aquasecurity/trivy/pkg/x/io" + "github.com/liamg/jfather" + "golang.org/x/xerrors" + "io" +) + +type Parser struct{} + +func NewParser() *Parser { + return &Parser{} +} + +func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, error) { + var lockfile sbtLockfile + input, err := io.ReadAll(r) + + if err != nil { + return nil, nil, xerrors.Errorf("failed to read sbt lockfile: %w", err) + } + if err := jfather.Unmarshal(input, &lockfile); err != nil { + return nil, nil, xerrors.Errorf("JSON decoding failed: %w", err) + } + + var libraries []ftypes.Package + + for _, dep := range lockfile.Dependencies { + libraries = append(libraries, ftypes.Package{ + ID: dependency.ID(ftypes.Sbt, dep.Organization+":"+dep.Name, dep.Version), + Name: dep.Organization + ":" + dep.Name, + Version: dep.Version, + Locations: []ftypes.Location{{StartLine: dep.StartLine, EndLine: dep.EndLine}}, + }) + } + + return libraries, nil, nil +} + +// UnmarshalJSONWithMetadata needed to detect start and end lines of deps +func (t *sbtLockfileDependency) UnmarshalJSONWithMetadata(node jfather.Node) error { + if err := node.Decode(&t); err != nil { + return err + } + // Decode func will overwrite line numbers if we save them first + t.StartLine = node.Range().Start.Line + t.EndLine = node.Range().End.Line + return nil +} + +// lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html +type sbtLockfile struct { + Version int `json:"lockVersion"` + Timestamp string `json:"timestamp"` + Configurations []string `json:"configurations"` + Dependencies []sbtLockfileDependency `json:"dependencies"` +} + +type sbtLockfileDependency struct { + Organization string `json:"org"` + Name string `json:"name"` + Version string `json:"version"` + Artifacts []sbtLockfileArtifact `json:"artifacts"` + Configurations []string `json:"configurations"` + StartLine int + EndLine int +} + +type sbtLockfileArtifact struct { + Name string `json:"name"` + Hash string `json:"hash"` +} diff --git a/pkg/dependency/parser/sbt/lockfile/parse_test.go b/pkg/dependency/parser/sbt/lockfile/parse_test.go new file mode 100644 index 000000000000..acb5e8d7852d --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/parse_test.go @@ -0,0 +1,87 @@ +package lockfile + +import ( + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "os" + "sort" + "strings" + "testing" +) + +func TestParser_Parse(t *testing.T) { + tests := []struct { + name string + inputFile string + want []ftypes.Package + }{ + { + name: "v1 happy path", + inputFile: "testdata/v1_happy.sbt.lock", + want: []ftypes.Package{ + { + ID: "org.apache.commons:commons-lang3:3.9", + Name: "org.apache.commons:commons-lang3", + Version: "3.9", + Locations: []ftypes.Location{ + { + StartLine: 10, + EndLine: 25, + }, + }, + }, + { + ID: "org.scala-lang:scala-library:2.12.10", + Name: "org.scala-lang:scala-library", + Version: "2.12.10", + Locations: []ftypes.Location{ + { + StartLine: 26, + EndLine: 41, + }, + }, + }, + { + ID: "org.typelevel:cats-core_2.12:2.9.0", + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + Locations: []ftypes.Location{ + { + StartLine: 42, + EndLine: 57, + }, + }, + }, + }, + }, + { + name: "empty", + inputFile: "testdata/empty.sbt.lock", + want: nil, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + parser := NewParser() + f, err := os.Open(tt.inputFile) + assert.NoError(t, err) + + libs, _, err := parser.Parse(f) + assert.Equal(t, nil, err) + + sortLibs(libs) + assert.Equal(t, tt.want, libs) + }) + } +} + +func sortLibs(libs []ftypes.Package) { + sort.Slice(libs, func(i, j int) bool { + ret := strings.Compare(libs[i].Name, libs[j].Name) + if ret == 0 { + return libs[i].Version < libs[j].Version + } + return ret < 0 + }) +} diff --git a/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock b/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock new file mode 100644 index 000000000000..6125547882da --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/testdata/empty.sbt.lock @@ -0,0 +1,10 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [] +} \ No newline at end of file diff --git a/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock new file mode 100644 index 000000000000..26b5ef401aeb --- /dev/null +++ b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock @@ -0,0 +1,59 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [ + { + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org" : "org.typelevel", + "name" : "cats-core_2.12", + "version" : "2.9.0", + "artifacts" : [ + { + "name" : "cats-core_2.12.jar", + "hash" : "sha1:844f21541d1809008586fbc1172dc02c96476639" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + } + ] +} \ No newline at end of file diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 6874b8a40b06..76e305ebe476 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -63,6 +63,7 @@ const ( Jar LangType = "jar" Pom LangType = "pom" Gradle LangType = "gradle" + Sbt LangType = "sbt" GoBinary LangType = "gobinary" GoModule LangType = "gomod" JavaScript LangType = "javascript" From 152382c605e18e85a94f706a2d4b9fba7cf03a75 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Thu, 6 Jun 2024 10:35:57 +0100 Subject: [PATCH 02/15] Add analyzer for SBT lockfiles --- pkg/fanal/analyzer/const.go | 1 + .../analyzer/language/java/sbt/lockfile.go | 88 +++++++++++++++++ .../language/java/sbt/lockfile_test.go | 94 +++++++++++++++++++ .../java/sbt/testdata/empty/build.sbt.lock | 10 ++ .../java/sbt/testdata/v1/build.sbt.lock | 59 ++++++++++++ 5 files changed, 252 insertions(+) create mode 100644 pkg/fanal/analyzer/language/java/sbt/lockfile.go create mode 100644 pkg/fanal/analyzer/language/java/sbt/lockfile_test.go create mode 100644 pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock create mode 100644 pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 99ac3cf4bee5..201cdc62bd99 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -55,6 +55,7 @@ const ( TypeJar Type = "jar" TypePom Type = "pom" TypeGradleLock Type = "gradle-lockfile" + TypeSbtLock Type = "sbt-lockfile" // Node.js TypeNpmPkgLock Type = "npm" diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go new file mode 100644 index 000000000000..3814fcc7b8f6 --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -0,0 +1,88 @@ +package sbt + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" + "golang.org/x/xerrors" + "io" + "io/fs" + "os" + "strings" + + "github.com/aquasecurity/trivy/pkg/dependency/parser/sbt/lockfile" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/log" +) + +func init() { + analyzer.RegisterPostAnalyzer(analyzer.TypeSbtLock, newSbtDependencyLockAnalyzer) +} + +const ( + version = 1 + fileNameSuffix = "sbt.lock" +) + +// sbtDependencyLockAnalyzer analyzes '*.sbt.lock' +type sbtDependencyLockAnalyzer struct { + logger *log.Logger + parser language.Parser +} + +func newSbtDependencyLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { + return &sbtDependencyLockAnalyzer{ + logger: log.WithPrefix("sbt"), + parser: lockfile.NewParser(), + }, nil +} + +func (a sbtDependencyLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { + required := func(path string, d fs.DirEntry) bool { + return a.Required(path, nil) + } + + var apps []types.Application + var err error + err = fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { + var app *types.Application + app, err = language.Parse(types.Sbt, filePath, r, a.parser) + if err != nil { + return xerrors.Errorf("%s parse error: %w", filePath, err) + } + + if app == nil { + // no dependencies - add empty application + app = &types.Application{ + Type: types.Sbt, + FilePath: filePath, + Packages: types.Packages{}, + } + } + + apps = append(apps, *app) + + return nil + }) + + if err != nil { + return nil, xerrors.Errorf("sbt walk error: %w", err) + } + + return &analyzer.AnalysisResult{ + Applications: apps, + }, nil +} + +func (a sbtDependencyLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { + return strings.HasSuffix(filePath, fileNameSuffix) +} + +func (a sbtDependencyLockAnalyzer) Type() analyzer.Type { + return analyzer.TypeSbtLock +} + +func (a sbtDependencyLockAnalyzer) Version() int { + return version +} diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go new file mode 100644 index 000000000000..1bb5acee60af --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go @@ -0,0 +1,94 @@ +package sbt + +import ( + "context" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "os" + "testing" +) + +func Test_sbtDependencyLockAnalyzer(t *testing.T) { + tests := []struct { + name string + dir string + want *analyzer.AnalysisResult + }{ + { + name: "empty lockfile", + dir: "testdata/empty", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Sbt, + FilePath: "build.sbt.lock", + Packages: types.Packages{}, + }, + }, + }, + }, + { + name: "v1 lockfile", + dir: "testdata/v1", + want: &analyzer.AnalysisResult{ + Applications: []types.Application{ + { + Type: types.Sbt, + FilePath: "build.sbt.lock", + Packages: types.Packages{ + { + ID: "org.apache.commons:commons-lang3:3.9", + Name: "org.apache.commons:commons-lang3", + Version: "3.9", + Locations: []types.Location{ + { + StartLine: 10, + EndLine: 25, + }, + }, + }, + { + ID: "org.scala-lang:scala-library:2.12.10", + Name: "org.scala-lang:scala-library", + Version: "2.12.10", + Locations: []types.Location{ + { + StartLine: 26, + EndLine: 41, + }, + }, + }, + { + ID: "org.typelevel:cats-core_2.12:2.9.0", + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + Locations: []types.Location{ + { + StartLine: 42, + EndLine: 57, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + a, err := newSbtDependencyLockAnalyzer(analyzer.AnalyzerOptions{}) + require.NoError(t, err) + + got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ + FS: os.DirFS(tt.dir), + }) + + require.NoError(t, err) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock b/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock new file mode 100644 index 000000000000..6125547882da --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/testdata/empty/build.sbt.lock @@ -0,0 +1,10 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [] +} \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock b/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock new file mode 100644 index 000000000000..26b5ef401aeb --- /dev/null +++ b/pkg/fanal/analyzer/language/java/sbt/testdata/v1/build.sbt.lock @@ -0,0 +1,59 @@ +{ + "lockVersion": 1, + "timestamp": "2024-06-05T13:41:10.992Z", + "configurations": [ + "compile", + "runtime", + "test" + ], + "dependencies": [ + { + "org": "org.apache.commons", + "name": "commons-lang3", + "version": "3.9", + "artifacts": [ + { + "name": "commons-lang3.jar", + "hash": "sha1:0122c7cee69b53ed4a7681c03d4ee4c0e2765da5" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org": "org.scala-lang", + "name": "scala-library", + "version": "2.12.10", + "artifacts": [ + { + "name": "scala-library.jar", + "hash": "sha1:3509860bc2e5b3da001ed45aca94ffbe5694dbda" + } + ], + "configurations": [ + "test", + "compile", + "runtime" + ] + }, + { + "org" : "org.typelevel", + "name" : "cats-core_2.12", + "version" : "2.9.0", + "artifacts" : [ + { + "name" : "cats-core_2.12.jar", + "hash" : "sha1:844f21541d1809008586fbc1172dc02c96476639" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + } + ] +} \ No newline at end of file From 05d78e8be640419efd77ce2a92c67c8e09d6cb22 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Thu, 6 Jun 2024 10:50:29 +0100 Subject: [PATCH 03/15] Add sbt to purl --- pkg/purl/purl.go | 2 +- pkg/purl/purl_test.go | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/purl/purl.go b/pkg/purl/purl.go index 92ce07be9741..19a537a39f76 100644 --- a/pkg/purl/purl.go +++ b/pkg/purl/purl.go @@ -442,7 +442,7 @@ func parseJulia(pkgName, pkgUUID string) (string, string, packageurl.Qualifiers) func purlType(t ftypes.TargetType) string { switch t { - case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt: return packageurl.TypeMaven case ftypes.Bundler, ftypes.GemSpec: return packageurl.TypeGem diff --git a/pkg/purl/purl_test.go b/pkg/purl/purl_test.go index ddcfc98222e6..25e9e7829d7b 100644 --- a/pkg/purl/purl_test.go +++ b/pkg/purl/purl_test.go @@ -51,6 +51,20 @@ func TestNewPackageURL(t *testing.T) { Version: "5.3.14", }, }, + { + name: "sbt package", + typ: ftypes.Sbt, + pkg: ftypes.Package{ + Name: "org.typelevel:cats-core_2.12", + Version: "2.9.0", + }, + want: &purl.PackageURL{ + Type: packageurl.TypeMaven, + Namespace: "org.typelevel", + Name: "cats-core_2.12", + Version: "2.9.0", + }, + }, { name: "yarn package", typ: ftypes.Yarn, From 1b8d34b033fd6cbf431fed2f47ca1e7f8904f320 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Thu, 6 Jun 2024 11:18:31 +0100 Subject: [PATCH 04/15] Add missing wiring for SBT --- pkg/detector/library/driver.go | 2 +- pkg/fanal/analyzer/all/import.go | 1 + pkg/fanal/analyzer/const.go | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkg/detector/library/driver.go b/pkg/detector/library/driver.go index f78932b13442..af91c30c786f 100644 --- a/pkg/detector/library/driver.go +++ b/pkg/detector/library/driver.go @@ -39,7 +39,7 @@ func NewDriver(libType ftypes.LangType) (Driver, bool) { case ftypes.GoBinary, ftypes.GoModule: ecosystem = vulnerability.Go comparer = compare.GenericComparer{} - case ftypes.Jar, ftypes.Pom, ftypes.Gradle: + case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt: ecosystem = vulnerability.Maven comparer = maven.Comparer{} case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript: diff --git a/pkg/fanal/analyzer/all/import.go b/pkg/fanal/analyzer/all/import.go index a5b0d05298a1..1849bcebf682 100644 --- a/pkg/fanal/analyzer/all/import.go +++ b/pkg/fanal/analyzer/all/import.go @@ -20,6 +20,7 @@ import ( _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/gradle" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/jar" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/pom" + _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/java/sbt" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/julia/pkg" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/npm" _ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/nodejs/pkg" diff --git a/pkg/fanal/analyzer/const.go b/pkg/fanal/analyzer/const.go index 201cdc62bd99..82bf3bb12296 100644 --- a/pkg/fanal/analyzer/const.go +++ b/pkg/fanal/analyzer/const.go @@ -174,6 +174,7 @@ var ( TypeJar, TypePom, TypeGradleLock, + TypeSbtLock, TypeNpmPkgLock, TypeNodePkg, TypeYarn, @@ -211,6 +212,7 @@ var ( TypePom, TypeConanLock, TypeGradleLock, + TypeSbtLock, TypeCocoaPods, TypeSwift, TypePubSpecLock, From 046d774e98b8438d47a28d33c4d1642fdd4a5c39 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Fri, 7 Jun 2024 10:13:52 +0100 Subject: [PATCH 05/15] Add sbt to docs --- docs/docs/configuration/reporting.md | 2 ++ docs/docs/coverage/language/index.md | 1 + docs/docs/coverage/language/java.md | 16 ++++++++++++++-- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index b8b61d34a346..17bf0b864283 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -64,6 +64,7 @@ The following languages are currently supported: | PHP | [composer.lock][composer-lock] | | Java | [pom.xml][pom-xml] | | | [*gradle.lockfile][gradle-lockfile] | +| | [*.sbt.lock][sbt-lockfile] | | Dart | [pubspec.lock][pubspec-lock] | This tree is the reverse of the dependency graph. @@ -447,5 +448,6 @@ $ trivy convert --format table --severity CRITICAL result.json [composer-lock]: ../coverage/language/php.md#composer [pom-xml]: ../coverage/language/java.md#pomxml [gradle-lockfile]: ../coverage/language/java.md#gradlelock +[sbt-lockfile]: ../coverage/language/java.md#sbt [pubspec-lock]: ../coverage/language/dart.md#dart [cargo-binaries]: ../coverage/language/rust.md#binaries \ No newline at end of file diff --git a/docs/docs/coverage/language/index.md b/docs/docs/coverage/language/index.md index eb694bbcc228..b9461b4e013b 100644 --- a/docs/docs/coverage/language/index.md +++ b/docs/docs/coverage/language/index.md @@ -38,6 +38,7 @@ On the other hand, when the target is a post-build artifact, like a container im | [Java](java.md) | JAR/WAR/PAR/EAR[^4] | ✅ | ✅ | - | - | | | pom.xml | - | - | ✅ | ✅ | | | *gradle.lockfile | - | - | ✅ | ✅ | +| | *.sbt.lock | - | - | ✅ | ✅ | | [Go](golang.md) | Binaries built by Go | ✅ | ✅ | - | - | | | go.mod | - | - | ✅ | ✅ | | [Rust](rust.md) | Cargo.lock | ✅ | ✅ | ✅ | ✅ | diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 87db939ea288..3e317b7a99b8 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -1,5 +1,5 @@ # Java -Trivy supports three types of Java scanning: `JAR/WAR/PAR/EAR`, `pom.xml` and `*gradle.lockfile` files. +Trivy supports four types of Java scanning: `JAR/WAR/PAR/EAR`, `pom.xml`, `*gradle.lockfile` and `*.sbt.lock` files. Each artifact supports the following scanners: @@ -8,6 +8,7 @@ Each artifact supports the following scanners: | JAR/WAR/PAR/EAR | ✓ | ✓ | - | | pom.xml | ✓ | ✓ | ✓ | | *gradle.lockfile | ✓ | ✓ | ✓ | +| *.sbt.lock | ✓ | ✓ | - | The following table provides an outline of the features Trivy offers. @@ -16,6 +17,7 @@ The following table provides an outline of the features Trivy offers. | JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | | pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | | *gradle.lockfile | - | Exclude | ✓ | ✓ | +| *.sbt.lock | - | Exclude | ✓ | ✓ | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. @@ -94,6 +96,15 @@ Trity also can detect licenses for dependencies. Make sure that you have cache[^8] directory to find licenses from `*.pom` dependency files. + +## SBT + +`build.sbt.lock` files only contain information about used dependencies. This requires a lockfile generated using the +[sbt-dependency-lock][sbt-dependency-lock] plugin. + +!!!note + All necessary files are checked locally. SBT file scanning doesn't require internet access. + [^1]: Uses maven repository to get information about dependencies. Internet access required. [^2]: It means `*.jar`, `*.war`, `*.par` and `*.ear` file [^3]: `ArtifactID`, `GroupID` and `Version` @@ -106,4 +117,5 @@ Make sure that you have cache[^8] directory to find licenses from `*.pom` depend [dependency-graph]: ../../configuration/reporting.md#show-origins-of-vulnerable-dependencies [maven-invoker-plugin]: https://maven.apache.org/plugins/maven-invoker-plugin/usage.html [maven-central]: https://repo.maven.apache.org/maven2/ -[maven-pom-repos]: https://maven.apache.org/settings.html#repositories \ No newline at end of file +[maven-pom-repos]: https://maven.apache.org/settings.html#repositories +[sbt-dependency-lock]: https://stringbean.github.io/sbt-dependency-lock From 336b07a85fdf552e9932af111994198e960e7de8 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Fri, 7 Jun 2024 10:27:14 +0100 Subject: [PATCH 06/15] Fix linting issues --- pkg/dependency/parser/sbt/lockfile/parse.go | 54 ++++++++++--------- .../parser/sbt/lockfile/parse_test.go | 11 ++-- .../analyzer/language/java/sbt/lockfile.go | 7 +-- .../language/java/sbt/lockfile_test.go | 10 ++-- 4 files changed, 45 insertions(+), 37 deletions(-) diff --git a/pkg/dependency/parser/sbt/lockfile/parse.go b/pkg/dependency/parser/sbt/lockfile/parse.go index 87e111dd5423..c34b8b8d78a3 100644 --- a/pkg/dependency/parser/sbt/lockfile/parse.go +++ b/pkg/dependency/parser/sbt/lockfile/parse.go @@ -1,14 +1,39 @@ package lockfile import ( + "io" + + "github.com/liamg/jfather" + "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" xio "github.com/aquasecurity/trivy/pkg/x/io" - "github.com/liamg/jfather" - "golang.org/x/xerrors" - "io" ) +// lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html +type sbtLockfile struct { + Version int `json:"lockVersion"` + Timestamp string `json:"timestamp"` + Configurations []string `json:"configurations"` + Dependencies []sbtLockfileDependency `json:"dependencies"` +} + +type sbtLockfileDependency struct { + Organization string `json:"org"` + Name string `json:"name"` + Version string `json:"version"` + Artifacts []sbtLockfileArtifact `json:"artifacts"` + Configurations []string `json:"configurations"` + StartLine int + EndLine int +} + +type sbtLockfileArtifact struct { + Name string `json:"name"` + Hash string `json:"hash"` +} + type Parser struct{} func NewParser() *Parser { @@ -50,26 +75,3 @@ func (t *sbtLockfileDependency) UnmarshalJSONWithMetadata(node jfather.Node) err t.EndLine = node.Range().End.Line return nil } - -// lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html -type sbtLockfile struct { - Version int `json:"lockVersion"` - Timestamp string `json:"timestamp"` - Configurations []string `json:"configurations"` - Dependencies []sbtLockfileDependency `json:"dependencies"` -} - -type sbtLockfileDependency struct { - Organization string `json:"org"` - Name string `json:"name"` - Version string `json:"version"` - Artifacts []sbtLockfileArtifact `json:"artifacts"` - Configurations []string `json:"configurations"` - StartLine int - EndLine int -} - -type sbtLockfileArtifact struct { - Name string `json:"name"` - Hash string `json:"hash"` -} diff --git a/pkg/dependency/parser/sbt/lockfile/parse_test.go b/pkg/dependency/parser/sbt/lockfile/parse_test.go index acb5e8d7852d..81b876f91eea 100644 --- a/pkg/dependency/parser/sbt/lockfile/parse_test.go +++ b/pkg/dependency/parser/sbt/lockfile/parse_test.go @@ -1,12 +1,15 @@ package lockfile import ( - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" "os" "sort" "strings" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" ) func TestParser_Parse(t *testing.T) { @@ -65,10 +68,10 @@ func TestParser_Parse(t *testing.T) { t.Run(tt.name, func(t *testing.T) { parser := NewParser() f, err := os.Open(tt.inputFile) - assert.NoError(t, err) + require.NoError(t, err) libs, _, err := parser.Parse(f) - assert.Equal(t, nil, err) + require.NoError(t, err) sortLibs(libs) assert.Equal(t, tt.want, libs) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 3814fcc7b8f6..341c984cf614 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -2,18 +2,19 @@ package sbt import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" - "golang.org/x/xerrors" "io" "io/fs" "os" "strings" + "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency/parser/sbt/lockfile" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" + "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func init() { diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go index 1bb5acee60af..5730807c138d 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go @@ -2,12 +2,14 @@ package sbt import ( "context" - "github.com/aquasecurity/trivy/pkg/fanal/analyzer" - "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" "os" "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" + "github.com/aquasecurity/trivy/pkg/fanal/types" ) func Test_sbtDependencyLockAnalyzer(t *testing.T) { From 510c1e49ba633f1ba8c217bdacbb3e5e7837ecb8 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Fri, 7 Jun 2024 15:41:27 +0100 Subject: [PATCH 07/15] Add integration test for sbt --- integration/repo_test.go | 8 + .../testdata/fixtures/repo/sbt/build.sbt.lock | 29 ++++ integration/testdata/sbt.json.golden | 149 ++++++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 integration/testdata/fixtures/repo/sbt/build.sbt.lock create mode 100644 integration/testdata/sbt.json.golden diff --git a/integration/repo_test.go b/integration/repo_test.go index e11e5a21a8b4..49e342d1a91a 100644 --- a/integration/repo_test.go +++ b/integration/repo_test.go @@ -153,6 +153,14 @@ func TestRepository(t *testing.T) { }, golden: "testdata/gradle.json.golden", }, + { + name: "sbt", + args: args{ + scanner: types.VulnerabilityScanner, + input: "testdata/fixtures/repo/sbt", + }, + golden: "testdata/sbt.json.golden", + }, { name: "conan", args: args{ diff --git a/integration/testdata/fixtures/repo/sbt/build.sbt.lock b/integration/testdata/fixtures/repo/sbt/build.sbt.lock new file mode 100644 index 000000000000..33bcdbee245e --- /dev/null +++ b/integration/testdata/fixtures/repo/sbt/build.sbt.lock @@ -0,0 +1,29 @@ +{ + "lockVersion" : 1, + "timestamp" : "2024-06-06T11:03:09.964557Z", + "configurations" : [ + "compile", + "optional", + "provided", + "runtime", + "test" + ], + "dependencies" : [ + { + "org" : "com.fasterxml.jackson.core", + "name" : "jackson-databind", + "version" : "2.9.1", + "artifacts" : [ + { + "name" : "jackson-databind.jar", + "hash" : "sha1:716da1830a2043f18882fc036ec26eb32cbe5aff" + } + ], + "configurations" : [ + "compile", + "runtime", + "test" + ] + } + ] +} \ No newline at end of file diff --git a/integration/testdata/sbt.json.golden b/integration/testdata/sbt.json.golden new file mode 100644 index 000000000000..94bf111fd221 --- /dev/null +++ b/integration/testdata/sbt.json.golden @@ -0,0 +1,149 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/repo/sbt", + "ArtifactType": "repository", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "build.sbt.lock", + "Class": "lang-pkgs", + "Type": "sbt", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2020-9548", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "9ccd2eb3e03373ff" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.4", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2020-9548", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory Maven", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Amaven" + }, + "Title": "jackson-databind: Serialization gadgets in anteros-core", + "Description": "FasterXML jackson-databind 2.x before 2.9.10.4 mishandles the interaction between serialization gadgets and typing, related to br.com.anteros.dbcp.AnterosDBCPConfig (aka anteros-core).", + "Severity": "CRITICAL", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 4, + "nvd": 4, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:P", + "V3Vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 6.8, + "V3Score": 9.8 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2020-9548", + "https://github.com/FasterXML/jackson-databind/issues/2634", + "https://github.com/advisories/GHSA-p43x-xfjf-5jhr", + "https://lists.apache.org/thread.html/r35d30db00440ef63b791c4b7f7acb036e14d4a23afa2a249cb66c0fd@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r9464a40d25c3ba1a55622db72f113eb494a889656962d098c70c5bb1@%3Cdev.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/r98c9b6e4c9e17792e2cd1ec3e4aa20b61a791939046d3f10888176bb@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rb6fecb5e96a6d61e175ff49f33f2713798dd05cf03067c169d195596@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rd5a4457be4623038c3989294429bc063eec433a2e55995d81591e2ca@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd49ab9565bec436a896bc00c4b9fc9dce1598e106c318524fbdfec6@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rdd4df698d5d8e635144d2994922bf0842e933809eae259521f3b5097@%3Cissues.zookeeper.apache.org%3E", + "https://lists.apache.org/thread.html/rf1bbc0ea4a9f014cf94df9a12a6477d24a27f52741dbc87f2fd52ff2@%3Cissues.geode.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2020/03/msg00008.html", + "https://medium.com/@cowtowncoder/on-jackson-cves-dont-panic-here-is-what-you-need-to-know-54cd0d6e8062", + "https://nvd.nist.gov/vuln/detail/CVE-2020-9548", + "https://security.netapp.com/advisory/ntap-20200904-0006/", + "https://www.oracle.com/security-alerts/cpujan2021.html", + "https://www.oracle.com/security-alerts/cpujul2020.html", + "https://www.oracle.com/security-alerts/cpuoct2020.html", + "https://www.oracle.com/security-alerts/cpuoct2021.html" + ], + "PublishedDate": "2020-03-02T04:15:00Z", + "LastModifiedDate": "2021-12-02T21:23:00Z" + }, + { + "VulnerabilityID": "CVE-2021-20190", + "PkgID": "com.fasterxml.jackson.core:jackson-databind:2.9.1", + "PkgName": "com.fasterxml.jackson.core:jackson-databind", + "PkgIdentifier": { + "PURL": "pkg:maven/com.fasterxml.jackson.core/jackson-databind@2.9.1", + "UID": "9ccd2eb3e03373ff" + }, + "InstalledVersion": "2.9.1", + "FixedVersion": "2.9.10.7", + "Status": "fixed", + "Layer": {}, + "SeveritySource": "nvd", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2021-20190", + "DataSource": { + "ID": "glad", + "Name": "GitLab Advisory Database Community", + "URL": "https://gitlab.com/gitlab-org/advisories-community" + }, + "Title": "jackson-databind: mishandles the interaction between serialization gadgets and typing, related to javax.swing", + "Description": "A flaw was found in jackson-databind before 2.9.10.7. FasterXML mishandles the interaction between serialization gadgets and typing. The highest threat from this vulnerability is to data confidentiality and integrity as well as system availability.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-502" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3, + "redhat": 3 + }, + "CVSS": { + "nvd": { + "V2Vector": "AV:N/AC:M/Au:N/C:P/I:P/A:C", + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V2Score": 8.3, + "V3Score": 8.1 + }, + "redhat": { + "V3Vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:H/I:H/A:H", + "V3Score": 8.1 + } + }, + "References": [ + "https://access.redhat.com/security/cve/CVE-2021-20190", + "https://bugzilla.redhat.com/show_bug.cgi?id=1916633", + "https://github.com/FasterXML/jackson-databind/commit/7dbf51bf78d157098074a20bd9da39bd48c18e4a", + "https://github.com/FasterXML/jackson-databind/issues/2854", + "https://github.com/advisories/GHSA-5949-rw7g-wx7w", + "https://lists.apache.org/thread.html/r380e9257bacb8551ee6fcf2c59890ae9477b2c78e553fa9ea08e9d9a@%3Ccommits.nifi.apache.org%3E", + "https://lists.debian.org/debian-lts-announce/2021/04/msg00025.html", + "https://nvd.nist.gov/vuln/detail/CVE-2021-20190", + "https://security.netapp.com/advisory/ntap-20210219-0008/" + ], + "PublishedDate": "2021-01-19T17:15:00Z", + "LastModifiedDate": "2021-07-20T23:15:00Z" + } + ] + } + ] +} From 6bb06ec49db7ca5020d43081ccc53f99ff0a6d28 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Mon, 10 Jun 2024 12:45:20 +0100 Subject: [PATCH 08/15] Correct sbt features --- docs/docs/coverage/language/java.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/coverage/language/java.md b/docs/docs/coverage/language/java.md index 3e317b7a99b8..bb90366c1772 100644 --- a/docs/docs/coverage/language/java.md +++ b/docs/docs/coverage/language/java.md @@ -17,7 +17,7 @@ The following table provides an outline of the features Trivy offers. | JAR/WAR/PAR/EAR | Trivy Java DB | Include | - | - | | pom.xml | Maven repository [^1] | Exclude | ✓ | ✓[^7] | | *gradle.lockfile | - | Exclude | ✓ | ✓ | -| *.sbt.lock | - | Exclude | ✓ | ✓ | +| *.sbt.lock | - | Exclude | - | ✓ | These may be enabled or disabled depending on the target. See [here](./index.md) for the detail. From cc283447952e47b0ff752d8cf12af5c1000df4fb Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Mon, 10 Jun 2024 19:18:12 +0100 Subject: [PATCH 09/15] Remove unused logger --- pkg/fanal/analyzer/language/java/sbt/lockfile.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 341c984cf614..308ec721837c 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -13,7 +13,6 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) @@ -28,13 +27,11 @@ const ( // sbtDependencyLockAnalyzer analyzes '*.sbt.lock' type sbtDependencyLockAnalyzer struct { - logger *log.Logger parser language.Parser } func newSbtDependencyLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { return &sbtDependencyLockAnalyzer{ - logger: log.WithPrefix("sbt"), parser: lockfile.NewParser(), }, nil } From e0d9c165f6bee8c40e81aa962df6f7eef061d563 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Tue, 11 Jun 2024 09:48:52 +0100 Subject: [PATCH 10/15] Changes from review feedback --- pkg/dependency/parser/sbt/lockfile/parse.go | 43 ++++++++++--------- .../parser/sbt/lockfile/parse_test.go | 13 ------ .../sbt/lockfile/testdata/v1_happy.sbt.lock | 14 ++++++ .../analyzer/language/java/sbt/lockfile.go | 23 ++++------ .../language/java/sbt/lockfile_test.go | 18 +++----- 5 files changed, 49 insertions(+), 62 deletions(-) diff --git a/pkg/dependency/parser/sbt/lockfile/parse.go b/pkg/dependency/parser/sbt/lockfile/parse.go index c34b8b8d78a3..040b0cfd7df2 100644 --- a/pkg/dependency/parser/sbt/lockfile/parse.go +++ b/pkg/dependency/parser/sbt/lockfile/parse.go @@ -2,6 +2,8 @@ package lockfile import ( "io" + "slices" + "sort" "github.com/liamg/jfather" "golang.org/x/xerrors" @@ -13,27 +15,19 @@ import ( // lockfile format defined at: https://stringbean.github.io/sbt-dependency-lock/file-formats/version-1.html type sbtLockfile struct { - Version int `json:"lockVersion"` - Timestamp string `json:"timestamp"` - Configurations []string `json:"configurations"` - Dependencies []sbtLockfileDependency `json:"dependencies"` + Version int `json:"lockVersion"` + Dependencies []sbtLockfileDependency `json:"dependencies"` } type sbtLockfileDependency struct { - Organization string `json:"org"` - Name string `json:"name"` - Version string `json:"version"` - Artifacts []sbtLockfileArtifact `json:"artifacts"` - Configurations []string `json:"configurations"` + Organization string `json:"org"` + Name string `json:"name"` + Version string `json:"version"` + Configurations []string `json:"configurations"` StartLine int EndLine int } -type sbtLockfileArtifact struct { - Name string `json:"name"` - Hash string `json:"hash"` -} - type Parser struct{} func NewParser() *Parser { @@ -51,17 +45,20 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, return nil, nil, xerrors.Errorf("JSON decoding failed: %w", err) } - var libraries []ftypes.Package + var libraries ftypes.Packages for _, dep := range lockfile.Dependencies { - libraries = append(libraries, ftypes.Package{ - ID: dependency.ID(ftypes.Sbt, dep.Organization+":"+dep.Name, dep.Version), - Name: dep.Organization + ":" + dep.Name, - Version: dep.Version, - Locations: []ftypes.Location{{StartLine: dep.StartLine, EndLine: dep.EndLine}}, - }) + if slices.ContainsFunc(dep.Configurations, isIncludedConfig) { + libraries = append(libraries, ftypes.Package{ + ID: dependency.ID(ftypes.Sbt, dep.Organization+":"+dep.Name, dep.Version), + Name: dep.Organization + ":" + dep.Name, + Version: dep.Version, + Locations: []ftypes.Location{{StartLine: dep.StartLine, EndLine: dep.EndLine}}, + }) + } } + sort.Sort(libraries) return libraries, nil, nil } @@ -75,3 +72,7 @@ func (t *sbtLockfileDependency) UnmarshalJSONWithMetadata(node jfather.Node) err t.EndLine = node.Range().End.Line return nil } + +func isIncludedConfig(config string) bool { + return config == "compile" || config == "runtime" +} diff --git a/pkg/dependency/parser/sbt/lockfile/parse_test.go b/pkg/dependency/parser/sbt/lockfile/parse_test.go index 81b876f91eea..a11e7b8aedb2 100644 --- a/pkg/dependency/parser/sbt/lockfile/parse_test.go +++ b/pkg/dependency/parser/sbt/lockfile/parse_test.go @@ -2,8 +2,6 @@ package lockfile import ( "os" - "sort" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -73,18 +71,7 @@ func TestParser_Parse(t *testing.T) { libs, _, err := parser.Parse(f) require.NoError(t, err) - sortLibs(libs) assert.Equal(t, tt.want, libs) }) } } - -func sortLibs(libs []ftypes.Package) { - sort.Slice(libs, func(i, j int) bool { - ret := strings.Compare(libs[i].Name, libs[j].Name) - if ret == 0 { - return libs[i].Version < libs[j].Version - } - return ret < 0 - }) -} diff --git a/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock index 26b5ef401aeb..0dcba8ba09c6 100644 --- a/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock +++ b/pkg/dependency/parser/sbt/lockfile/testdata/v1_happy.sbt.lock @@ -54,6 +54,20 @@ "runtime", "test" ] + }, + { + "org" : "org.scalatest", + "name" : "scalatest-core_2.13", + "version" : "3.2.15", + "artifacts" : [ + { + "name" : "scalatest-core_2.13.jar", + "hash" : "sha1:231d1f4049a9fa4bd65c17b806a58180b9f4abe1" + } + ], + "configurations" : [ + "test" + ] } ] } \ No newline at end of file diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 308ec721837c..afc53373aed1 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -2,12 +2,11 @@ package sbt import ( "context" + "golang.org/x/xerrors" "io" "io/fs" "os" - "strings" - - "golang.org/x/xerrors" + "path/filepath" "github.com/aquasecurity/trivy/pkg/dependency/parser/sbt/lockfile" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" @@ -21,8 +20,8 @@ func init() { } const ( - version = 1 - fileNameSuffix = "sbt.lock" + version = 1 + requiredFile = "build.sbt.lock" ) // sbtDependencyLockAnalyzer analyzes '*.sbt.lock' @@ -50,16 +49,10 @@ func (a sbtDependencyLockAnalyzer) PostAnalyze(_ context.Context, input analyzer return xerrors.Errorf("%s parse error: %w", filePath, err) } - if app == nil { - // no dependencies - add empty application - app = &types.Application{ - Type: types.Sbt, - FilePath: filePath, - Packages: types.Packages{}, - } - } + if app != nil { - apps = append(apps, *app) + apps = append(apps, *app) + } return nil }) @@ -74,7 +67,7 @@ func (a sbtDependencyLockAnalyzer) PostAnalyze(_ context.Context, input analyzer } func (a sbtDependencyLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { - return strings.HasSuffix(filePath, fileNameSuffix) + return requiredFile == filepath.Base(filePath) } func (a sbtDependencyLockAnalyzer) Type() analyzer.Type { diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go index 5730807c138d..66a370775118 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go @@ -18,19 +18,6 @@ func Test_sbtDependencyLockAnalyzer(t *testing.T) { dir string want *analyzer.AnalysisResult }{ - { - name: "empty lockfile", - dir: "testdata/empty", - want: &analyzer.AnalysisResult{ - Applications: []types.Application{ - { - Type: types.Sbt, - FilePath: "build.sbt.lock", - Packages: types.Packages{}, - }, - }, - }, - }, { name: "v1 lockfile", dir: "testdata/v1", @@ -78,6 +65,11 @@ func Test_sbtDependencyLockAnalyzer(t *testing.T) { }, }, }, + { + name: "empty lockfile", + dir: "testdata/empty", + want: &analyzer.AnalysisResult{}, + }, } for _, tt := range tests { From 11e1e6da733d4f5b8cc46f11f09c249179637ec2 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Tue, 11 Jun 2024 17:30:58 +0100 Subject: [PATCH 11/15] Fix lint issue --- pkg/fanal/analyzer/language/java/sbt/lockfile.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index afc53373aed1..75592c3738de 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -2,12 +2,13 @@ package sbt import ( "context" - "golang.org/x/xerrors" "io" "io/fs" "os" "path/filepath" + "golang.org/x/xerrors" + "github.com/aquasecurity/trivy/pkg/dependency/parser/sbt/lockfile" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" From 10cfe1bf2e10e613a48a48dcb1683d52197c8e04 Mon Sep 17 00:00:00 2001 From: Michael Stringer Date: Tue, 18 Jun 2024 09:31:37 +0100 Subject: [PATCH 12/15] Switch from PostAnalyzer to Analyzer --- .../analyzer/language/java/sbt/lockfile.go | 45 ++++--------------- .../language/java/sbt/lockfile_test.go | 28 +++++++----- 2 files changed, 24 insertions(+), 49 deletions(-) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 75592c3738de..26efcb1034a0 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -2,8 +2,6 @@ package sbt import ( "context" - "io" - "io/fs" "os" "path/filepath" @@ -13,11 +11,10 @@ import ( "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) func init() { - analyzer.RegisterPostAnalyzer(analyzer.TypeSbtLock, newSbtDependencyLockAnalyzer) + analyzer.RegisterAnalyzer(&sbtDependencyLockAnalyzer{}) } const ( @@ -26,45 +23,19 @@ const ( ) // sbtDependencyLockAnalyzer analyzes '*.sbt.lock' -type sbtDependencyLockAnalyzer struct { - parser language.Parser -} - -func newSbtDependencyLockAnalyzer(_ analyzer.AnalyzerOptions) (analyzer.PostAnalyzer, error) { - return &sbtDependencyLockAnalyzer{ - parser: lockfile.NewParser(), - }, nil -} - -func (a sbtDependencyLockAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { - required := func(path string, d fs.DirEntry) bool { - return a.Required(path, nil) - } - - var apps []types.Application - var err error - err = fsutils.WalkDir(input.FS, ".", required, func(filePath string, _ fs.DirEntry, r io.Reader) error { - var app *types.Application - app, err = language.Parse(types.Sbt, filePath, r, a.parser) - if err != nil { - return xerrors.Errorf("%s parse error: %w", filePath, err) - } - - if app != nil { +type sbtDependencyLockAnalyzer struct{} - apps = append(apps, *app) - } +func (a sbtDependencyLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { + filePath := filepath.Join(input.Dir, input.FilePath) + parser := lockfile.NewParser() - return nil - }) + res, err := language.Analyze(types.Sbt, filePath, input.Content, parser) if err != nil { - return nil, xerrors.Errorf("sbt walk error: %w", err) + return nil, xerrors.Errorf("%s parse error: %w", filePath, err) } - return &analyzer.AnalysisResult{ - Applications: apps, - }, nil + return res, nil } func (a sbtDependencyLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go index 66a370775118..0469d9156a19 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile_test.go @@ -14,18 +14,18 @@ import ( func Test_sbtDependencyLockAnalyzer(t *testing.T) { tests := []struct { - name string - dir string - want *analyzer.AnalysisResult + name string + inputFile string + want *analyzer.AnalysisResult }{ { - name: "v1 lockfile", - dir: "testdata/v1", + name: "v1 lockfile", + inputFile: "testdata/v1/build.sbt.lock", want: &analyzer.AnalysisResult{ Applications: []types.Application{ { Type: types.Sbt, - FilePath: "build.sbt.lock", + FilePath: "testdata/v1/build.sbt.lock", Packages: types.Packages{ { ID: "org.apache.commons:commons-lang3:3.9", @@ -66,19 +66,23 @@ func Test_sbtDependencyLockAnalyzer(t *testing.T) { }, }, { - name: "empty lockfile", - dir: "testdata/empty", - want: &analyzer.AnalysisResult{}, + name: "empty lockfile", + inputFile: "testdata/empty/build.sbt.lock", + want: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - a, err := newSbtDependencyLockAnalyzer(analyzer.AnalyzerOptions{}) + f, err := os.Open(tt.inputFile) require.NoError(t, err) - got, err := a.PostAnalyze(context.Background(), analyzer.PostAnalysisInput{ - FS: os.DirFS(tt.dir), + a := sbtDependencyLockAnalyzer{} + ctx := context.Background() + + got, err := a.Analyze(ctx, analyzer.AnalysisInput{ + FilePath: tt.inputFile, + Content: f, }) require.NoError(t, err) From 87fd73ba2c251106b67dfb854c2f239e74d6ed1c Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 19 Jun 2024 11:11:09 +0400 Subject: [PATCH 13/15] refactor: use const Signed-off-by: knqyf263 --- pkg/fanal/analyzer/language/java/sbt/lockfile.go | 7 ++----- pkg/fanal/types/const.go | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 26efcb1034a0..136f785b7f10 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -17,10 +17,7 @@ func init() { analyzer.RegisterAnalyzer(&sbtDependencyLockAnalyzer{}) } -const ( - version = 1 - requiredFile = "build.sbt.lock" -) +const version = 1 // sbtDependencyLockAnalyzer analyzes '*.sbt.lock' type sbtDependencyLockAnalyzer struct{} @@ -39,7 +36,7 @@ func (a sbtDependencyLockAnalyzer) Analyze(_ context.Context, input analyzer.Ana } func (a sbtDependencyLockAnalyzer) Required(filePath string, _ os.FileInfo) bool { - return requiredFile == filepath.Base(filePath) + return types.SbtLock == filepath.Base(filePath) } func (a sbtDependencyLockAnalyzer) Type() analyzer.Type { diff --git a/pkg/fanal/types/const.go b/pkg/fanal/types/const.go index 76e305ebe476..9ad8c1a2c57b 100644 --- a/pkg/fanal/types/const.go +++ b/pkg/fanal/types/const.go @@ -115,6 +115,7 @@ const ( GoSum = "go.sum" MavenPom = "pom.xml" + SbtLock = "build.sbt.lock" NpmPkg = "package.json" NpmPkgLock = "package-lock.json" From 23ce7e672b2d25fbdad9a3bfd4e2c8cb5a2c71f8 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 19 Jun 2024 11:43:30 +0400 Subject: [PATCH 14/15] fix: not join dir Signed-off-by: knqyf263 --- pkg/fanal/analyzer/language/java/sbt/lockfile.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/fanal/analyzer/language/java/sbt/lockfile.go b/pkg/fanal/analyzer/language/java/sbt/lockfile.go index 136f785b7f10..4d1d17cb5d34 100644 --- a/pkg/fanal/analyzer/language/java/sbt/lockfile.go +++ b/pkg/fanal/analyzer/language/java/sbt/lockfile.go @@ -23,13 +23,12 @@ const version = 1 type sbtDependencyLockAnalyzer struct{} func (a sbtDependencyLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { - filePath := filepath.Join(input.Dir, input.FilePath) parser := lockfile.NewParser() - res, err := language.Analyze(types.Sbt, filePath, input.Content, parser) + res, err := language.Analyze(types.Sbt, input.FilePath, input.Content, parser) if err != nil { - return nil, xerrors.Errorf("%s parse error: %w", filePath, err) + return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err) } return res, nil From 3b43605b80c2a442e9a2bcf9f39f22cad2f3db19 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 19 Jun 2024 11:46:14 +0400 Subject: [PATCH 15/15] refactor: define a variable for name Signed-off-by: knqyf263 --- pkg/dependency/parser/sbt/lockfile/parse.go | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/pkg/dependency/parser/sbt/lockfile/parse.go b/pkg/dependency/parser/sbt/lockfile/parse.go index 040b0cfd7df2..3b5b1865903d 100644 --- a/pkg/dependency/parser/sbt/lockfile/parse.go +++ b/pkg/dependency/parser/sbt/lockfile/parse.go @@ -49,11 +49,17 @@ func (Parser) Parse(r xio.ReadSeekerAt) ([]ftypes.Package, []ftypes.Dependency, for _, dep := range lockfile.Dependencies { if slices.ContainsFunc(dep.Configurations, isIncludedConfig) { + name := dep.Organization + ":" + dep.Name libraries = append(libraries, ftypes.Package{ - ID: dependency.ID(ftypes.Sbt, dep.Organization+":"+dep.Name, dep.Version), - Name: dep.Organization + ":" + dep.Name, - Version: dep.Version, - Locations: []ftypes.Location{{StartLine: dep.StartLine, EndLine: dep.EndLine}}, + ID: dependency.ID(ftypes.Sbt, name, dep.Version), + Name: name, + Version: dep.Version, + Locations: []ftypes.Location{ + { + StartLine: dep.StartLine, + EndLine: dep.EndLine, + }, + }, }) } }