Files
gitea/modules/egress/policies.go
T
TheFox0x7andwxiaoguang bd2a6c40d7 fix(egress): expose more ranges as restricted rather than reserved (#39560)
Introduce second list of addresses which are classified as dialable if
explicitly allowed when in Lax mode.
Restricted pool now includes: link-local, site local, private (including
ULA), CGNAT, discard, dummy, documentation and test addreses.
Reserved pool shrinks to: this network, wireserver embedding/translation
ranges and multicasts

Rationale for the choice is that while items in restricted pool can be
dangerous to allow they could be a legitimate target in some
deployments. Ranges left in reserved list are ranges which make no sense
to dial, are public (wireserver) or are 6to4 embedding which cannot be
reasonably verified to be safe. To unlock those a proxy should be used
instead

fixes: https://github.com/go-gitea/gitea/issues/39557

---------

Signed-off-by: TheFox0x7 <thefox0x7@gmail.com>
Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
2026-10-06 14:36:06 +08:00

120 lines
3.7 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.WithLocalNeedsIPAllow(),
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.WithLocalNeedsIPAllow(),
policy.WithProxy(proxy.Proxy()))
}
func policyMode(mode string) policy.Mode {
if mode == "strict" {
return policy.Strict
}
return policy.Lax
}