Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/guide/references/configuration/cli/trivy_repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ trivy repository [flags] (REPO_PATH | REPO_URL)
- github
- cosign-vuln
(default "table")
--git-password string password or personal access token to authenticate to a private git repository. TRIVY_GIT_PASSWORD should be used for security reasons.
--git-username string username to authenticate to a private git repository. Defaults to a dummy user, which is enough for token-based authentication.
--helm-api-versions strings Available API versions used for Capabilities.APIVersions. This flag is the same as the api-versions flag of the helm template command. (can specify multiple or separate values with commas: policy/v1/PodDisruptionBudget,apps/v1/Deployment)
--helm-kube-version string Kubernetes version used for Capabilities.KubeVersion. This flag is the same as the kube-version flag of the helm template command.
--helm-set strings specify Helm values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)
Expand Down
6 changes: 6 additions & 0 deletions docs/guide/references/configuration/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,12 @@ repository:
# Same as '--commit'
commit: ""

# Same as '--git-password'
git-password: ""

# Same as '--git-username'
git-username: ""

# Same as '--tag'
tag: ""

Expand Down
24 changes: 24 additions & 0 deletions docs/guide/target/repository.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,3 +179,27 @@ $ trivy repo <your private GitHub repo URL>
$ export GITLAB_TOKEN="your_private_gitlab_token"
$ trivy repo <your private GitLab repo URL>
```

#### Other Git hosting platforms

`GITHUB_TOKEN` and `GITLAB_TOKEN` only cover GitHub and GitLab. For any other platform - Bitbucket, Gitea, GitHub Enterprise, or a self-hosted server - pass the credentials explicitly with `--git-username` and `--git-password`:

```
$ trivy repo --git-username <username> --git-password <password> <your private repo URL>
```

`--git-password` also accepts a personal access token. Platforms that authenticate by token alone ignore the username, so `--git-username` can be omitted in that case:

```
$ trivy repo --git-password <token> <your private repo URL>
```

!!! warning
Passing a secret on the command line exposes it to other users on the machine through the process list and may leave it in your shell history. Use the `TRIVY_GIT_PASSWORD` environment variable instead:

```
$ export TRIVY_GIT_PASSWORD="your_personal_access_token"
$ trivy repo <your private repo URL>
```

These credentials take precedence over `GITHUB_TOKEN` and `GITLAB_TOKEN`.
2 changes: 2 additions & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -647,6 +647,8 @@ func (r *runner) initScannerConfig(ctx context.Context, opts flag.Options) (Scan
RepoBranch: opts.RepoBranch,
RepoCommit: opts.RepoCommit,
RepoTag: opts.RepoTag,
RepoGitUsername: opts.RepoGitUsername,
RepoGitPassword: opts.RepoGitPassword,
SBOMSources: opts.SBOMSources,
RekorURL: opts.RekorURL,
AWSRegion: opts.Region,
Expand Down
8 changes: 5 additions & 3 deletions pkg/fanal/artifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,11 @@ type Option struct {
Original string

// Git repositories
RepoBranch string
RepoCommit string
RepoTag string
RepoBranch string
RepoCommit string
RepoTag string
RepoGitUsername string
RepoGitPassword string

// For image scanning
ImageOption types.ImageOptions
Expand Down
24 changes: 20 additions & 4 deletions pkg/fanal/artifact/repo/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ func cloneRepo(u *url.URL, artifactOpt artifact.Option) (string, error) {

cloneOptions := git.CloneOptions{
URL: u.String(),
Auth: gitAuth(),
Auth: gitAuth(artifactOpt),
Progress: os.Stderr,
InsecureSkipTLS: artifactOpt.Insecure,
}
Expand Down Expand Up @@ -152,12 +152,28 @@ func newURL(rawurl string) (*url.URL, error) {
return u, nil
}

// Helper function to check for a GitHub/GitLab token from env vars in order to
// make authenticated requests to access private repos
func gitAuth() http.AuthMethod {
// Helper function to look up the credentials used to access private repos.
// Credentials passed explicitly take precedence, so that repositories hosted
// anywhere (Bitbucket, GitHub Enterprise, self-hosted, etc.) can be scanned.
// Otherwise we fall back to the GitHub/GitLab token env vars.
func gitAuth(artifactOpt artifact.Option) http.AuthMethod {
// The username can be anything for HTTPS Git operations
gitUsername := "fanal-aquasecurity-scan"

// Credentials given via --git-username/--git-password (or the corresponding
// TRIVY_GIT_USERNAME/TRIVY_GIT_PASSWORD env vars) win over everything else.
if artifactOpt.RepoGitPassword != "" {
username := artifactOpt.RepoGitUsername
if username == "" {
// Token-based authentication doesn't care about the username
username = gitUsername
}
return &http.BasicAuth{
Username: username,
Password: artifactOpt.RepoGitPassword,
}
}

// We first check if a GitHub token was provided
githubToken := os.Getenv("GITHUB_TOKEN")
if githubToken != "" {
Expand Down
87 changes: 82 additions & 5 deletions pkg/fanal/artifact/repo/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,9 @@ func setupAuthTestServer(t *testing.T, username, password string) *url.URL {
}

// testInspectArtifact is a helper function to inspect an artifact and assert the results
func testInspectArtifact(t *testing.T, target, wantRepoURL, wantErr string) {
func testInspectArtifact(t *testing.T, target, wantRepoURL, wantErr string, artifactOpt artifact.Option) {
t.Helper()
art, cleanup, err := NewArtifact(target, cache.NewMemoryCache(), walker.NewFS(), artifact.Option{})
art, cleanup, err := NewArtifact(target, cache.NewMemoryCache(), walker.NewFS(), artifactOpt)
t.Cleanup(cleanup)

if wantErr != "" {
Expand Down Expand Up @@ -430,7 +430,84 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
}

// Test using helper function
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr)
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr, artifact.Option{})
})
}
})

// Test with credentials passed explicitly via --git-username/--git-password
t.Run("git credential options", func(t *testing.T) {
const defaultGitUsername = "fanal-aquasecurity-scan" // The username Trivy falls back to

// One server expects a real username, the other only cares about the token
namedURL := setupAuthTestServer(t, testUsername, testPassword)
tokenURL := setupAuthTestServer(t, defaultGitUsername, testPassword)

tests := []struct {
name string
target string
artifactOpt artifact.Option
envVars map[string]string
wantErr string
wantRepoURL string
}{
{
name: "success with username and password",
target: namedURL.String(),
artifactOpt: artifact.Option{
RepoGitUsername: testUsername,
RepoGitPassword: testPassword,
},
wantRepoURL: namedURL.String(),
},
{
name: "success with password only, falling back to the default username",
target: tokenURL.String(),
artifactOpt: artifact.Option{
RepoGitPassword: testPassword,
},
wantRepoURL: tokenURL.String(),
},
{
name: "credentials take precedence over GITHUB_TOKEN",
target: namedURL.String(),
artifactOpt: artifact.Option{
RepoGitUsername: testUsername,
RepoGitPassword: testPassword,
},
envVars: map[string]string{
"GITHUB_TOKEN": "wrongpassword",
},
wantRepoURL: namedURL.String(),
},
{
name: "failure with wrong password",
target: namedURL.String(),
artifactOpt: artifact.Option{
RepoGitUsername: testUsername,
RepoGitPassword: "wrongpassword",
},
wantErr: "authentication required",
},
{
name: "failure with username only",
target: namedURL.String(),
artifactOpt: artifact.Option{
RepoGitUsername: testUsername,
},
wantErr: "authentication required",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set test environment variables
for key, value := range tt.envVars {
t.Setenv(key, value)
}

// Test using helper function
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr, tt.artifactOpt)
})
}
})
Expand Down Expand Up @@ -475,7 +552,7 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test using helper function
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr)
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr, artifact.Option{})
})
}
})
Expand All @@ -500,7 +577,7 @@ func TestArtifact_InspectWithAuth(t *testing.T) {
require.NoError(t, err)

// Scan and verify the local cloned directory
testInspectArtifact(t, cloneDir, tsURL.String(), "")
testInspectArtifact(t, cloneDir, tsURL.String(), "", artifact.Option{})
})
}

Expand Down
44 changes: 32 additions & 12 deletions pkg/flag/repo_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,41 @@ var (
ConfigName: "repository.tag",
Usage: "pass the tag name to be scanned",
}
GitUsernameFlag = Flag[string]{
Name: "git-username",
ConfigName: "repository.git-username",
Usage: "username to authenticate to a private git repository. Defaults to a dummy user, which is enough for token-based authentication.",
}
GitPasswordFlag = Flag[string]{
Name: "git-password",
ConfigName: "repository.git-password",
Usage: "password or personal access token to authenticate to a private git repository. TRIVY_GIT_PASSWORD should be used for security reasons.",
}
)

type RepoFlagGroup struct {
Branch *Flag[string]
Commit *Flag[string]
Tag *Flag[string]
Branch *Flag[string]
Commit *Flag[string]
Tag *Flag[string]
GitUsername *Flag[string]
GitPassword *Flag[string]
}

type RepoOptions struct {
RepoBranch string
RepoCommit string
RepoTag string
RepoBranch string
RepoCommit string
RepoTag string
RepoGitUsername string
RepoGitPassword string
}

func NewRepoFlagGroup() *RepoFlagGroup {
return &RepoFlagGroup{
Branch: FetchBranchFlag.Clone(),
Commit: FetchCommitFlag.Clone(),
Tag: FetchTagFlag.Clone(),
Branch: FetchBranchFlag.Clone(),
Commit: FetchCommitFlag.Clone(),
Tag: FetchTagFlag.Clone(),
GitUsername: GitUsernameFlag.Clone(),
GitPassword: GitPasswordFlag.Clone(),
}
}

Expand All @@ -47,14 +63,18 @@ func (f *RepoFlagGroup) Flags() []Flagger {
f.Branch,
f.Commit,
f.Tag,
f.GitUsername,
f.GitPassword,
}
}

func (f *RepoFlagGroup) ToOptions(opts *Options) error {
opts.RepoOptions = RepoOptions{
RepoBranch: f.Branch.Value(),
RepoCommit: f.Commit.Value(),
RepoTag: f.Tag.Value(),
RepoBranch: f.Branch.Value(),
RepoCommit: f.Commit.Value(),
RepoTag: f.Tag.Value(),
RepoGitUsername: f.GitUsername.Value(),
RepoGitPassword: f.GitPassword.Value(),
}
return nil
}
8 changes: 8 additions & 0 deletions schema/trivy-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,14 @@
"tag": {
"type": "string",
"description": "pass the tag name to be scanned"
},
"git-username": {
"type": "string",
"description": "username to authenticate to a private git repository. Defaults to a dummy user, which is enough for token-based authentication."
},
"git-password": {
"type": "string",
"description": "password or personal access token to authenticate to a private git repository. TRIVY_GIT_PASSWORD should be used for security reasons."
}
}
},
Expand Down