feat(repo): add support for branch, commit, & tag (#2494)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Shubham Palriwala
2022-07-17 16:24:28 +05:30
committed by GitHub
parent 783e7cfe0c
commit 30c9f90bf8
22 changed files with 242 additions and 11 deletions

View File

@@ -66,6 +66,11 @@ Client/Server Flags
--token string for authentication in client/server mode --token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token") --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: Global Flags:
--cache-dir string cache directory (default "/Users/teppei/Library/Caches/trivy") --cache-dir string cache directory (default "/Users/teppei/Library/Caches/trivy")
-c, --config string config path (default "trivy.yaml") -c, --config string config path (default "trivy.yaml")

View File

@@ -240,6 +240,24 @@ kubernetes:
namespace: 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 ## Client/Server Options
Available in client/server mode Available in client/server mode

View File

@@ -147,6 +147,30 @@ Total: 20 (UNKNOWN: 3, LOW: 0, MEDIUM: 7, HIGH: 5, CRITICAL: 5)
</details> </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 ## 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. 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.

View File

@@ -400,6 +400,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
ScanFlagGroup: flag.NewScanFlagGroup(), ScanFlagGroup: flag.NewScanFlagGroup(),
SecretFlagGroup: flag.NewSecretFlagGroup(), SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
RepoFlagGroup: flag.NewRepoFlagGroup(),
} }
cmd := &cobra.Command{ cmd := &cobra.Command{

View File

@@ -497,6 +497,9 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
InsecureSkipTLS: opts.Insecure, InsecureSkipTLS: opts.Insecure,
Offline: opts.OfflineScan, Offline: opts.OfflineScan,
NoProgress: opts.NoProgress || opts.Quiet, NoProgress: opts.NoProgress || opts.Quiet,
RepoBranch: opts.RepoBranch,
RepoCommit: opts.RepoCommit,
RepoTag: opts.RepoTag,
// For misconfiguration scanning // For misconfiguration scanning
MisconfScannerOption: configScannerOptions, MisconfScannerOption: configScannerOptions,

View File

@@ -20,6 +20,9 @@ type Option struct {
Offline bool Offline bool
InsecureSkipTLS bool InsecureSkipTLS bool
AppDirs []string AppDirs []string
RepoBranch string
RepoCommit string
RepoTag string
MisconfScannerOption misconf.ScannerOption MisconfScannerOption misconf.ScannerOption
SecretScannerOption secret.ScannerOption SecretScannerOption secret.ScannerOption

View File

@@ -7,6 +7,7 @@ import (
"os" "os"
git "github.com/go-git/go-git/v5" 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" "github.com/go-git/go-git/v5/plumbing/transport/http"
"golang.org/x/xerrors" "golang.org/x/xerrors"
@@ -39,7 +40,6 @@ func NewArtifact(rawurl string, c cache.ArtifactCache, artifactOpt artifact.Opti
URL: u.String(), URL: u.String(),
Auth: gitAuth(), Auth: gitAuth(),
Progress: os.Stdout, Progress: os.Stdout,
Depth: 1,
InsecureSkipTLS: artifactOpt.InsecureSkipTLS, InsecureSkipTLS: artifactOpt.InsecureSkipTLS,
} }
@@ -48,9 +48,37 @@ func NewArtifact(rawurl string, c cache.ArtifactCache, artifactOpt artifact.Opti
cloneOptions.Progress = nil 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 { 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() { cleanup = func() {

View File

@@ -38,11 +38,14 @@ func TestNewArtifact(t *testing.T) {
rawurl string rawurl string
c cache.ArtifactCache c cache.ArtifactCache
noProgress bool noProgress bool
repoBranch string
repoTag string
repoCommit string
} }
tests := []struct { tests := []struct {
name string name string
args args args args
wantErr bool assertion assert.ErrorAssertionFunc
}{ }{
{ {
name: "happy path", name: "happy path",
@@ -51,6 +54,7 @@ func TestNewArtifact(t *testing.T) {
c: nil, c: nil,
noProgress: false, noProgress: false,
}, },
assertion: assert.NoError,
}, },
{ {
name: "happy noProgress", name: "happy noProgress",
@@ -59,6 +63,34 @@ func TestNewArtifact(t *testing.T) {
c: nil, c: nil,
noProgress: true, 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", name: "sad path",
@@ -67,7 +99,9 @@ func TestNewArtifact(t *testing.T) {
c: nil, c: nil,
noProgress: false, 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", name: "invalid url",
@@ -76,14 +110,54 @@ func TestNewArtifact(t *testing.T) {
c: nil, c: nil,
noProgress: false, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
_, cleanup, err := NewArtifact(tt.args.rawurl, tt.args.c, artifact.Option{NoProgress: tt.args.noProgress}) _, cleanup, err := NewArtifact(tt.args.rawurl, tt.args.c, artifact.Option{
assert.Equal(t, tt.wantErr, err != nil) NoProgress: tt.args.noProgress,
RepoBranch: tt.args.repoBranch,
RepoTag: tt.args.repoTag,
RepoCommit: tt.args.repoCommit,
})
tt.assertion(t, err)
defer cleanup() defer cleanup()
}) })
} }

Submodule pkg/fanal/artifact/remote/testdata/test added at 0d8bf3f07c

View File

@@ -0,0 +1,2 @@
x<01><>Aj<41>0s<>+<2B><08><><EFBFBD><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>h߼1\<5C><><EFBFBD>~<7E>un<75>[<5B><>
\ 4[<5B><11><><06>f<EFBFBD><66><EFBFBD><EFBFBD><1C><08><05><>'<27><>}<7D>:<3A>*?

View File

@@ -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>

View File

@@ -1 +1 @@
c906fc4a94762f8a2c77c718947143d16e4e9ec7 0d8bf3f07c3970b3e38c2cfb1c619cb86fae76d2

View File

@@ -0,0 +1 @@
d7937c5f0ce7f2054e4e3be65ab3cd0f9462dc1b

View File

@@ -0,0 +1 @@
c906fc4a94762f8a2c77c718947143d16e4e9ec7

View File

@@ -51,6 +51,7 @@ type Flags struct {
LicenseFlagGroup *LicenseFlagGroup LicenseFlagGroup *LicenseFlagGroup
MisconfFlagGroup *MisconfFlagGroup MisconfFlagGroup *MisconfFlagGroup
RemoteFlagGroup *RemoteFlagGroup RemoteFlagGroup *RemoteFlagGroup
RepoFlagGroup *RepoFlagGroup
ReportFlagGroup *ReportFlagGroup ReportFlagGroup *ReportFlagGroup
SBOMFlagGroup *SBOMFlagGroup SBOMFlagGroup *SBOMFlagGroup
ScanFlagGroup *ScanFlagGroup ScanFlagGroup *ScanFlagGroup
@@ -68,6 +69,7 @@ type Options struct {
LicenseOptions LicenseOptions
MisconfOptions MisconfOptions
RemoteOptions RemoteOptions
RepoOptions
ReportOptions ReportOptions
SBOMOptions SBOMOptions
ScanOptions ScanOptions
@@ -209,6 +211,9 @@ func (f *Flags) groups() []FlagGroup {
if f.RemoteFlagGroup != nil { if f.RemoteFlagGroup != nil {
groups = append(groups, f.RemoteFlagGroup) groups = append(groups, f.RemoteFlagGroup)
} }
if f.RepoFlagGroup != nil {
groups = append(groups, f.RepoFlagGroup)
}
return groups return groups
} }
@@ -302,6 +307,10 @@ func (f *Flags) ToOptions(appVersion string, args []string, globalFlags *GlobalF
opts.RemoteOptions = f.RemoteFlagGroup.ToOptions() opts.RemoteOptions = f.RemoteFlagGroup.ToOptions()
} }
if f.RepoFlagGroup != nil {
opts.RepoOptions = f.RepoFlagGroup.ToOptions()
}
if f.ReportFlagGroup != nil { if f.ReportFlagGroup != nil {
opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output) opts.ReportOptions, err = f.ReportFlagGroup.ToOptions(output)
if err != nil { if err != nil {

58
pkg/flag/repo.go Normal file
View 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),
}
}