mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 15:16:33 -08:00
feat(repo): add support for branch, commit, & tag (#2494)
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
committed by
GitHub
parent
783e7cfe0c
commit
30c9f90bf8
@@ -66,6 +66,11 @@ Client/Server Flags
|
||||
--token string for authentication in client/server mode
|
||||
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
|
||||
|
||||
Repository Flags
|
||||
--branch string pass the branch name to be scanned
|
||||
--commit string pass the commit hash to be scanned
|
||||
--tag string pass the tag name to be scanned
|
||||
|
||||
Global Flags:
|
||||
--cache-dir string cache directory (default "/Users/teppei/Library/Caches/trivy")
|
||||
-c, --config string config path (default "trivy.yaml")
|
||||
|
||||
@@ -240,6 +240,24 @@ kubernetes:
|
||||
namespace:
|
||||
```
|
||||
|
||||
## Repository Options
|
||||
Available with git repository scanning (`trivy repo`)
|
||||
|
||||
```
|
||||
repository:
|
||||
# Same as '--branch'
|
||||
# Default is empty
|
||||
branch:
|
||||
|
||||
# Same as '--commit'
|
||||
# Default is empty
|
||||
commit:
|
||||
|
||||
# Same as '--tag'
|
||||
# Default is empty
|
||||
tag:
|
||||
```
|
||||
|
||||
## Client/Server Options
|
||||
Available in client/server mode
|
||||
|
||||
|
||||
@@ -147,6 +147,30 @@ Total: 20 (UNKNOWN: 3, LOW: 0, MEDIUM: 7, HIGH: 5, CRITICAL: 5)
|
||||
|
||||
</details>
|
||||
|
||||
## Scanning a Branch
|
||||
|
||||
Pass a `--branch` agrument with a valid branch name on the remote repository provided:
|
||||
|
||||
```
|
||||
$ trivy repo --branch <branch-name> <repo-name>
|
||||
```
|
||||
|
||||
## Scanning upto a Commit
|
||||
|
||||
Pass a `--commit` agrument with a valid commit hash on the remote repository provided:
|
||||
|
||||
```
|
||||
$ trivy repo --commit <commit-hash> <repo-name>
|
||||
```
|
||||
|
||||
## Scanning a Tag
|
||||
|
||||
Pass a `--tag` agrument with a valid tag on the remote repository provided:
|
||||
|
||||
```
|
||||
$ trivy repo --tag <tag-name> <repo-name>
|
||||
```
|
||||
|
||||
## Scanning Private Repositories
|
||||
|
||||
In order to scan private GitHub or GitLab repositories, the environment variable `GITHUB_TOKEN` or `GITLAB_TOKEN` must be set, respectively, with a valid token that has access to the private repository being scanned.
|
||||
|
||||
@@ -400,6 +400,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
|
||||
ScanFlagGroup: flag.NewScanFlagGroup(),
|
||||
SecretFlagGroup: flag.NewSecretFlagGroup(),
|
||||
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
|
||||
RepoFlagGroup: flag.NewRepoFlagGroup(),
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
|
||||
@@ -497,6 +497,9 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
|
||||
InsecureSkipTLS: opts.Insecure,
|
||||
Offline: opts.OfflineScan,
|
||||
NoProgress: opts.NoProgress || opts.Quiet,
|
||||
RepoBranch: opts.RepoBranch,
|
||||
RepoCommit: opts.RepoCommit,
|
||||
RepoTag: opts.RepoTag,
|
||||
|
||||
// For misconfiguration scanning
|
||||
MisconfScannerOption: configScannerOptions,
|
||||
|
||||
@@ -20,6 +20,9 @@ type Option struct {
|
||||
Offline bool
|
||||
InsecureSkipTLS bool
|
||||
AppDirs []string
|
||||
RepoBranch string
|
||||
RepoCommit string
|
||||
RepoTag string
|
||||
|
||||
MisconfScannerOption misconf.ScannerOption
|
||||
SecretScannerOption secret.ScannerOption
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"os"
|
||||
|
||||
git "github.com/go-git/go-git/v5"
|
||||
"github.com/go-git/go-git/v5/plumbing"
|
||||
"github.com/go-git/go-git/v5/plumbing/transport/http"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
@@ -39,7 +40,6 @@ func NewArtifact(rawurl string, c cache.ArtifactCache, artifactOpt artifact.Opti
|
||||
URL: u.String(),
|
||||
Auth: gitAuth(),
|
||||
Progress: os.Stdout,
|
||||
Depth: 1,
|
||||
InsecureSkipTLS: artifactOpt.InsecureSkipTLS,
|
||||
}
|
||||
|
||||
@@ -48,9 +48,37 @@ func NewArtifact(rawurl string, c cache.ArtifactCache, artifactOpt artifact.Opti
|
||||
cloneOptions.Progress = nil
|
||||
}
|
||||
|
||||
_, err = git.PlainClone(tmpDir, false, &cloneOptions)
|
||||
if artifactOpt.RepoCommit == "" {
|
||||
cloneOptions.Depth = 1
|
||||
}
|
||||
|
||||
if artifactOpt.RepoBranch != "" {
|
||||
cloneOptions.ReferenceName = plumbing.NewBranchReferenceName(artifactOpt.RepoBranch)
|
||||
cloneOptions.SingleBranch = true
|
||||
}
|
||||
|
||||
if artifactOpt.RepoTag != "" {
|
||||
cloneOptions.ReferenceName = plumbing.NewTagReferenceName(artifactOpt.RepoTag)
|
||||
cloneOptions.SingleBranch = true
|
||||
}
|
||||
|
||||
r, err := git.PlainClone(tmpDir, false, &cloneOptions)
|
||||
if err != nil {
|
||||
return nil, cleanup, xerrors.Errorf("git error: %w", err)
|
||||
return nil, cleanup, xerrors.Errorf("git clone error: %w", err)
|
||||
}
|
||||
|
||||
if artifactOpt.RepoCommit != "" {
|
||||
w, err := r.Worktree()
|
||||
if err != nil {
|
||||
return nil, cleanup, xerrors.Errorf("git worktree error: %w", err)
|
||||
}
|
||||
|
||||
err = w.Checkout(&git.CheckoutOptions{
|
||||
Hash: plumbing.NewHash(artifactOpt.RepoCommit),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, cleanup, xerrors.Errorf("git checkout error: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
cleanup = func() {
|
||||
|
||||
@@ -38,11 +38,14 @@ func TestNewArtifact(t *testing.T) {
|
||||
rawurl string
|
||||
c cache.ArtifactCache
|
||||
noProgress bool
|
||||
repoBranch string
|
||||
repoTag string
|
||||
repoCommit string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
assertion assert.ErrorAssertionFunc
|
||||
}{
|
||||
{
|
||||
name: "happy path",
|
||||
@@ -51,6 +54,7 @@ func TestNewArtifact(t *testing.T) {
|
||||
c: nil,
|
||||
noProgress: false,
|
||||
},
|
||||
assertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "happy noProgress",
|
||||
@@ -59,6 +63,34 @@ func TestNewArtifact(t *testing.T) {
|
||||
c: nil,
|
||||
noProgress: true,
|
||||
},
|
||||
assertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "branch",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoBranch: "valid-branch",
|
||||
},
|
||||
assertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "tag",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoTag: "v1.0.0",
|
||||
},
|
||||
assertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "commit",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778d",
|
||||
},
|
||||
assertion: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "sad path",
|
||||
@@ -67,7 +99,9 @@ func TestNewArtifact(t *testing.T) {
|
||||
c: nil,
|
||||
noProgress: false,
|
||||
},
|
||||
wantErr: true,
|
||||
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, "repository not found")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid url",
|
||||
@@ -76,14 +110,54 @@ func TestNewArtifact(t *testing.T) {
|
||||
c: nil,
|
||||
noProgress: false,
|
||||
},
|
||||
wantErr: true,
|
||||
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, "url parse error")
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid branch",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoBranch: "invalid-branch",
|
||||
},
|
||||
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, `couldn't find remote ref "refs/heads/invalid-branch"`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid tag",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoTag: "v1.0.9",
|
||||
},
|
||||
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, `couldn't find remote ref "refs/tags/v1.0.9"`)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid commit",
|
||||
args: args{
|
||||
rawurl: ts.URL + "/test.git",
|
||||
c: nil,
|
||||
repoCommit: "6ac152fe2b87cb5e243414df71790a32912e778e",
|
||||
},
|
||||
assertion: func(t assert.TestingT, err error, args ...interface{}) bool {
|
||||
return assert.ErrorContains(t, err, "git checkout error: object not found")
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, cleanup, err := NewArtifact(tt.args.rawurl, tt.args.c, artifact.Option{NoProgress: tt.args.noProgress})
|
||||
assert.Equal(t, tt.wantErr, err != nil)
|
||||
_, cleanup, err := NewArtifact(tt.args.rawurl, tt.args.c, artifact.Option{
|
||||
NoProgress: tt.args.noProgress,
|
||||
RepoBranch: tt.args.repoBranch,
|
||||
RepoTag: tt.args.repoTag,
|
||||
RepoCommit: tt.args.repoCommit,
|
||||
})
|
||||
tt.assertion(t, err)
|
||||
defer cleanup()
|
||||
})
|
||||
}
|
||||
|
||||
1
pkg/fanal/artifact/remote/testdata/test
vendored
Submodule
1
pkg/fanal/artifact/remote/testdata/test
vendored
Submodule
Submodule pkg/fanal/artifact/remote/testdata/test added at 0d8bf3f07c
@@ -0,0 +1,2 @@
|
||||
x<01><>Aj<41>0s<>+<2B><08><><EFBFBD>X˲<58> <09><>`,<2C>"<22><>^<5E>c<EFBFBD><63>G<EFBFBD><47> Ї<><D087><EFBFBD><EFBFBD>{<7B>M<EFBFBD>G<EFBFBD><47><EFBFBD>xb<78><1C>$<24>nZӄ3<07>d<EFBFBD><64><EFBFBD>V<EFBFBD><56><12>ul|<7C>]!rv<72><17><>Ly <09>'<27>W<>Q<EFBFBD><51>>9/D<>j<EFBFBD>Ժ<1F>Yϥr<CFA5><72><EFBFBD>h1\<5C><><EFBFBD>~<7E>un<75>[<5B><>
|
||||
\4[<5B><11><><06>f<EFBFBD><66><EFBFBD><EFBFBD><1C><08><05><>'<27><>}<7D>:<3A>*?
|
||||
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/1c/1d7deed649fbecd66fab423ccd9d001bf9ff91
vendored
Normal file
Binary file not shown.
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/27/aaec53f92314d9438a53c703f169d2cbf5001a
vendored
Normal file
Binary file not shown.
@@ -0,0 +1,3 @@
|
||||
x<01><>K<EFBFBD><4B>0Dg<44>S<EFBFBD>><10><>B<>r<><72><EFBFBD>5Xqp<71>$<24>mr<6D>@-<2D><><EFBFBD>Qym<79>
|
||||
<18>~dc6.<2E>@<40><>&*<2A>&˔<>O<EFBFBD><<3C><>l
|
||||
<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><1B><05>8<EFBFBD><38><1D>.S<12>c<>:<3A><><EFBFBD>%<1D><><EFBFBD>9*<2A>e^7<><37><EFBFBD>4c<34><63>e<EFBFBD>/\<10><>ǧ<EFBFBD><EFBFBD>u9<75><39>]@<1F>Х#o<07>i*<2A><>CF9<46><39><EFBFBD> =<08>o<EFBFBD>RV<><56><EFBFBD>R<EFBFBD>
|
||||
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/c0/42cd14d2b999cade090785af47e9f8b8e342ff
vendored
Normal file
Binary file not shown.
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/d7/937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b
vendored
Normal file
Binary file not shown.
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/e2/4866d1d31ddffdb27fbcf583d5deb4386d5145
vendored
Normal file
Binary file not shown.
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d
vendored
Normal file
BIN
pkg/fanal/artifact/remote/testdata/test.git/objects/f4/836be6497e83e13dc0cfbce7e6b973b1ea511d
vendored
Normal file
Binary file not shown.
@@ -1 +1 @@
|
||||
c906fc4a94762f8a2c77c718947143d16e4e9ec7
|
||||
0d8bf3f07c3970b3e38c2cfb1c619cb86fae76d2
|
||||
|
||||
1
pkg/fanal/artifact/remote/testdata/test.git/refs/heads/valid-branch
vendored
Normal file
1
pkg/fanal/artifact/remote/testdata/test.git/refs/heads/valid-branch
vendored
Normal file
@@ -0,0 +1 @@
|
||||
d7937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b
|
||||
1
pkg/fanal/artifact/remote/testdata/test.git/refs/tags/v1.0.0
vendored
Normal file
1
pkg/fanal/artifact/remote/testdata/test.git/refs/tags/v1.0.0
vendored
Normal file
@@ -0,0 +1 @@
|
||||
c906fc4a94762f8a2c77c718947143d16e4e9ec7
|
||||
@@ -51,6 +51,7 @@ type Flags struct {
|
||||
LicenseFlagGroup *LicenseFlagGroup
|
||||
MisconfFlagGroup *MisconfFlagGroup
|
||||
RemoteFlagGroup *RemoteFlagGroup
|
||||
RepoFlagGroup *RepoFlagGroup
|
||||
ReportFlagGroup *ReportFlagGroup
|
||||
SBOMFlagGroup *SBOMFlagGroup
|
||||
ScanFlagGroup *ScanFlagGroup
|
||||
@@ -68,6 +69,7 @@ type Options struct {
|
||||
LicenseOptions
|
||||
MisconfOptions
|
||||
RemoteOptions
|
||||
RepoOptions
|
||||
ReportOptions
|
||||
SBOMOptions
|
||||
ScanOptions
|
||||
@@ -209,6 +211,9 @@ func (f *Flags) groups() []FlagGroup {
|
||||
if f.RemoteFlagGroup != nil {
|
||||
groups = append(groups, f.RemoteFlagGroup)
|
||||
}
|
||||
if f.RepoFlagGroup != nil {
|
||||
groups = append(groups, f.RepoFlagGroup)
|
||||
}
|
||||
return groups
|
||||
}
|
||||
|
||||
@@ -302,6 +307,10 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
|
||||
opts.RemoteOptions = f.RemoteFlagGroup.ToOptions()
|
||||
}
|
||||
|
||||
if f.RepoFlagGroup != nil {
|
||||
opts.RepoOptions = f.RepoFlagGroup.ToOptions()
|
||||
}
|
||||
|
||||
if f.ReportFlagGroup != nil {
|
||||
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output)
|
||||
if err != nil {
|
||||
|
||||
58
pkg/flag/repo.go
Normal file
58
pkg/flag/repo.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package flag
|
||||
|
||||
var (
|
||||
FetchBranchFlag = Flag{
|
||||
Name: "branch",
|
||||
ConfigName: "repository.branch",
|
||||
Value: "",
|
||||
Usage: "pass the branch name to be scanned",
|
||||
}
|
||||
FetchCommitFlag = Flag{
|
||||
Name: "commit",
|
||||
ConfigName: "repository.commit",
|
||||
Value: "",
|
||||
Usage: "pass the commit hash to be scanned",
|
||||
}
|
||||
FetchTagFlag = Flag{
|
||||
Name: "tag",
|
||||
ConfigName: "repository.tag",
|
||||
Value: "",
|
||||
Usage: "pass the tag name to be scanned",
|
||||
}
|
||||
)
|
||||
|
||||
type RepoFlagGroup struct {
|
||||
Branch *Flag
|
||||
Commit *Flag
|
||||
Tag *Flag
|
||||
}
|
||||
|
||||
type RepoOptions struct {
|
||||
RepoBranch string
|
||||
RepoCommit string
|
||||
RepoTag string
|
||||
}
|
||||
|
||||
func NewRepoFlagGroup() *RepoFlagGroup {
|
||||
return &RepoFlagGroup{
|
||||
Branch: &FetchBranchFlag,
|
||||
Commit: &FetchCommitFlag,
|
||||
Tag: &FetchTagFlag,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *RepoFlagGroup) Name() string {
|
||||
return "Repository"
|
||||
}
|
||||
|
||||
func (f *RepoFlagGroup) Flags() []*Flag {
|
||||
return []*Flag{f.Branch, f.Commit, f.Tag}
|
||||
}
|
||||
|
||||
func (f *RepoFlagGroup) ToOptions() RepoOptions {
|
||||
return RepoOptions{
|
||||
RepoBranch: getString(f.Branch),
|
||||
RepoCommit: getString(f.Commit),
|
||||
RepoTag: getString(f.Tag),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user