test: add HTTP basic authentication to git test server (#9407)

This commit is contained in:
Teppei Fukuda
2025-09-01 13:42:41 +04:00
committed by GitHub
parent aa7cf4387c
commit 6fa3849c10
4 changed files with 219 additions and 24 deletions

View File

@@ -15,6 +15,7 @@ import (
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/sosedoff/gitkit"
"github.com/stretchr/testify/require"
@@ -27,7 +28,31 @@ var signature = &object.Signature{
When: time.Now(),
}
func NewServer(t *testing.T, repo, dir string) *httptest.Server {
// Options contains configuration options for git server authentication
type Options struct {
Username string
Password string
}
// setupGitServer creates and starts a git server with the given bare repository directory
func setupGitServer(t *testing.T, bareDir string, opts Options) *httptest.Server {
hasAuth := opts.Username != "" && opts.Password != ""
service := gitkit.New(gitkit.Config{
Dir: bareDir,
Auth: hasAuth,
})
if hasAuth {
service.AuthFunc = func(cred gitkit.Credential, _ *gitkit.Request) (bool, error) {
return cred.Username == opts.Username && cred.Password == opts.Password, nil
}
}
err := service.Setup()
require.NoError(t, err)
return httptest.NewServer(service)
}
func NewServer(t *testing.T, repo, dir string, opts Options) *httptest.Server {
wtDir := t.TempDir()
// git init
@@ -53,16 +78,10 @@ func NewServer(t *testing.T, repo, dir string) *httptest.Server {
_, err = git.PlainClone(gitDir, true, &git.CloneOptions{URL: wtDir})
require.NoError(t, err)
// Set up a git server
service := gitkit.New(gitkit.Config{Dir: bareDir})
err = service.Setup()
require.NoError(t, err)
return httptest.NewServer(service)
return setupGitServer(t, bareDir, opts)
}
// NewServerWithRepository creates a git server with an existing repository
func NewServerWithRepository(t *testing.T, repo, dir string) *httptest.Server {
func NewServerWithRepository(t *testing.T, repo, dir string, opts Options) *httptest.Server {
// Create a bare repository
bareDir := t.TempDir()
gitDir := filepath.Join(bareDir, repo+".git")
@@ -85,17 +104,12 @@ func NewServerWithRepository(t *testing.T, repo, dir string) *httptest.Server {
require.NoError(t, err)
}
// Set up a git server
service := gitkit.New(gitkit.Config{Dir: bareDir})
err = service.Setup()
require.NoError(t, err)
return httptest.NewServer(service)
return setupGitServer(t, bareDir, opts)
}
// NewTestServer creates a git server with the local copy of "github.com/aquasecurity/trivy-test-repo".
// If the test repository doesn't exist, it suggests running 'mage test:unit'.
func NewTestServer(t *testing.T) *httptest.Server {
func NewTestServer(t *testing.T, opts Options) *httptest.Server {
_, filePath, _, _ := runtime.Caller(0)
dir := filepath.Join(filepath.Dir(filePath), "testdata", "test-repo")
@@ -103,14 +117,21 @@ func NewTestServer(t *testing.T) *httptest.Server {
require.Fail(t, "test-repo not found. Please run 'mage test:unit' to set up the test fixtures")
}
return NewServerWithRepository(t, "test-repo", dir)
return NewServerWithRepository(t, "test-repo", dir, opts)
}
func Clone(t *testing.T, ts *httptest.Server, repo, worktree string) *git.Repository {
func Clone(t *testing.T, ts *httptest.Server, repo, worktree string, opts Options) *git.Repository {
cloneOptions := git.CloneOptions{
URL: ts.URL + "/" + repo + ".git",
}
if opts.Username != "" && opts.Password != "" {
cloneOptions.Auth = &http.BasicAuth{
Username: opts.Username,
Password: opts.Password,
}
}
r, err := git.PlainClone(worktree, false, &cloneOptions)
require.NoError(t, err)

View File

@@ -3,10 +3,12 @@
package repo
import (
"net/url"
"os"
"path/filepath"
"testing"
"github.com/go-git/go-git/v5"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -22,7 +24,7 @@ import (
)
func TestNewArtifact(t *testing.T) {
ts := gittest.NewTestServer(t)
ts := gittest.NewTestServer(t, gittest.Options{})
defer ts.Close()
type args struct {
@@ -164,7 +166,7 @@ func TestNewArtifact(t *testing.T) {
}
func TestArtifact_Inspect(t *testing.T) {
ts := gittest.NewTestServer(t)
ts := gittest.NewTestServer(t, gittest.Options{})
defer ts.Close()
tests := []struct {
@@ -330,6 +332,178 @@ func TestArtifact_Inspect(t *testing.T) {
}
}
// setupAuthTestServer creates a test server with authentication and returns parsed URL with /test-repo.git path
func setupAuthTestServer(t *testing.T, username, password string) *url.URL {
t.Helper()
ts := gittest.NewTestServer(t, gittest.Options{
Username: username,
Password: password,
})
t.Cleanup(ts.Close)
tsURL, err := url.Parse(ts.URL)
require.NoError(t, err)
tsURL.Path = "/test-repo.git"
return tsURL
}
// testInspectArtifact is a helper function to inspect an artifact and assert the results
func testInspectArtifact(t *testing.T, target, wantRepoURL, wantErr string) {
t.Helper()
art, cleanup, err := NewArtifact(target, cache.NewMemoryCache(), walker.NewFS(), artifact.Option{})
t.Cleanup(cleanup)
if wantErr != "" {
require.ErrorContains(t, err, wantErr)
return
}
require.NoError(t, err)
// Verify Inspect works
ref, err := art.Inspect(t.Context())
require.NoError(t, err)
// Verify the RepoURL
assert.Equal(t, wantRepoURL, ref.RepoMetadata.RepoURL)
// Verify we have blob IDs (indicating successful scan)
assert.NotEmpty(t, ref.BlobIDs)
}
func TestArtifact_InspectWithAuth(t *testing.T) {
const (
testUsername = "testuser"
testPassword = "testpass"
)
// Test with environment variable authentication (GITHUB_TOKEN, GITLAB_TOKEN)
t.Run("environment variable authentication", func(t *testing.T) {
const testGitUsername = "fanal-aquasecurity-scan" // This is the username used by Trivy
// Setup test server with authentication
tsURL := setupAuthTestServer(t, testGitUsername, testPassword)
tests := []struct {
name string
target string
envVars map[string]string
wantErr string
wantRepoURL string
}{
{
name: "success with GITHUB_TOKEN",
target: tsURL.String(),
envVars: map[string]string{
"GITHUB_TOKEN": testPassword,
},
wantRepoURL: tsURL.String(),
},
{
name: "success with GITLAB_TOKEN",
target: tsURL.String(),
envVars: map[string]string{
"GITLAB_TOKEN": testPassword,
},
wantRepoURL: tsURL.String(),
},
{
name: "failure without token",
target: tsURL.String(),
wantErr: "authentication required",
},
{
name: "failure with wrong token",
target: tsURL.String(),
envVars: map[string]string{
"GITHUB_TOKEN": "wrongpassword",
},
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)
})
}
})
// Test with URL-embedded authentication
t.Run("URL embedded authentication", func(t *testing.T) {
// Setup test server with authentication
tsURL := setupAuthTestServer(t, testUsername, testPassword)
// Helper function to generate target URL with credentials
makeTarget := func(username, password string) string {
u := *tsURL // Copy the URL
if username != "" && password != "" {
u.User = url.UserPassword(username, password)
}
return u.String()
}
tests := []struct {
name string
target string
wantRepoURL string
wantErr string
}{
{
name: "success with embedded credentials",
target: makeTarget(testUsername, testPassword),
wantRepoURL: makeTarget(testUsername, testPassword), // TODO: username/password should be stripped
},
{
name: "failure with wrong password",
target: makeTarget(testUsername, "wrongpass"),
wantErr: "authentication required",
},
{
name: "failure without credentials",
target: makeTarget("", ""),
wantErr: "authentication required",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test using helper function
testInspectArtifact(t, tt.target, tt.wantRepoURL, tt.wantErr)
})
}
})
// Test cloning with embedded credentials and then scanning the local directory
t.Run("clone with credentials then scan local", func(t *testing.T) {
// Setup test server with authentication
tsURL := setupAuthTestServer(t, testUsername, testPassword)
// Add credentials to URL
tsURL.User = url.UserPassword(testUsername, testPassword)
targetWithCreds := tsURL.String()
// Clone the repository with URL-embedded credentials
cloneDir := filepath.Join(t.TempDir(), "cloned-repo")
// Use go-git directly to clone with URL-embedded credentials
_, err := git.PlainClone(cloneDir, false, &git.CloneOptions{
URL: targetWithCreds,
})
require.NoError(t, err)
// Scan and verify the local cloned directory
// TODO: The credentials in the URL should be stripped in the RepoURL
testInspectArtifact(t, cloneDir, targetWithCreds, "")
})
}
func Test_newURL(t *testing.T) {
type args struct {
rawurl string

View File

@@ -51,7 +51,7 @@ func buildGitSource(repoURL string) string { return "git::" + repoURL }
func TestResolveModuleFromCache(t *testing.T) {
repo := "terraform-aws-s3-bucket"
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket", gittest.Options{})
defer gs.Close()
repoURL := gs.URL + "/" + repo + ".git"
@@ -141,7 +141,7 @@ func TestResolveModuleFromCache(t *testing.T) {
func TestResolveModuleFromCacheWithDifferentSubdir(t *testing.T) {
repo := "terraform-aws-s3-bucket"
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket")
gs := gittest.NewServer(t, repo, "testdata/terraform-aws-s3-bucket", gittest.Options{})
defer gs.Close()
repoURL := gs.URL + "/" + repo + ".git"

View File

@@ -27,10 +27,10 @@ import (
)
func setupGitRepository(t *testing.T, repo, dir string) *httptest.Server {
gs := gittest.NewServer(t, repo, dir)
gs := gittest.NewServer(t, repo, dir, gittest.Options{})
worktree := t.TempDir()
r := gittest.Clone(t, gs, repo, worktree)
r := gittest.Clone(t, gs, repo, worktree, gittest.Options{})
// git tag
gittest.SetTag(t, r, "v0.2.0")