mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-01 20:59:45 +09:00
1b1274486c
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>
118 lines
3.6 KiB
Go
118 lines
3.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package egress
|
|
|
|
import (
|
|
"cmp"
|
|
"errors"
|
|
"fmt"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"strings"
|
|
|
|
"gitea.dev/modules/egress/policy"
|
|
"gitea.dev/modules/proxy"
|
|
"gitea.dev/modules/setting"
|
|
|
|
"golang.org/x/net/http/httpproxy"
|
|
)
|
|
|
|
func NewMigrationPolicy() *policy.Policy {
|
|
return newMigrationPolicy(proxy.Proxy())
|
|
}
|
|
|
|
// NewGitPolicy is the migration policy for the git proxy, which keeps git's own proxy choice
|
|
func NewGitPolicy() (*policy.Policy, error) {
|
|
selectProxy, err := gitProxySelector()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return newMigrationPolicy(selectProxy), nil
|
|
}
|
|
|
|
// gitProxySelector picks proxies like git did: [git.config] http.proxy, else a [proxy] PROXY_URL, else the environment incl. ALL_PROXY
|
|
func gitProxySelector() (func(*http.Request) (*url.URL, error), error) {
|
|
env := httpproxy.FromEnvironment()
|
|
if rawURL, ok := setting.GitConfig.Options["http.proxy"]; ok {
|
|
gitProxy, err := normalizeGitProxy(rawURL)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("[git.config] http.proxy: %w", err)
|
|
}
|
|
env.HTTPProxy, env.HTTPSProxy = gitProxy, gitProxy
|
|
return requestProxy(env), nil
|
|
}
|
|
if setting.Proxy.Enabled && setting.Proxy.ProxyURL != "" {
|
|
return proxy.Proxy(), nil
|
|
}
|
|
allProxy := cmp.Or(os.Getenv("all_proxy"), os.Getenv("ALL_PROXY"))
|
|
env.HTTPProxy, env.HTTPSProxy = cmp.Or(env.HTTPProxy, allProxy), cmp.Or(env.HTTPSProxy, allProxy)
|
|
return requestProxy(env), nil
|
|
}
|
|
|
|
func requestProxy(cfg *httpproxy.Config) func(*http.Request) (*url.URL, error) {
|
|
proxyFunc := cfg.ProxyFunc()
|
|
return func(req *http.Request) (*url.URL, error) { return proxyFunc(req.URL) }
|
|
}
|
|
|
|
// normalizeGitProxy reads a proxy URL the way git reads http.proxy: http is the default scheme, 1080 curl's default port
|
|
func normalizeGitProxy(rawURL string) (string, error) {
|
|
if rawURL == "" {
|
|
return "", nil
|
|
}
|
|
if !strings.Contains(rawURL, "://") {
|
|
rawURL = "http://" + rawURL
|
|
}
|
|
proxyURL, err := url.Parse(rawURL)
|
|
if err != nil {
|
|
return "", errors.New("invalid URL") // the parse error would echo its credentials
|
|
}
|
|
if proxyURL.Scheme == "http" && proxyURL.Port() == "" {
|
|
proxyURL.Host = net.JoinHostPort(proxyURL.Hostname(), "1080")
|
|
}
|
|
return proxyURL.String(), nil
|
|
}
|
|
|
|
func newMigrationPolicy(selectProxy func(*http.Request) (*url.URL, error)) *policy.Policy {
|
|
return policy.NewPolicy("migrations", policyMode(setting.Migrations.EgressMode),
|
|
policy.WithAllow(setting.Migrations.AllowedHostList, "migrations.ALLOWED_HOST_LIST"),
|
|
policy.WithBlock(setting.Migrations.BlockedHostList, "migrations.BLOCKED_HOST_LIST"),
|
|
policy.WithLocalNeedsIPAllow(),
|
|
policy.WithProxy(selectProxy))
|
|
}
|
|
|
|
func NewWebhookPolicy() *policy.Policy {
|
|
var p *policy.Policy
|
|
selectProxy := proxy.WebHookProxy()
|
|
if webhookProxy := setting.Webhook.ProxyURLFixed; webhookProxy != nil {
|
|
next := selectProxy
|
|
selectProxy = func(req *http.Request) (*url.URL, error) {
|
|
u, err := next(req)
|
|
if err == nil && u == webhookProxy {
|
|
err = p.CheckHost(req.URL) // the webhook proxy resolves the target, so only its name can be checked
|
|
}
|
|
return u, err
|
|
}
|
|
}
|
|
p = policy.NewPolicy("webhook", policyMode(setting.Security.EgressMode),
|
|
policy.WithAllow(setting.Webhook.AllowedHostList, "security.ALLOWED_HOST_LIST"),
|
|
policy.WithProxy(selectProxy))
|
|
|
|
return p
|
|
}
|
|
|
|
func NewSecurityPolicy(usage string) *policy.Policy {
|
|
return policy.NewPolicy(usage, policyMode(setting.Security.EgressMode),
|
|
policy.WithAllow(setting.Security.AllowedHostList, "security.ALLOWED_HOST_LIST"),
|
|
policy.WithProxy(proxy.Proxy()))
|
|
}
|
|
|
|
func policyMode(mode string) policy.Mode {
|
|
if mode == "strict" {
|
|
return policy.Strict
|
|
}
|
|
return policy.Lax
|
|
}
|