Skip to content
Merged
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/introduction/proxying.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ HTTP_PROXY=socks5://127.0.0.1:1080 pipeleek gl scan -u https://gitlab.internal.c

Alternatively, use the `--proxy` flag to set any proxy from the command line without relying on `HTTP_PROXY`. It accepts both HTTP and SOCKS5 URLs and takes precedence over the environment variable:

All Gitea commands (`scan`, `enum`, `secrets`, `variables`, and `vuln`) use these shared HTTP settings, including `--ignore-proxy`, `--tls-verification`, and `--http-timeout`. For `vuln`, the settings apply to both the Gitea version lookup and NIST requests.

```bash
# HTTP proxy
pipeleek --proxy http://127.0.0.1:8080 gl scan -u https://gitlab.com -t glpat-xxxxx
Expand Down
4 changes: 3 additions & 1 deletion pkg/gitea/enum/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import (
"fmt"

"code.gitea.io/sdk/gitea"
"github.com/CompassSecurity/pipeleek/pkg/httpclient"
"github.com/rs/zerolog/log"
)

// RunEnum performs the enumeration of Gitea access rights.
func RunEnum(giteaURL, apiToken string) error {
client, err := gitea.NewClient(giteaURL, gitea.SetToken(apiToken))
client, err := gitea.NewClient(giteaURL, gitea.SetToken(apiToken),
gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient()))
if err != nil {
return err
}
Expand Down
207 changes: 207 additions & 0 deletions pkg/gitea/http_settings_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
package gitea_test

import (
"bytes"
"errors"
"net"
"net/http"
"net/http/httptest"
"sync/atomic"
"testing"
"time"

giteaenum "github.com/CompassSecurity/pipeleek/pkg/gitea/enum"
"github.com/CompassSecurity/pipeleek/pkg/gitea/secrets"
"github.com/CompassSecurity/pipeleek/pkg/gitea/variables"
"github.com/CompassSecurity/pipeleek/pkg/gitea/vuln"
"github.com/CompassSecurity/pipeleek/pkg/httpclient"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func serveGitea(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
switch r.URL.Path {
case "/api/v1/version":
_, _ = w.Write([]byte(`{"version":"1.20.0"}`))
case "/api/v1/user":
_, _ = w.Write([]byte(`{"id":1,"login":"testuser"}`))
case "/api/v1/user/repos", "/api/v1/user/orgs":
_, _ = w.Write([]byte(`[]`))
default:
w.WriteHeader(http.StatusNotFound)
}
}

func TestCommandsHTTPSettings(t *testing.T) {
commands := []struct {
name string
run func(string) error
}{
{
name: "enum",
run: func(url string) error {
return giteaenum.RunEnum(url, "test-token")
},
},
{
name: "secrets",
run: func(url string) error {
return secrets.ListAllSecrets(secrets.Config{URL: url, Token: "test-token"})
},
},
{
name: "variables",
run: func(url string) error {
return variables.ListAllVariables(variables.Config{URL: url, Token: "test-token"})
},
},
}

runCommandsHTTPSettings(t, commands)
}

func TestVulnHTTPSettings(t *testing.T) {
httpclient.SetIgnoreProxy(true)
t.Cleanup(func() {
httpclient.SetProxy("")
httpclient.SetIgnoreProxy(false)
httpclient.SetInsecureSkipVerify(true)
httpclient.SetHTTPTimeout(0)
})
nist := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"totalResults":0,"vulnerabilities":[]}`))
}))
defer nist.Close()
t.Setenv("PIPELEEK_NIST_BASE_URL", nist.URL)

for _, mode := range []string{"explicit proxy", "self-signed TLS", "enforced TLS", "timeout"} {
t.Run(mode, func(t *testing.T) {
httpclient.SetProxy("")
httpclient.SetInsecureSkipVerify(true)
httpclient.SetHTTPTimeout(0)
var output bytes.Buffer
savedLogger := log.Logger
log.Logger = zerolog.New(&output)
defer func() { log.Logger = savedLogger }()

var versionRequests atomic.Int32
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/nist" {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"totalResults":0,"vulnerabilities":[]}`))
return
}
versionRequests.Add(1)
if mode == "timeout" {
time.Sleep(100 * time.Millisecond)
}
serveGitea(w, r)
})
var target *httptest.Server
if mode == "self-signed TLS" || mode == "enforced TLS" {
target = httptest.NewTLSServer(handler)
} else {
target = httptest.NewServer(handler)
}
defer target.Close()
url := target.URL
switch mode {
case "explicit proxy":
httpclient.SetProxy(target.URL)
t.Setenv("PIPELEEK_NIST_BASE_URL", "http://nist.invalid/nist")
url = "http://gitea.invalid"
case "enforced TLS":
httpclient.SetInsecureSkipVerify(false)
case "timeout":
httpclient.SetHTTPTimeout(20 * time.Millisecond)
}

vuln.RunCheckVulns(url, "test-token")
assert.Contains(t, output.String(), "Finished vuln scan")
if mode == "enforced TLS" || mode == "timeout" {
assert.Contains(t, output.String(), "Failed creating Gitea client")
assert.Contains(t, output.String(), `"version":"none"`)
} else {
assert.Contains(t, output.String(), `"version":"1.20.0"`)
assert.NotContains(t, output.String(), "Failed creating Gitea client")
assert.Positive(t, versionRequests.Load())
}
})
}
}

func runCommandsHTTPSettings(t *testing.T, commands []struct {
name string
run func(string) error
}) {
t.Helper()
for _, command := range commands {
t.Run(command.name, func(t *testing.T) {
httpclient.SetProxy("")
httpclient.SetIgnoreProxy(true)
httpclient.SetInsecureSkipVerify(true)
httpclient.SetHTTPTimeout(0)
t.Cleanup(func() {
httpclient.SetProxy("")
httpclient.SetIgnoreProxy(false)
httpclient.SetInsecureSkipVerify(true)
httpclient.SetHTTPTimeout(0)
})

t.Run("explicit proxy takes precedence over ignore-proxy", func(t *testing.T) {
var directRequests, proxyRequests atomic.Int32
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
directRequests.Add(1)
serveGitea(w, r)
}))
defer target.Close()
proxy := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxyRequests.Add(1)
assert.True(t, r.URL.IsAbs(), "proxy must receive an absolute request URL")
assert.Equal(t, "token test-token", r.Header.Get("Authorization"))
serveGitea(w, r)
}))
defer proxy.Close()

httpclient.SetProxy(proxy.URL)
defer httpclient.SetProxy("")

require.NoError(t, command.run(target.URL))
assert.GreaterOrEqual(t, proxyRequests.Load(), int32(3), "all SDK requests must use the proxy")
assert.Zero(t, directRequests.Load(), "requests must not bypass the explicit proxy")
})

t.Run("TLS verification settings", func(t *testing.T) {
target := httptest.NewTLSServer(http.HandlerFunc(serveGitea))
defer target.Close()

httpclient.SetInsecureSkipVerify(true)
require.NoError(t, command.run(target.URL), "default settings must support self-signed targets")

httpclient.SetInsecureSkipVerify(false)
defer httpclient.SetInsecureSkipVerify(true)
require.Error(t, command.run(target.URL), "enforced verification must reject an untrusted certificate")
})

t.Run("HTTP timeout", func(t *testing.T) {
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
time.Sleep(100 * time.Millisecond)
serveGitea(w, r)
}))
defer target.Close()

httpclient.SetHTTPTimeout(10 * time.Millisecond)
defer httpclient.SetHTTPTimeout(0)
err := command.run(target.URL)
require.Error(t, err)
var netErr net.Error
require.True(t, errors.As(err, &netErr), "expected a network timeout, got %v", err)
assert.True(t, netErr.Timeout())
})
})
}
}
4 changes: 3 additions & 1 deletion pkg/gitea/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"code.gitea.io/sdk/gitea"
"github.com/CompassSecurity/pipeleek/pkg/httpclient"
"github.com/rs/zerolog/log"
)

Expand Down Expand Up @@ -58,7 +59,8 @@ func ListAllSecrets(cfg Config) error {
}

func createClientContext(cfg Config) (*clientContext, error) {
client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token))
client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token),
gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient()))
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/gitea/variables/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ func ListAllVariables(cfg Config) error {
}

func createClientContext(cfg Config) (*clientContext, error) {
client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token))
client, err := gitea.NewClient(cfg.URL, gitea.SetToken(cfg.Token),
gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient()))
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion pkg/gitea/vuln/vuln.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import (
// RunCheckVulns checks the Gitea instance for vulnerabilities
func RunCheckVulns(giteaUrl, giteaApiToken string) {
version := "none"
giteaClient, err := gitea.NewClient(giteaUrl, gitea.SetToken(giteaApiToken))
giteaClient, err := gitea.NewClient(giteaUrl, gitea.SetToken(giteaApiToken),
gitea.SetHTTPClient(httpclient.GetPipeleekStandardHTTPClient()))
if err != nil {
log.Warn().Err(err).Msg("Failed creating Gitea client")
} else {
Expand Down
31 changes: 19 additions & 12 deletions tests/e2e/gitea/enum/enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,19 +58,26 @@ func TestGiteaEnum(t *testing.T) {
})
defer cleanup()

stdout, stderr, exitErr := testutil.RunCLI(t, []string{
"gitea", "enum",
"--url", server.URL,
"--token", "gitea-token",
}, nil, 10*time.Second)
for _, mode := range []string{"direct", "explicit proxy"} {
t.Run(mode, func(t *testing.T) {
args := []string{"gitea", "enum", "--token", "gitea-token"}
if mode == "explicit proxy" {
args = append(args, "--url", "http://gitea.invalid", "--proxy", server.URL, "--ignore-proxy")
} else {
args = append(args, "--url", server.URL)
}
before := len(getRequests())
stdout, stderr, exitErr := testutil.RunCLI(t, args, nil, 10*time.Second)

assert.Nil(t, exitErr, "Enum command should succeed")

requests := getRequests()
assert.True(t, len(requests) >= 1, "Should make API requests")

t.Logf("STDOUT:\n%s", stdout)
t.Logf("STDERR:\n%s", stderr)
assert.Nil(t, exitErr, "Enum command should succeed")
assert.Greater(t, len(getRequests()), before, "Should make API requests")
output := stdout + stderr
assert.Contains(t, output, "testuser")
assert.Contains(t, output, "repo1")
assert.Contains(t, output, "repo2")
assert.Contains(t, output, "my-org")
})
}
}

// TestGitea_APIErrors tests various API error responses
44 changes: 22 additions & 22 deletions tests/e2e/gitea/secrets/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,28 +72,28 @@ func TestGiteaSecrets_Success(t *testing.T) {
})
defer cleanup()

stdout, stderr, exitErr := testutil.RunCLI(t, []string{
"gitea", "secrets",
"--url", server.URL,
"--token", "test-token",
}, nil, 10*time.Second)

assert.Nil(t, exitErr, "Secrets command should succeed")

output := stdout + stderr
t.Logf("Output:\n%s", output)

// Verify expected API calls were made
requests := getRequests()
assert.True(t, len(requests) >= 4, "Should make multiple API requests")

// Verify output contains secrets
assert.Contains(t, output, "ORG_SECRET_1", "Should output org secret 1")
assert.Contains(t, output, "ORG_SECRET_2", "Should output org secret 2")
assert.Contains(t, output, "REPO_SECRET_1", "Should output repo secret 1")
assert.Contains(t, output, "REPO_SECRET_2", "Should output repo secret 2")
assert.Contains(t, output, "test-org", "Should output organization name")
assert.Contains(t, output, "test-repo", "Should output repository name")
for _, mode := range []string{"direct", "explicit proxy"} {
t.Run(mode, func(t *testing.T) {
args := []string{"gitea", "secrets", "--token", "test-token"}
if mode == "explicit proxy" {
args = append(args, "--url", "http://gitea.invalid", "--proxy", server.URL, "--ignore-proxy")
} else {
args = append(args, "--url", server.URL)
}
before := len(getRequests())
stdout, stderr, exitErr := testutil.RunCLI(t, args, nil, 10*time.Second)

assert.Nil(t, exitErr, "Secrets command should succeed")
assert.GreaterOrEqual(t, len(getRequests())-before, 4, "Should make multiple API requests")
output := stdout + stderr
assert.Contains(t, output, "ORG_SECRET_1", "Should output org secret 1")
assert.Contains(t, output, "ORG_SECRET_2", "Should output org secret 2")
assert.Contains(t, output, "REPO_SECRET_1", "Should output repo secret 1")
assert.Contains(t, output, "REPO_SECRET_2", "Should output repo secret 2")
assert.Contains(t, output, "test-org", "Should output organization name")
assert.Contains(t, output, "test-repo", "Should output repository name")
})
}
}

func TestGiteaSecrets_OrgPagination(t *testing.T) {
Expand Down
17 changes: 17 additions & 0 deletions tests/e2e/gitea/variables/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,23 @@ func TestGiteaVariables_Success(t *testing.T) {
assert.Contains(t, output, "REPO_VAR_2", "Should output repo variable 2")
assert.Contains(t, output, "test-org", "Should output organization name")
assert.Contains(t, output, "test-repo", "Should output repository name")

t.Run("explicit proxy", func(t *testing.T) {
before := len(getRequests())
stdout, stderr, exitErr := testutil.RunCLI(t, []string{
"gitea", "variables",
"--url", "http://gitea.invalid",
"--token", "test-token",
"--proxy", server.URL,
"--ignore-proxy",
}, nil, 10*time.Second)
assert.Nil(t, exitErr, "Variables command should succeed through the proxy")
assert.GreaterOrEqual(t, len(getRequests())-before, 4)
output := stdout + stderr
for _, value := range []string{"ORG_VAR_1", "ORG_VAR_2", "REPO_VAR_1", "REPO_VAR_2", "org_value_1", "repo_value_1"} {
assert.Contains(t, output, value)
}
})
}

func TestGiteaVariables_Pagination(t *testing.T) {
Expand Down
Loading
Loading