Skip to content

Commit 6fa3849

Browse files
authored
test: add HTTP basic authentication to git test server (#9407)
1 parent aa7cf43 commit 6fa3849

File tree

4 files changed

+219
-24
lines changed

4 files changed

+219
-24
lines changed

internal/gittest/server.go

Lines changed: 39 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/go-git/go-git/v5/config"
1616
"github.com/go-git/go-git/v5/plumbing"
1717
"github.com/go-git/go-git/v5/plumbing/object"
18+
"github.com/go-git/go-git/v5/plumbing/transport/http"
1819
"github.com/sosedoff/gitkit"
1920
"github.com/stretchr/testify/require"
2021

@@ -27,7 +28,31 @@ var signature = &object.Signature{
2728
When: time.Now(),
2829
}
2930

30-
func NewServer(t *testing.T, repo, dir string) *httptest.Server {
31+
// Options contains configuration options for git server authentication
32+
type Options struct {
33+
Username string
34+
Password string
35+
}
36+
37+
// setupGitServer creates and starts a git server with the given bare repository directory
38+
func setupGitServer(t *testing.T, bareDir string, opts Options) *httptest.Server {
39+
hasAuth := opts.Username != "" && opts.Password != ""
40+
service := gitkit.New(gitkit.Config{
41+
Dir: bareDir,
42+
Auth: hasAuth,
43+
})
44+
if hasAuth {
45+
service.AuthFunc = func(cred gitkit.Credential, _ *gitkit.Request) (bool, error) {
46+
return cred.Username == opts.Username && cred.Password == opts.Password, nil
47+
}
48+
}
49+
err := service.Setup()
50+
require.NoError(t, err)
51+
52+
return httptest.NewServer(service)
53+
}
54+
55+
func NewServer(t *testing.T, repo, dir string, opts Options) *httptest.Server {
3156
wtDir := t.TempDir()
3257

3358
// git init
@@ -53,16 +78,10 @@ func NewServer(t *testing.T, repo, dir string) *httptest.Server {
5378
_, err = git.PlainClone(gitDir, true, &git.CloneOptions{URL: wtDir})
5479
require.NoError(t, err)
5580

56-
// Set up a git server
57-
service := gitkit.New(gitkit.Config{Dir: bareDir})
58-
err = service.Setup()
59-
require.NoError(t, err)
60-
61-
return httptest.NewServer(service)
81+
return setupGitServer(t, bareDir, opts)
6282
}
6383

64-
// NewServerWithRepository creates a git server with an existing repository
65-
func NewServerWithRepository(t *testing.T, repo, dir string) *httptest.Server {
84+
func NewServerWithRepository(t *testing.T, repo, dir string, opts Options) *httptest.Server {
6685
// Create a bare repository
6786
bareDir := t.TempDir()
6887
gitDir := filepath.Join(bareDir, repo+".git")
@@ -85,32 +104,34 @@ func NewServerWithRepository(t *testing.T, repo, dir string) *httptest.Server {
85104
require.NoError(t, err)
86105
}
87106

88-
// Set up a git server
89-
service := gitkit.New(gitkit.Config{Dir: bareDir})
90-
err = service.Setup()
91-
require.NoError(t, err)
92-
93-
return httptest.NewServer(service)
107+
return setupGitServer(t, bareDir, opts)
94108
}
95109

96110
// NewTestServer creates a git server with the local copy of "github.com/aquasecurity/trivy-test-repo".
97111
// If the test repository doesn't exist, it suggests running 'mage test:unit'.
98-
func NewTestServer(t *testing.T) *httptest.Server {
112+
func NewTestServer(t *testing.T, opts Options) *httptest.Server {
99113
_, filePath, _, _ := runtime.Caller(0)
100114
dir := filepath.Join(filepath.Dir(filePath), "testdata", "test-repo")
101115

102116
if _, err := os.Stat(dir); os.IsNotExist(err) {
103117
require.Fail(t, "test-repo not found. Please run 'mage test:unit' to set up the test fixtures")
104118
}
105119

106-
return NewServerWithRepository(t, "test-repo", dir)
120+
return NewServerWithRepository(t, "test-repo", dir, opts)
107121
}
108122

109-
func Clone(t *testing.T, ts *httptest.Server, repo, worktree string) *git.Repository {
123+
func Clone(t *testing.T, ts *httptest.Server, repo, worktree string, opts Options) *git.Repository {
110124
cloneOptions := git.CloneOptions{
111125
URL: ts.URL + "/" + repo + ".git",
112126
}
113127

128+
if opts.Username != "" && opts.Password != "" {
129+
cloneOptions.Auth = &http.BasicAuth{
130+
Username: opts.Username,
131+
Password: opts.Password,
132+
}
133+
}
134+
114135
r, err := git.PlainClone(worktree, false, &cloneOptions)
115136
require.NoError(t, err)
116137

pkg/fanal/artifact/repo/git_test.go

Lines changed: 176 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33
package repo
44

55
import (
6+
"net/url"
67
"os"
78
"path/filepath"
89
"testing"
910

11+
"github.com/go-git/go-git/v5"
1012
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214

@@ -22,7 +24,7 @@ import (
2224
)
2325

2426
func TestNewArtifact(t *testing.T) {
25-
ts := gittest.NewTestServer(t)
27+
ts := gittest.NewTestServer(t, gittest.Options{})
2628
defer ts.Close()
2729

2830
type args struct {
@@ -164,7 +166,7 @@ func TestNewArtifact(t *testing.T) {
164166
}
165167

166168
func TestArtifact_Inspect(t *testing.T) {
167-
ts := gittest.NewTestServer(t)
169+
ts := gittest.NewTestServer(t, gittest.Options{})
168170
defer ts.Close()
169171

170172
tests := []struct {
@@ -330,6 +332,178 @@ func TestArtifact_Inspect(t *testing.T) {
330332
}
331333
}
332334

335+
// setupAuthTestServer creates a test server with authentication and returns parsed URL with /test-repo.git path
336+
func setupAuthTestServer(t *testing.T, username, password string) *url.URL {
337+
t.Helper()
338+
ts := gittest.NewTestServer(t, gittest.Options{
339+
Username: username,
340+
Password: password,
341+
})
342+
t.Cleanup(ts.Close)
343+
344+
tsURL, err := url.Parse(ts.URL)
345+
require.NoError(t, err)
346+
tsURL.Path = "/test-repo.git"
347+
348+
return tsURL
349+
}
350+
351+
// testInspectArtifact is a helper function to inspect an artifact and assert the results
352+
func testInspectArtifact(t *testing.T, target, wantRepoURL, wantErr string) {
353+
t.Helper()
354+
art, cleanup, err := NewArtifact(target, cache.NewMemoryCache(), walker.NewFS(), artifact.Option{})
355+
t.Cleanup(cleanup)
356+
357+
if wantErr != "" {
358+
require.ErrorContains(t, err, wantErr)
359+
return
360+
}
361+
require.NoError(t, err)
362+
363+
// Verify Inspect works
364+
ref, err := art.Inspect(t.Context())
365+
require.NoError(t, err)
366+
367+
// Verify the RepoURL
368+
assert.Equal(t, wantRepoURL, ref.RepoMetadata.RepoURL)
369+
370+
// Verify we have blob IDs (indicating successful scan)
371+
assert.NotEmpty(t, ref.BlobIDs)
372+
}
373+
374+
func TestArtifact_InspectWithAuth(t *testing.T) {
375+
const (
376+
testUsername = "testuser"
377+
testPassword = "testpass"
378+
)
379+
380+
// Test with environment variable authentication (GITHUB_TOKEN, GITLAB_TOKEN)
381+
t.Run("environment variable authentication", func(t *testing.T) {
382+
const testGitUsername = "fanal-aquasecurity-scan" // This is the username used by Trivy
383+
384+
// Setup test server with authentication
385+
tsURL := setupAuthTestServer(t, testGitUsername, testPassword)
386+
387+
tests := []struct {
388+
name string
389+
target string
390+
envVars map[string]string
391+
wantErr string
392+
wantRepoURL string
393+
}{
394+
{
395+
name: "success with GITHUB_TOKEN",
396+
target: tsURL.String(),
397+
envVars: map[string]string{
398+
"GITHUB_TOKEN": testPassword,
399+
},
400+
wantRepoURL: tsURL.String(),
401+
},
402+
{
403+
name: "success with GITLAB_TOKEN",
404+
target: tsURL.String(),
405+
envVars: map[string]string{
406+
"GITLAB_TOKEN": testPassword,
407+
},
408+
wantRepoURL: tsURL.String(),
409+
},
410+
{
411+
name: "failure without token",
412+
target: tsURL.String(),
413+
wantErr: "authentication required",
414+
},
415+
{
416+
name: "failure with wrong token",
417+
target: tsURL.String(),
418+
envVars: map[string]string{
419+
"GITHUB_TOKEN": "wrongpassword",
420+
},
421+
wantErr: "authentication required",
422+
},
423+
}
424+
425+
for _, tt := range tests {
426+
t.Run(tt.name, func(t *testing.T) {
427+
// Set test environment variables
428+
for key, value := range tt.envVars {
429+
t.Setenv(key, value)
430+
}
431+
432+
// Test using helper function
433+
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr)
434+
})
435+
}
436+
})
437+
438+
// Test with URL-embedded authentication
439+
t.Run("URL embedded authentication", func(t *testing.T) {
440+
// Setup test server with authentication
441+
tsURL := setupAuthTestServer(t, testUsername, testPassword)
442+
443+
// Helper function to generate target URL with credentials
444+
makeTarget := func(username, password string) string {
445+
u := *tsURL // Copy the URL
446+
if username != "" && password != "" {
447+
u.User = url.UserPassword(username, password)
448+
}
449+
return u.String()
450+
}
451+
452+
tests := []struct {
453+
name string
454+
target string
455+
wantRepoURL string
456+
wantErr string
457+
}{
458+
{
459+
name: "success with embedded credentials",
460+
target: makeTarget(testUsername, testPassword),
461+
wantRepoURL: makeTarget(testUsername, testPassword), // TODO: username/password should be stripped
462+
},
463+
{
464+
name: "failure with wrong password",
465+
target: makeTarget(testUsername, "wrongpass"),
466+
wantErr: "authentication required",
467+
},
468+
{
469+
name: "failure without credentials",
470+
target: makeTarget("", ""),
471+
wantErr: "authentication required",
472+
},
473+
}
474+
475+
for _, tt := range tests {
476+
t.Run(tt.name, func(t *testing.T) {
477+
// Test using helper function
478+
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr)
479+
})
480+
}
481+
})
482+
483+
// Test cloning with embedded credentials and then scanning the local directory
484+
t.Run("clone with credentials then scan local", func(t *testing.T) {
485+
// Setup test server with authentication
486+
tsURL := setupAuthTestServer(t, testUsername, testPassword)
487+
488+
// Add credentials to URL
489+
tsURL.User = url.UserPassword(testUsername, testPassword)
490+
targetWithCreds := tsURL.String()
491+
492+
// Clone the repository with URL-embedded credentials
493+
cloneDir := filepath.Join(t.TempDir(), "cloned-repo")
494+
495+
// Use go-git directly to clone with URL-embedded credentials
496+
_, err := git.PlainClone(cloneDir, false, &git.CloneOptions{
497+
URL: targetWithCreds,
498+
})
499+
require.NoError(t, err)
500+
501+
// Scan and verify the local cloned directory
502+
// TODO: The credentials in the URL should be stripped in the RepoURL
503+
testInspectArtifact(t, cloneDir, targetWithCreds, "")
504+
})
505+
}
506+
333507
func Test_newURL(t *testing.T) {
334508
type args struct {
335509
rawurl string

pkg/iac/scanners/terraform/parser/resolvers/cache_integration_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func buildGitSource(repoURL string) string { return "git::" + repoURL }
5151
func TestResolveModuleFromCache(t *testing.T) {
5252

5353
repo := "terraform-aws-s3-bucket"
54-
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
54+
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket", gittest.Options{})
5555
defer gs.Close()
5656

5757
repoURL := gs.URL + "/" + repo + ".git"
@@ -141,7 +141,7 @@ func TestResolveModuleFromCache(t *testing.T) {
141141

142142
func TestResolveModuleFromCacheWithDifferentSubdir(t *testing.T) {
143143
repo := "terraform-aws-s3-bucket"
144-
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
144+
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket", gittest.Options{})
145145
defer gs.Close()
146146

147147
repoURL := gs.URL + "/" + repo + ".git"

pkg/plugin/manager_unix_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@ import (
2727
)
2828

2929
func setupGitRepository(t *testing.T, repo, dir string) *httptest.Server {
30-
gs := gittest.NewServer(t, repo, dir)
30+
gs := gittest.NewServer(t, repo, dir, gittest.Options{})
3131

3232
worktree := t.TempDir()
33-
r := gittest.Clone(t, gs, repo, worktree)
33+
r := gittest.Clone(t, gs, repo, worktree, gittest.Options{})
3434

3535
// git tag
3636
gittest.SetTag(t, r, "v0.2.0")

0 commit comments

Comments
 (0)