mirror of
https://github.com/go-gitea/gitea.git
synced 2026-10-21 09:06:46 +09:00
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>
125 lines
4.6 KiB
Go
125 lines
4.6 KiB
Go
// Copyright 2026 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package egress
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"gitea.dev/modules/egress/policy"
|
|
"gitea.dev/modules/setting"
|
|
"gitea.dev/modules/test"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func mustURL(t *testing.T, raw string) *url.URL {
|
|
t.Helper()
|
|
u, err := url.Parse(raw)
|
|
require.NoError(t, err)
|
|
return u
|
|
}
|
|
|
|
func TestNewMigrationPolicy(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Migrations)()
|
|
for _, tc := range []struct {
|
|
allow, block, mode, target string
|
|
want bool
|
|
}{
|
|
{target: "https://1.2.3.4", want: true},
|
|
{allow: "github.com", target: "https://10.0.0.1"}, // a hostname allow doesn't cover a private IP
|
|
{allow: "github.com", target: "https://8.8.8.8", want: true}, // lax exempts public targets
|
|
{allow: "github.com", mode: "strict", target: "https://8.8.8.8"},
|
|
{block: "8.8.0.0/16", target: "https://8.8.8.8"},
|
|
} {
|
|
setting.Migrations.AllowedHostList, setting.Migrations.BlockedHostList, setting.Migrations.EgressMode = tc.allow, tc.block, tc.mode
|
|
u, err := url.Parse(tc.target)
|
|
require.NoError(t, err)
|
|
err = NewMigrationPolicy().CheckHostIPs(u)
|
|
assert.Equal(t, tc.want, err == nil, "%+v: %v", tc, err)
|
|
}
|
|
}
|
|
|
|
func TestWebhookPolicyProxy(t *testing.T) {
|
|
proxyURL := &url.URL{Scheme: "http", Host: "localhost:8080"}
|
|
defer test.MockVariableValue(&setting.Security.EgressMode, "lax")()
|
|
defer test.MockVariableValue(&setting.Webhook.AllowedHostList, "discordapp.com,s.discordapp.com")()
|
|
defer test.MockVariableValue(&setting.Webhook.ProxyURL, proxyURL.String())()
|
|
defer test.MockVariableValue(&setting.Webhook.ProxyURLFixed, proxyURL)()
|
|
defer test.MockVariableValue(&setting.Webhook.ProxyHosts, []string{"*.discordapp.com", "discordapp.com"})()
|
|
selectProxy := NewWebhookPolicy().NewHTTPTransport().Proxy
|
|
|
|
for target, want := range map[string]string{
|
|
"https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx": proxyURL.String(),
|
|
"http://s.discordapp.com/assets/xxxxxx": proxyURL.String(),
|
|
"http://github.com/a/b": "",
|
|
"http://www.discordapp.com/assets/xxxxxx": proxyURL.String(),
|
|
} {
|
|
req, err := http.NewRequest(http.MethodPost, target, nil)
|
|
require.NoError(t, err)
|
|
req.Host = ""
|
|
u, err := selectProxy(req)
|
|
require.NoError(t, err, target)
|
|
if want == "" {
|
|
assert.Nil(t, u, target)
|
|
} else {
|
|
assert.Equal(t, want, u.String(), target)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestWebhookPolicyNeedsIPAllow(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Webhook.AllowedHostList, "localhost")()
|
|
defer test.MockVariableValue(&setting.Security.EgressMode, "lax")()
|
|
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() { _ = ln.Close() })
|
|
dial := func() error {
|
|
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
|
|
require.True(t, ok)
|
|
target := net.JoinHostPort("localhost", strconv.Itoa(tcpAddr.Port))
|
|
conn, err := NewWebhookPolicy().NewDialContext()(t.Context(), "tcp", target)
|
|
if err == nil {
|
|
_ = conn.Close()
|
|
}
|
|
return err
|
|
}
|
|
assert.ErrorIs(t, dial(), policy.ErrDenied) // a host name entry doesn't cover the loopback address
|
|
setting.Webhook.AllowedHostList = "loopback"
|
|
assert.NoError(t, dial()) // an IP entry does
|
|
}
|
|
|
|
func TestSecurityPolicy(t *testing.T) {
|
|
defer test.MockVariableValue(&setting.Security.AllowedHostList, "avatars.example.com")()
|
|
defer test.MockVariableValue(&setting.Security.EgressMode, "lax")()
|
|
lax := NewSecurityPolicy("test")
|
|
assert.NoError(t, lax.CheckHost(mustURL(t, "https://avatars.example.com")))
|
|
assert.NoError(t, lax.CheckHost(mustURL(t, "https://8.8.8.8"))) // lax exempts public targets
|
|
assert.Error(t, lax.CheckHost(mustURL(t, "https://10.0.0.1"))) // restricted targets still need an allow entry
|
|
|
|
setting.Security.EgressMode = "strict"
|
|
strict := NewSecurityPolicy("test")
|
|
assert.NoError(t, strict.CheckHost(mustURL(t, "https://avatars.example.com")))
|
|
assert.Error(t, strict.CheckHost(mustURL(t, "https://8.8.8.8")))
|
|
}
|
|
|
|
func TestNewGitPolicy(t *testing.T) {
|
|
gitConfig := map[string]string{"http.proxy": "proxy.corp"}
|
|
defer test.MockVariableValue(&setting.GitConfig.Options, gitConfig)()
|
|
gitPolicy, err := NewGitPolicy()
|
|
require.NoError(t, err)
|
|
selected, err := gitPolicy.Proxy(&http.Request{URL: &url.URL{Scheme: "https", Host: "git.example.com"}})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "http://proxy.corp:1080", selected.String())
|
|
|
|
gitConfig["http.proxy"] = "http://user:secret@[::1"
|
|
_, err = NewGitPolicy()
|
|
require.Error(t, err)
|
|
assert.NotContains(t, err.Error(), "secret")
|
|
}
|