Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for gradle.lockfile #2759

Merged
merged 10 commits into from Sep 1, 2022
45 changes: 23 additions & 22 deletions docs/docs/vulnerability/detection/language.md
Expand Up @@ -2,28 +2,29 @@

`Trivy` automatically detects the following files in the container and scans vulnerabilities in the application dependencies.

| Language | File | Image[^8] | Rootfs[^9] | Filesystem[^10] | Repository[^11] | Dev dependencies |
| -------- |-------------------------| :-------: | :--------: | :-------------: | :-------------: | ---------------- |
| Ruby | Gemfile.lock | - | - | ✅ | ✅ | included |
| | gemspec | ✅ | ✅ | - | - | included |
| Python | Pipfile.lock | - | - | ✅ | ✅ | excluded |
| | poetry.lock | - | - | ✅ | ✅ | included |
| | requirements.txt | - | - | ✅ | ✅ | included |
| | egg package[^1] | ✅ | ✅ | - | - | excluded |
| | wheel package[^2] | ✅ | ✅ | - | - | excluded |
| PHP | composer.lock | ✅ | ✅ | ✅ | ✅ | excluded |
| Node.js | package-lock.json | - | - | ✅ | ✅ | excluded |
| | yarn.lock | - | - | ✅ | ✅ | included |
| | pnpm-lock.yaml | - | - | ✅ | ✅ | excluded |
| | package.json | ✅ | ✅ | - | - | excluded |
| .NET | packages.lock.json | ✅ | ✅ | ✅ | ✅ | included |
| | packages.config | ✅ | ✅ | ✅ | ✅ | excluded |
| | .deps.json | ✅ | ✅ | ✅ | ✅ | excluded |
| Java | JAR/WAR/PAR/EAR[^3][^4] | ✅ | ✅ | - | - | included |
| | pom.xml[^5] | - | - | ✅ | ✅ | excluded |
| Go | Binaries built by Go[^6] | ✅ | ✅ | - | - | excluded |
| | go.mod[^7] | - | - | ✅ | ✅ | included |
| Rust | Cargo.lock | ✅ | ✅ | ✅ | ✅ | included |
| Language | File | Image[^8] | Rootfs[^9] | Filesystem[^10] | Repository[^11] | Dev dependencies |
| -------- |--------------------------------------------------------------------------------------------| :-------: | :--------: | :-------------: | :-------------: | ---------------- |
| Ruby | Gemfile.lock | - | - | ✅ | ✅ | included |
| | gemspec | ✅ | ✅ | - | - | included |
| Python | Pipfile.lock | - | - | ✅ | ✅ | excluded |
| | poetry.lock | - | - | ✅ | ✅ | included |
| | requirements.txt | - | - | ✅ | ✅ | included |
| | egg package[^1] | ✅ | ✅ | - | - | excluded |
| | wheel package[^2] | ✅ | ✅ | - | - | excluded |
| PHP | composer.lock | ✅ | ✅ | ✅ | ✅ | excluded |
| Node.js | package-lock.json | - | - | ✅ | ✅ | excluded |
| | yarn.lock | - | - | ✅ | ✅ | included |
| | pnpm-lock.yaml | - | - | ✅ | ✅ | excluded |
| | package.json | ✅ | ✅ | - | - | excluded |
| .NET | packages.lock.json | ✅ | ✅ | ✅ | ✅ | included |
| | packages.config | ✅ | ✅ | ✅ | ✅ | excluded |
| | .deps.json | ✅ | ✅ | ✅ | ✅ | excluded |
| Java | JAR/WAR/PAR/EAR[^3][^4] | ✅ | ✅ | - | - | included |
| | pom.xml[^5] | - | - | ✅ | ✅ | excluded |
| | *gradle.lockfile | - | - | ✅ | ✅ | excluded |
| Go | Binaries built by Go[^6] | ✅ | ✅ | - | - | excluded |
| | go.mod[^7] | - | - | ✅ | ✅ | included |
| Rust | Cargo.lock | ✅ | ✅ | ✅ | ✅ | included |
| | Binaries built with [cargo-auditable](https://github.com/rust-secure-code/cargo-auditable) | ✅ | ✅ | - | - | excluded

The path of these files does not matter.
Expand Down
4 changes: 3 additions & 1 deletion go.mod
Expand Up @@ -255,7 +255,7 @@ require (
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mattn/go-runewidth v0.0.13 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/microsoft/go-rustaudit v0.0.0-20220805122630-097fff025e34 // indirect
github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-testing-interface v1.0.0 // indirect
Expand Down Expand Up @@ -379,3 +379,5 @@ replace github.com/docker/docker => github.com/docker/docker v20.10.3-0.20220224
// v1.2.0 is taken from github.com/open-policy-agent/opa v0.42.0
// v1.2.0 incompatible with github.com/docker/docker v20.10.3-0.20220224222438-c78f6963a1c0+incompatible
replace oras.land/oras-go => oras.land/oras-go v1.1.1

replace github.com/aquasecurity/go-dep-parser => github.com/aquasecurity/go-dep-parser v0.0.0-20220819122527-ea25d7972573
8 changes: 4 additions & 4 deletions go.sum
Expand Up @@ -206,8 +206,8 @@ github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986 h1:2a30
github.com/aquasecurity/bolt-fixtures v0.0.0-20200903104109-d34e7f983986/go.mod h1:NT+jyeCzXk6vXR5MTkdn4z64TgGfE5HMLC8qfj5unl8=
github.com/aquasecurity/defsec v0.71.9 h1:eo244v1RQzziClY9xXyVftPibE0fddXbTtkvH52/slU=
github.com/aquasecurity/defsec v0.71.9/go.mod h1:2jYgkIi3UFbkrbtpnr3Cu49JZ3MGuLMJAhyh63jV1I4=
github.com/aquasecurity/go-dep-parser v0.0.0-20220815163410-fcf26eb92b86 h1:sc8hDjSxO3aiG0R7HvaAVnY6329NTtv9AqDGpVQxAPQ=
github.com/aquasecurity/go-dep-parser v0.0.0-20220815163410-fcf26eb92b86/go.mod h1:wwxn1SyOEY8W5hy8aDQDoExX+ybVsi+xfIllXz93+Fk=
github.com/aquasecurity/go-dep-parser v0.0.0-20220819122527-ea25d7972573 h1:X7HZXkH1966LxT834HvkoDv5pdW7iUeZZIV0KACWpZ0=
github.com/aquasecurity/go-dep-parser v0.0.0-20220819122527-ea25d7972573/go.mod h1:6G1Y5nht5TL9kr1SzmrdE8PrmbNXo9nHx3qFR3qURg0=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce h1:QgBRgJvtEOBtUXilDb1MLi1p1MWoyFDXAu5DEUl5nwM=
github.com/aquasecurity/go-gem-version v0.0.0-20201115065557-8eed6fe000ce/go.mod h1:HXgVzOPvXhVGLJs4ZKO817idqr/xhwsTcj17CLYY74s=
github.com/aquasecurity/go-mock-aws v0.0.0-20220726154943-99847deb62b0 h1:tihCUjLWkF0b1SAjAKcFltUs3SpsqGrLtI+Frye0D10=
Expand Down Expand Up @@ -1191,8 +1191,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/maxbrunsfeld/counterfeiter/v6 v6.2.2/go.mod h1:eD9eIE7cdwcMi9rYluz88Jz2VyhSmden33/aXg4oVIY=
github.com/microsoft/go-rustaudit v0.0.0-20220805122630-097fff025e34 h1:W/tuIksfbU5I1xVm2zxi0afcIhDvmnebpdq+tA3OPAE=
github.com/microsoft/go-rustaudit v0.0.0-20220805122630-097fff025e34/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g=
github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032 h1:TLygBUBxikNJJfLwgm+Qwdgq1FtfV8Uh7bcxRyTzK8s=
github.com/microsoft/go-rustaudit v0.0.0-20220808201409-204dfee52032/go.mod h1:vYT9HE7WCvL64iVeZylKmCsWKfE+JZ8105iuh2Trk8g=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
Expand Down
2 changes: 1 addition & 1 deletion pkg/detector/library/driver.go
Expand Up @@ -37,7 +37,7 @@ func NewDriver(libType string) (Driver, error) {
case ftypes.GoBinary, ftypes.GoModule:
ecosystem = vulnerability.Go
comparer = compare.GenericComparer{}
case ftypes.Jar, ftypes.Pom:
case ftypes.Jar, ftypes.Pom, ftypes.GradleLock:
ecosystem = vulnerability.Maven
comparer = maven.Comparer{}
case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.NodePkg, ftypes.JavaScript:
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/analyzer/all/import.go
Expand Up @@ -7,6 +7,7 @@ import (
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/binary"
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/golang/mod"
_ "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/nodejs/npm"
Expand Down
9 changes: 5 additions & 4 deletions pkg/fanal/analyzer/const.go
Expand Up @@ -47,8 +47,9 @@ const (
TypeComposer Type = "composer"

// Java
TypeJar Type = "jar"
TypePom Type = "pom"
TypeJar Type = "jar"
TypePom Type = "pom"
TypeGradleLock Type = "gradle-lockfile"

// Node.js
TypeNpmPkgLock Type = "npm"
Expand Down Expand Up @@ -113,15 +114,15 @@ var (

// TypeLanguages has all language analyzers
TypeLanguages = []Type{
TypeBundler, TypeGemSpec, TypeCargo, TypeComposer, TypeJar, TypePom,
TypeBundler, TypeGemSpec, TypeCargo, TypeComposer, TypeJar, TypePom, TypeGradleLock,
TypeNpmPkgLock, TypeNodePkg, TypeYarn, TypePnpm, TypeNuget, TypeDotNetDeps,
TypePythonPkg, TypePip, TypePipenv, TypePoetry, TypeGoBinary, TypeGoMod, TypeRustBinary,
}

// TypeLockfiles has all lock file analyzers
TypeLockfiles = []Type{
TypeBundler, TypeNpmPkgLock, TypeYarn,
TypePnpm, TypePip, TypePipenv, TypePoetry, TypeGoMod, TypePom,
TypePnpm, TypePip, TypePipenv, TypePoetry, TypeGoMod, TypePom, TypeGradleLock,
}

// TypeIndividualPkgs has all analyzers for individual packages
Expand Down
48 changes: 48 additions & 0 deletions pkg/fanal/analyzer/language/java/gradle/lockfile.go
@@ -0,0 +1,48 @@
package gradle

import (
"context"
"os"
"path/filepath"
"strings"

"github.com/aquasecurity/go-dep-parser/pkg/gradle/lockfile"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
"github.com/aquasecurity/trivy/pkg/fanal/types"

"golang.org/x/xerrors"
)

func init() {
analyzer.RegisterAnalyzer(&gradleLockAnalyzer{})
}

const (
version = 1
fileNameSuffix = "gradle.lockfile"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can see on GitHub, that user have lock files with another names. is it ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is suffix of filename.
If I understand correctly - we need to check only this suffix.

return strings.HasSuffix(filepath.Base(filePath), fileNameSuffix)

)

// gradleLockAnalyzer analyzes '*gradle.lockfile'
type gradleLockAnalyzer struct{}

func (a gradleLockAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
p := lockfile.NewParser()
res, err := language.Analyze(types.GradleLock, input.FilePath, input.Content, p)
if err != nil {
return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err)
}
return res, nil
}

func (a gradleLockAnalyzer) Required(filePath string, _ os.FileInfo) bool {
return strings.HasSuffix(filepath.Base(filePath), fileNameSuffix)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

os.FileInfo contains a base file name, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. But all language analyzers use filepath (expect go and rust binaries). That is why i also used filepath.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i meant that we don't neet to call filepath.Base(filePath), because os.FileInfo already has a file name, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. Looks like an extra step.
Used os.FileInfo

}

func (a gradleLockAnalyzer) Type() analyzer.Type {
return analyzer.TypeGradleLock
}

func (a gradleLockAnalyzer) Version() int {
return version
}
90 changes: 90 additions & 0 deletions pkg/fanal/analyzer/language/java/gradle/lockfile_test.go
@@ -0,0 +1,90 @@
package gradle

import (
"os"
"testing"

"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
"github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func Test_gradleLockAnalyzer_Analyze(t *testing.T) {
tests := []struct {
name string
inputFile string
want *analyzer.AnalysisResult
}{
{
name: "happy path",
inputFile: "testdata/happy.lockfile",
want: &analyzer.AnalysisResult{
Applications: []types.Application{
{
Type: types.GradleLock,
FilePath: "testdata/happy.lockfile",
Libraries: []types.Package{
{
Name: "com.example:example",
Version: "0.0.1",
},
},
},
},
},
},
{
name: "empty file",
inputFile: "testdata/empty.lockfile",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
f, err := os.Open(tt.inputFile)
require.NoError(t, err)
defer f.Close()

a := gradleLockAnalyzer{}
got, err := a.Analyze(nil, analyzer.AnalysisInput{
FilePath: tt.inputFile,
Content: f,
})

assert.NoError(t, err)
assert.Equal(t, tt.want, got)
})
}
}

func Test_nugetLibraryAnalyzer_Required(t *testing.T) {
tests := []struct {
name string
filePath string
want bool
}{
{
name: "default name",
filePath: "test/gradle.lockfile",
want: true,
},
{
name: "name with prefix",
filePath: "test/settings-gradle.lockfile",
want: true,
},
{
name: "zip",
filePath: "test.zip",
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
a := gradleLockAnalyzer{}
got := a.Required(tt.filePath, nil)
assert.Equal(t, tt.want, got)
})
}
}
@@ -0,0 +1,4 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
empty=
@@ -0,0 +1,5 @@
# This is a Gradle generated file for dependency locking.
# Manual edits can break the build and are not advised.
# This file is expected to be part of source control.
com.example:example:0.0.1=classpath
empty=
2 changes: 1 addition & 1 deletion pkg/fanal/analyzer/language/rust/binary/binary_test.go
Expand Up @@ -28,7 +28,7 @@ func Test_rustBinaryLibraryAnalyzer_Analyze(t *testing.T) {
FilePath: "testdata/executable_rust",
Libraries: []types.Package{
{Name: "crate_with_features", Version: "0.1.0"},
{Name: "library_crate", Version: "0.1.0"},
{Name: "library_crate", Version: "0.1.0", Indirect: true},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did it do in this PR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed

},
},
},
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/types/const.go
Expand Up @@ -23,6 +23,7 @@ const (
Pnpm = "pnpm"
Jar = "jar"
Pom = "pom"
GradleLock = "gradle-lock"
Copy link
Collaborator

@knqyf263 knqyf263 Aug 30, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think Gradle is enough. Other languages don't mention if it is a lock file.

Suggested change
GradleLock = "gradle-lock"
Gradle = "gradle"

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

GoBinary = "gobinary"
GoModule = "gomod"
JavaScript = "javascript"
Expand Down