mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
test: add HTTP basic authentication to git test server (#9407)
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user