mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-02 04:35:53 +00:00
fix(git)!: use internal proxy for all git operations (#39426)
Introduces gitproxy module which spawns a small forward proxy as scanner for git calls Replaces hostmatcher with matchlist which supports port rules Deprecates ALLOWED_DOMAINS/BLOCKED_DOMAINS and ALLOW_LOCALNETWORKS settings in migration in favor of full names we have in security configs. Removes `external` preset in favor of lax/strict modes, strict mode requiring explicit ports if they aren't standard http/s ones. Breaking changes: - `external` preset no longer works as deny rule. To enforce that, use `strict` mode and allow ranges to connect to - Wildcards are no longer accepted in IP addresses - `*` is no longer allowed as entry in lists - domain rules now use curl like syntax `*.example.com` matching subdomains but not `example.com`, `example.com` matching itself and all subdomains. `example.*` is not a valid rule - In the default `lax` mode, `[security] ALLOWED_HOST_LIST` no longer restricts public hosts, set `EGRESS_MODE = strict` to keep an exclusive list. A startup warning flags this - Invalid list entries are logged at startup, invalid `BLOCKED_HOST_LIST`/`BLOCKED_DOMAINS` entries stop it Docs: https://gitea.com/gitea/docs/pulls/557 Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: bircni <bircni@icloud.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
@@ -255,10 +255,14 @@ func commonBaseEnvs() []string {
|
||||
|
||||
// CommonGitCmdEnvs returns the common environment variables for a "git" command.
|
||||
func CommonGitCmdEnvs() []string {
|
||||
return append(commonBaseEnvs(), []string{
|
||||
envs := append(commonBaseEnvs(), []string{
|
||||
"LC_ALL=C", // ensure git output is in English, error messages are parsed in English
|
||||
"GIT_TERMINAL_PROMPT=0", // avoid prompting for credentials interactively, supported since git v2.3
|
||||
}...)
|
||||
if extra := extraEnvs.Load(); extra != nil {
|
||||
envs = append(envs, *extra...)
|
||||
}
|
||||
return envs
|
||||
}
|
||||
|
||||
// CommonCmdServEnvs is like CommonGitCmdEnvs, but it only returns minimal required environment variables for the "gitea serv" command
|
||||
|
||||
@@ -6,6 +6,7 @@ package gitcmd
|
||||
import (
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sync/atomic"
|
||||
|
||||
"gitea.dev/modules/log"
|
||||
"gitea.dev/modules/setting"
|
||||
@@ -13,6 +14,13 @@ import (
|
||||
|
||||
var GitExecutable = "git" // the command name of git, will be updated to an absolute path during initialization
|
||||
|
||||
var extraEnvs atomic.Pointer[[]string]
|
||||
|
||||
// SetExtraEnvs adds envs to every git command, the git proxy routes git's network remotes with them
|
||||
func SetExtraEnvs(envs []string) {
|
||||
extraEnvs.Store(&envs)
|
||||
}
|
||||
|
||||
// SetExecutablePath changes the path of git executable and checks the file permission and version.
|
||||
func SetExecutablePath(path string) error {
|
||||
// If path is empty, we use the default value of GitExecutable "git" to search for the location of git.
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
// Copyright 2026 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package git
|
||||
|
||||
import "gitea.dev/modules/git/gitcmd"
|
||||
|
||||
func HandleGitCmdHTTPRedirection(cmd *gitcmd.Command, targets ...string) {
|
||||
// Protect from SSRF vector (e.g. migrating from an attacker URL).
|
||||
// cmd.AddConfig("http.followRedirects", "false")
|
||||
// However, we can't do so at the moment:
|
||||
// this fails due to 301: git -c http.followRedirects=false clone -v https://gitlab.com/{owner}/{repo}
|
||||
// this succeeds: git -c http.followRedirects=false clone -v https://gitlab.com/{owner}/{repo}.git
|
||||
// FIXME: GIT-CLONE-HTTP-REDIRECT-SSRF: need a complete solution in the future
|
||||
}
|
||||
+1
-17
@@ -7,7 +7,6 @@ package git
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
@@ -19,7 +18,6 @@ import (
|
||||
"gitea.dev/modules/cache"
|
||||
"gitea.dev/modules/git/gitcmd"
|
||||
"gitea.dev/modules/git/gitrepo"
|
||||
"gitea.dev/modules/proxy"
|
||||
"gitea.dev/modules/setting"
|
||||
"gitea.dev/modules/util"
|
||||
)
|
||||
@@ -183,7 +181,6 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
|
||||
}
|
||||
|
||||
cmd := gitcmd.NewCommand().AddArguments("clone")
|
||||
HandleGitCmdHTTPRedirection(cmd, from, to)
|
||||
if opts.SkipTLSVerify {
|
||||
cmd.AddArguments("-c", "http.sslVerify=false")
|
||||
}
|
||||
@@ -220,20 +217,7 @@ func Clone(ctx context.Context, from, to string, opts CloneRepoOptions) error {
|
||||
opts.Timeout = -1
|
||||
}
|
||||
|
||||
envs := os.Environ()
|
||||
if opts.Env != nil {
|
||||
envs = opts.Env
|
||||
} else {
|
||||
u, err := url.Parse(from)
|
||||
if err == nil {
|
||||
envs = proxy.EnvWithProxy(u)
|
||||
}
|
||||
}
|
||||
|
||||
return cmd.
|
||||
WithTimeout(opts.Timeout).
|
||||
WithEnv(envs).
|
||||
RunWithStderr(ctx)
|
||||
return cmd.WithTimeout(opts.Timeout).WithEnv(opts.Env).RunWithStderr(ctx)
|
||||
}
|
||||
|
||||
// PushOptions options when push to remote
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
package git
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"path/filepath"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
@@ -22,24 +19,3 @@ func TestRepoIsEmpty(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, isEmpty)
|
||||
}
|
||||
|
||||
// TestCloneRefusesRedirects ensures Clone never follows HTTP redirects, so a remote
|
||||
// cannot redirect to an otherwise-blocked address (SSRF, e.g. during migration).
|
||||
func TestCloneRefusesRedirects(t *testing.T) {
|
||||
t.Skip("FIXME: GIT-CLONE-HTTP-REDIRECT-SSRF: need a complete solution in the future")
|
||||
var targetHit atomic.Bool
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
targetHit.Store(true)
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
redirect := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, target.URL+r.URL.Path, http.StatusFound)
|
||||
}))
|
||||
defer redirect.Close()
|
||||
|
||||
err := Clone(t.Context(), redirect.URL, filepath.Join(t.TempDir(), "dst"), CloneRepoOptions{})
|
||||
assert.Error(t, err)
|
||||
assert.False(t, targetHit.Load(), "git must not follow the redirect to the target")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user