chore: initialize Gitea v1.27.2 fork
giteabot backport / giteabot (push) Canceled after 0s
giteabot / giteabot (push) Canceled after 0s
release-nightly / nightly-binary (push) Canceled after 0s
release-nightly / nightly-container (push) Canceled after 0s
cache-seeder / gobuild (push) Canceled after 0s
cache-seeder / lint (bindata, lint-backend) (push) Canceled after 0s
release-nightly-snapcraft / build-and-publish (push) Canceled after 0s
giteabot backport / giteabot (push) Canceled after 0s
giteabot / giteabot (push) Canceled after 0s
release-nightly / nightly-binary (push) Canceled after 0s
release-nightly / nightly-container (push) Canceled after 0s
cache-seeder / gobuild (push) Canceled after 0s
cache-seeder / lint (bindata, lint-backend) (push) Canceled after 0s
release-nightly-snapcraft / build-and-publish (push) Canceled after 0s
Includes direct password setup links in registration emails. Assisted-by: Codex:GPT-5
This commit is contained in:
@@ -0,0 +1,211 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package hostmatcher
|
||||
|
||||
import (
|
||||
"net"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HostMatchList is used to check if a host or IP is in a list.
|
||||
type HostMatchList struct {
|
||||
SettingKeyHint string
|
||||
SettingValue string
|
||||
|
||||
// builtins networks
|
||||
builtins []string
|
||||
// patterns for host names (with wildcard support)
|
||||
patterns []string
|
||||
// ipNets is the CIDR network list
|
||||
ipNets []*net.IPNet
|
||||
}
|
||||
|
||||
// MatchBuiltinExternal A valid global-unicast IP that is neither private (see MatchBuiltinPrivate)
|
||||
// nor a reserved special-purpose range (see reservedIPNets); i.e. a routable host on the public internet.
|
||||
const MatchBuiltinExternal = "external"
|
||||
|
||||
// reservedIPNets are special-purpose ranges that net.IP.IsPrivate omits but that must not be
|
||||
// treated as public/external destinations (CGNAT, cloud metadata, IPv6 transition, etc.). We layer
|
||||
// these on top of net.IP.IsPrivate (RFC 1918 / RFC 4193) so future additions to Go's IsPrivate are
|
||||
// picked up automatically, while still covering the ranges it leaves out; otherwise the default
|
||||
// allow-list would let authenticated users reach cloud metadata, internal, and IPv6 transition
|
||||
// endpoints (SSRF), and a "private" block-list would fail to catch them.
|
||||
var reservedIPNets = sync.OnceValue(func() []*net.IPNet {
|
||||
var nets []*net.IPNet
|
||||
for _, cidr := range []string{
|
||||
// IPv4
|
||||
"100.64.0.0/10", // RFC 6598 Carrier-Grade NAT
|
||||
"168.63.129.16/32", // Azure WireServer metadata endpoint
|
||||
"192.0.0.0/24", // RFC 6890 IETF protocol assignments
|
||||
"192.0.2.0/24", // RFC 5737 TEST-NET-1
|
||||
"192.88.99.0/24", // RFC 7526 6to4 relay anycast (deprecated)
|
||||
"198.18.0.0/15", // RFC 2544 benchmarking
|
||||
"198.51.100.0/24", // RFC 5737 TEST-NET-2
|
||||
"203.0.113.0/24", // RFC 5737 TEST-NET-3
|
||||
// IPv6
|
||||
"100::/64", // RFC 6666 discard-only
|
||||
"64:ff9b::/96", // RFC 6052 NAT64 (can embed IPv4 such as 169.254.169.254)
|
||||
"64:ff9b:1::/48", // RFC 8215 local-use NAT64
|
||||
"2001::/32", // RFC 4380 Teredo tunneling (embeds IPv4)
|
||||
"2001:10::/28", // RFC 4843 ORCHID (deprecated)
|
||||
"2001:20::/28", // RFC 7343 ORCHIDv2
|
||||
"2001:db8::/32", // RFC 3849 documentation
|
||||
"2002::/16", // RFC 3056 6to4 (embeds IPv4)
|
||||
} {
|
||||
_, ipNet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
panic("hostmatcher: invalid reserved CIDR " + cidr + ": " + err.Error())
|
||||
}
|
||||
nets = append(nets, ipNet)
|
||||
}
|
||||
return nets
|
||||
})
|
||||
|
||||
// isReservedIP reports whether ip falls in reserved special-purpose
|
||||
// range (see reservedIPNets) that must not be considered a public/external destination.
|
||||
func isReservedIP(ip net.IP) bool {
|
||||
for _, ipNet := range reservedIPNets() {
|
||||
if ipNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchBuiltinPrivate RFC 1918 (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and RFC 4193 (FC00::/7),
|
||||
// plus the reserved special-purpose ranges in reservedIPNets (CGNAT, NAT64, cloud metadata, etc.).
|
||||
// Also called LAN/Intranet.
|
||||
const MatchBuiltinPrivate = "private"
|
||||
|
||||
// MatchBuiltinLoopback 127.0.0.0/8 for IPv4 and ::1/128 for IPv6, localhost is included.
|
||||
const MatchBuiltinLoopback = "loopback"
|
||||
|
||||
func isBuiltin(s string) bool {
|
||||
return s == MatchBuiltinExternal || s == MatchBuiltinPrivate || s == MatchBuiltinLoopback
|
||||
}
|
||||
|
||||
// ParseHostMatchList parses the host list HostMatchList
|
||||
func ParseHostMatchList(settingKeyHint, hostList string) *HostMatchList {
|
||||
hl := &HostMatchList{SettingKeyHint: settingKeyHint, SettingValue: hostList}
|
||||
for s := range strings.SplitSeq(hostList, ",") {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
_, ipNet, err := net.ParseCIDR(s)
|
||||
if err == nil {
|
||||
hl.ipNets = append(hl.ipNets, ipNet)
|
||||
} else if isBuiltin(s) {
|
||||
hl.builtins = append(hl.builtins, s)
|
||||
} else {
|
||||
hl.patterns = append(hl.patterns, s)
|
||||
}
|
||||
}
|
||||
return hl
|
||||
}
|
||||
|
||||
// ParseSimpleMatchList parse a simple matchlist (no built-in networks, no CIDR support, only wildcard pattern match)
|
||||
func ParseSimpleMatchList(settingKeyHint, matchList string) *HostMatchList {
|
||||
hl := &HostMatchList{
|
||||
SettingKeyHint: settingKeyHint,
|
||||
SettingValue: matchList,
|
||||
}
|
||||
for s := range strings.SplitSeq(matchList, ",") {
|
||||
s = strings.ToLower(strings.TrimSpace(s))
|
||||
if s == "" {
|
||||
continue
|
||||
}
|
||||
// we keep the same result as old `matchlist`, so no builtin/CIDR support here, we only match wildcard patterns
|
||||
hl.patterns = append(hl.patterns, s)
|
||||
}
|
||||
return hl
|
||||
}
|
||||
|
||||
// AppendBuiltin appends more builtins to match
|
||||
func (hl *HostMatchList) AppendBuiltin(builtin string) {
|
||||
hl.builtins = append(hl.builtins, builtin)
|
||||
}
|
||||
|
||||
// IsEmpty checks if the checklist is empty
|
||||
func (hl *HostMatchList) IsEmpty() bool {
|
||||
return hl == nil || (len(hl.builtins) == 0 && len(hl.patterns) == 0 && len(hl.ipNets) == 0)
|
||||
}
|
||||
|
||||
func (hl *HostMatchList) checkPattern(host string) bool {
|
||||
host = strings.ToLower(strings.TrimSpace(host))
|
||||
for _, pattern := range hl.patterns {
|
||||
if matched, _ := filepath.Match(pattern, host); matched {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// matchesIP determines if the given IP matches any of the configured rules
|
||||
func (hl *HostMatchList) matchesIP(ip net.IP) bool {
|
||||
if slices.Contains(hl.patterns, "*") {
|
||||
return true
|
||||
}
|
||||
for _, builtin := range hl.builtins {
|
||||
switch builtin {
|
||||
case MatchBuiltinExternal:
|
||||
// External address must be a global unicast, must not be in reserved range and must not be in private range
|
||||
if ip.IsGlobalUnicast() && !isReservedIP(ip) && !ip.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
case MatchBuiltinPrivate:
|
||||
// Private address must be global unicast, must not be in range we explicitly exclude for security reasons
|
||||
// and must be in private range
|
||||
if ip.IsGlobalUnicast() && !isReservedIP(ip) && ip.IsPrivate() {
|
||||
return true
|
||||
}
|
||||
case MatchBuiltinLoopback:
|
||||
if ip.IsLoopback() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, ipNet := range hl.ipNets {
|
||||
if ipNet.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchHostName checks if the host matches an allow/deny(block) list
|
||||
func (hl *HostMatchList) MatchHostName(host string) bool {
|
||||
if hl == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
hostname, _, err := net.SplitHostPort(host)
|
||||
if err != nil {
|
||||
hostname = host
|
||||
}
|
||||
if hl.checkPattern(hostname) {
|
||||
return true
|
||||
}
|
||||
if ip := net.ParseIP(hostname); ip != nil {
|
||||
return hl.matchesIP(ip)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// MatchIPAddr checks if the IP matches an allow/deny(block) list, it's safe to pass `nil` to `ip`
|
||||
func (hl *HostMatchList) MatchIPAddr(ip net.IP) bool {
|
||||
if hl == nil {
|
||||
return false
|
||||
}
|
||||
host := ip.String() // nil-safe, we will get "<nil>" if ip is nil
|
||||
return hl.checkPattern(host) || hl.matchesIP(ip)
|
||||
}
|
||||
|
||||
// MatchHostOrIP checks if the host or IP matches an allow/deny(block) list
|
||||
func (hl *HostMatchList) MatchHostOrIP(host string, ip net.IP) bool {
|
||||
return hl.MatchHostName(host) || hl.MatchIPAddr(ip)
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package hostmatcher
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHostOrIPMatchesList(t *testing.T) {
|
||||
type tc struct {
|
||||
host string
|
||||
ip net.IP
|
||||
expected bool
|
||||
}
|
||||
|
||||
// for IPv6: "::1" is loopback, "fd00::/8" is private
|
||||
|
||||
hl := ParseHostMatchList("", "private, External, *.myDomain.com, 169.254.1.0/24")
|
||||
|
||||
test := func(cases []tc) {
|
||||
for _, c := range cases {
|
||||
assert.Equalf(t, c.expected, hl.MatchHostOrIP(c.host, c.ip), "case domain=%s, ip=%v, expected=%v", c.host, c.ip, c.expected)
|
||||
}
|
||||
}
|
||||
|
||||
cases := []tc{
|
||||
{"", net.IPv4zero, false},
|
||||
{"", net.IPv6zero, false},
|
||||
|
||||
{"", net.ParseIP("127.0.0.1"), false},
|
||||
{"127.0.0.1", nil, false},
|
||||
{"", net.ParseIP("::1"), false},
|
||||
|
||||
{"", net.ParseIP("10.0.1.1"), true},
|
||||
{"10.0.1.1", nil, true},
|
||||
{"10.0.1.1:8080", nil, true},
|
||||
{"", net.ParseIP("192.168.1.1"), true},
|
||||
{"192.168.1.1", nil, true},
|
||||
{"", net.ParseIP("fd00::1"), true},
|
||||
{"fd00::1", nil, true},
|
||||
|
||||
{"", net.ParseIP("8.8.8.8"), true},
|
||||
{"", net.ParseIP("1001::1"), true},
|
||||
|
||||
{"mydomain.com", net.IPv4zero, false},
|
||||
{"sub.mydomain.com", net.IPv4zero, true},
|
||||
{"sub.mydomain.com:8080", net.IPv4zero, true},
|
||||
|
||||
{"", net.ParseIP("169.254.1.1"), true},
|
||||
{"169.254.1.1", nil, true},
|
||||
{"", net.ParseIP("169.254.2.2"), false},
|
||||
{"169.254.2.2", nil, false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseHostMatchList("", "loopback")
|
||||
cases = []tc{
|
||||
{"", net.IPv4zero, false},
|
||||
{"", net.ParseIP("127.0.0.1"), true},
|
||||
{"", net.ParseIP("10.0.1.1"), false},
|
||||
{"", net.ParseIP("192.168.1.1"), false},
|
||||
{"", net.ParseIP("8.8.8.8"), false},
|
||||
|
||||
{"", net.ParseIP("::1"), true},
|
||||
{"", net.ParseIP("fd00::1"), false},
|
||||
{"", net.ParseIP("1000::1"), false},
|
||||
|
||||
{"mydomain.com", net.IPv4zero, false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseHostMatchList("", "private")
|
||||
cases = []tc{
|
||||
{"", net.IPv4zero, false},
|
||||
{"", net.ParseIP("127.0.0.1"), false},
|
||||
{"", net.ParseIP("10.0.1.1"), true},
|
||||
{"", net.ParseIP("192.168.1.1"), true},
|
||||
{"", net.ParseIP("8.8.8.8"), false},
|
||||
|
||||
{"", net.ParseIP("::1"), false},
|
||||
{"", net.ParseIP("fd00::1"), true},
|
||||
{"", net.ParseIP("1000::1"), false},
|
||||
|
||||
{"mydomain.com", net.IPv4zero, false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseHostMatchList("", "external")
|
||||
cases = []tc{
|
||||
{"", net.IPv4zero, false},
|
||||
{"", net.ParseIP("127.0.0.1"), false},
|
||||
{"", net.ParseIP("10.0.1.1"), false},
|
||||
{"", net.ParseIP("192.168.1.1"), false},
|
||||
{"", net.ParseIP("8.8.8.8"), true},
|
||||
|
||||
{"", net.ParseIP("::1"), false},
|
||||
{"", net.ParseIP("fd00::1"), false},
|
||||
{"", net.ParseIP("1000::1"), true},
|
||||
|
||||
{"mydomain.com", net.IPv4zero, false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseHostMatchList("", "*")
|
||||
cases = []tc{
|
||||
{"", net.IPv4zero, true},
|
||||
{"", net.ParseIP("127.0.0.1"), true},
|
||||
{"", net.ParseIP("10.0.1.1"), true},
|
||||
{"", net.ParseIP("192.168.1.1"), true},
|
||||
{"", net.ParseIP("8.8.8.8"), true},
|
||||
|
||||
{"", net.ParseIP("::1"), true},
|
||||
{"", net.ParseIP("fd00::1"), true},
|
||||
{"", net.ParseIP("1000::1"), true},
|
||||
|
||||
{"mydomain.com", net.IPv4zero, true},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
// built-in network names can be escaped (warping the first char with `[]`) to be used as a real host name
|
||||
// this mechanism is reversed for internal usage only (maybe for some rare cases), it's not supposed to be used by end users
|
||||
// a real user should never use loopback/private/external as their host names
|
||||
hl = ParseHostMatchList("", "loopback, [p]rivate")
|
||||
cases = []tc{
|
||||
{"loopback", nil, false},
|
||||
{"", net.ParseIP("127.0.0.1"), true},
|
||||
{"private", nil, true},
|
||||
{"", net.ParseIP("192.168.1.1"), false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseSimpleMatchList("", "loopback, *.domain.com")
|
||||
cases = []tc{
|
||||
{"loopback", nil, true},
|
||||
{"", net.ParseIP("127.0.0.1"), false},
|
||||
{"sub.domain.com", nil, true},
|
||||
{"other.com", nil, false},
|
||||
{"", net.ParseIP("1.1.1.1"), false},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseSimpleMatchList("", "external")
|
||||
cases = []tc{
|
||||
{"", net.ParseIP("192.168.1.1"), false},
|
||||
{"", net.ParseIP("1.1.1.1"), false},
|
||||
{"external", nil, true},
|
||||
}
|
||||
test(cases)
|
||||
|
||||
hl = ParseSimpleMatchList("", "")
|
||||
cases = []tc{
|
||||
{"", net.ParseIP("192.168.1.1"), false},
|
||||
{"", net.ParseIP("1.1.1.1"), false},
|
||||
{"external", nil, false},
|
||||
}
|
||||
test(cases)
|
||||
}
|
||||
|
||||
// TestReservedRanges ensures special-purpose ranges that net.IP.IsPrivate misses are kept out of the
|
||||
// "external" allow-list (the default for webhook delivery and repository migrations) and folded into
|
||||
// the "private" block-list, so they cannot be used for SSRF to metadata/internal endpoints.
|
||||
func TestReservedRanges(t *testing.T) {
|
||||
external := ParseHostMatchList("", "external")
|
||||
private := ParseHostMatchList("", "private")
|
||||
|
||||
// legitimate public destinations: external, not private
|
||||
for _, ip := range []string{"8.8.8.8", "1.1.1.1", "2001:4860:4860::8888", "1000::1"} {
|
||||
addr := net.ParseIP(ip)
|
||||
assert.Truef(t, external.MatchIPAddr(addr), "public ip %s should be external", ip)
|
||||
assert.Falsef(t, private.MatchIPAddr(addr), "public ip %s should not be private", ip)
|
||||
}
|
||||
|
||||
// RFC 1918 / RFC 4193 private ranges (now folded into privateIPNets instead of net.IP.IsPrivate):
|
||||
// not external, blockable as private. Includes range edges to guard the CIDR boundaries.
|
||||
for _, ip := range []string{
|
||||
"10.0.0.0", "10.255.255.255", // 10.0.0.0/8
|
||||
"172.16.0.0", "172.31.255.255", // 172.16.0.0/12
|
||||
"192.168.0.0", "192.168.255.255", // 192.168.0.0/16
|
||||
"fc00::", "fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", // fc00::/7
|
||||
} {
|
||||
addr := net.ParseIP(ip)
|
||||
assert.Falsef(t, external.MatchIPAddr(addr), "private ip %s must not be external", ip)
|
||||
assert.Truef(t, private.MatchIPAddr(addr), "private ip %s should match private block-list", ip)
|
||||
}
|
||||
|
||||
// 172.32.0.0 is just outside 172.16.0.0/12: a public destination, not private
|
||||
if addr := net.ParseIP("172.32.0.0"); assert.NotNil(t, addr) {
|
||||
assert.True(t, external.MatchIPAddr(addr), "172.32.0.0 should be external")
|
||||
assert.False(t, private.MatchIPAddr(addr), "172.32.0.0 should not be private")
|
||||
}
|
||||
|
||||
// reserved ranges that IsPrivate does not cover: not external, but blockable as private
|
||||
for _, ip := range []string{
|
||||
"100.64.0.1", // CGNAT
|
||||
"100.127.255.254", // CGNAT
|
||||
"168.63.129.16", // Azure WireServer
|
||||
"192.0.2.1", // TEST-NET-1
|
||||
"198.18.0.1", // benchmarking
|
||||
"198.51.100.1", // TEST-NET-2
|
||||
"203.0.113.1", // TEST-NET-3
|
||||
"169.254.169.254", // Cloud metadata
|
||||
"192.88.99.1", // 6to4 relay anycast
|
||||
"64:ff9b::1", // NAT64
|
||||
"64:ff9b::a9fe:a9fe", // NAT64 embedding 169.254.169.254
|
||||
"2001::1", // Teredo
|
||||
"2002::1", // 6to4
|
||||
"2001:db8::1", // documentation
|
||||
"fe80::1", // link local address
|
||||
} {
|
||||
addr := net.ParseIP(ip)
|
||||
assert.Falsef(t, external.MatchIPAddr(addr), "reserved ip %s must not be external", ip)
|
||||
assert.Falsef(t, private.MatchIPAddr(addr), "reserved ip %s should match private block-list", ip)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
// Copyright 2021 The Gitea Authors. All rights reserved.
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package hostmatcher
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
// NewDialContext returns a DialContext for Transport, the DialContext will do allow/block list check
|
||||
func NewDialContext(usage string, allowList, blockList *HostMatchList, proxy *url.URL) func(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||
// How Go HTTP Client works with redirection:
|
||||
// transport.RoundTrip URL=http://domain.com, Host=domain.com
|
||||
// transport.DialContext addrOrHost=domain.com:80
|
||||
// dialer.Control tcp4:11.22.33.44:80
|
||||
// transport.RoundTrip URL=http://www.domain.com/, Host=(empty here, in the direction, HTTP client doesn't fill the Host field)
|
||||
// transport.DialContext addrOrHost=domain.com:80
|
||||
// dialer.Control tcp4:11.22.33.44:80
|
||||
return func(ctx context.Context, network, addrOrHost string) (net.Conn, error) {
|
||||
dialer := net.Dialer{
|
||||
// default values comes from http.DefaultTransport
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
|
||||
Control: func(network, ipAddr string, c syscall.RawConn) error {
|
||||
host, port, err := net.SplitHostPort(addrOrHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if proxy != nil {
|
||||
// Always allow the host of the proxy, but only on the specified port.
|
||||
if host == proxy.Hostname() && port == proxy.Port() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// in Control func, the addr was already resolved to IP:PORT format, there is no cost to do ResolveTCPAddr here
|
||||
tcpAddr, err := net.ResolveTCPAddr(network, ipAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s can only call HTTP servers via TCP, deny '%s(%s:%s)', err=%w", usage, host, network, ipAddr, err)
|
||||
}
|
||||
|
||||
var blockedError error
|
||||
if blockList.MatchHostOrIP(host, tcpAddr.IP) {
|
||||
blockedError = fmt.Errorf("%s can not call blocked HTTP servers (check your %s setting), deny '%s(%s)'", usage, blockList.SettingKeyHint, host, ipAddr)
|
||||
}
|
||||
|
||||
// if we have an allow-list, check the allow-list first
|
||||
if !allowList.IsEmpty() {
|
||||
if !allowList.MatchHostOrIP(host, tcpAddr.IP) {
|
||||
return fmt.Errorf("%s can only call allowed HTTP servers (check your %s setting), deny '%s(%s)'", usage, allowList.SettingKeyHint, host, ipAddr)
|
||||
}
|
||||
}
|
||||
// otherwise, we always follow the blocked list
|
||||
return blockedError
|
||||
},
|
||||
}
|
||||
return dialer.DialContext(ctx, network, addrOrHost)
|
||||
}
|
||||
}
|
||||
|
||||
// NewHTTPTransport builds an http.Transport that validates the request target against the allow/block
|
||||
// lists on the direct-dial path (DialContext). When an HTTP proxy is configured the proxy resolves and
|
||||
// dials the target itself, so restricting the proxied target is the proxy server's responsibility, not
|
||||
// Gitea's. proxyFunc selects the proxy URL per request (the http.Transport.Proxy selector, e.g.
|
||||
// proxy.Proxy()); proxyURLFixed is the fixed proxy address the dialer must always permit; tlsConfig may
|
||||
// be nil. blockList may be nil for callers that only maintain an allow-list.
|
||||
func NewHTTPTransport(usage string, allowList, blockList *HostMatchList, proxyFunc func(*http.Request) (*url.URL, error), proxyURLFixed *url.URL, tlsConfig *tls.Config) *http.Transport {
|
||||
return &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
Proxy: proxyFunc,
|
||||
DialContext: NewDialContext(usage, allowList, blockList, proxyURLFixed),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user