Files
gitea/modules/egress/policies.go
T
TheFox0x7 1b1274486c 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>
2026-09-29 14:43:36 +02:00

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
}