aboutsummaryrefslogtreecommitdiffstats
path: root/services
diff options
context:
space:
mode:
authorGiteabot <teabot@gitea.io>2023-10-18 21:07:20 +0800
committerGitHub <noreply@github.com>2023-10-18 15:07:20 +0200
commitca4418eff12d92a4da29bba4331451bf6cd0b620 (patch)
treeca00e6c5ce55ba1c0eb3c78f0680cae27ca537f1 /services
parent80c0c8815203128703eae741e712289393458687 (diff)
downloadgitea-ca4418eff12d92a4da29bba4331451bf6cd0b620.tar.gz
gitea-ca4418eff12d92a4da29bba4331451bf6cd0b620.zip
Support allowed hosts for webhook to work with proxy (#27655) (#27674)
Backport #27655 by @wolfogre When `webhook.PROXY_URL` has been set, the old code will check if the proxy host is in `ALLOWED_HOST_LIST` or reject requests through the proxy. It requires users to add the proxy host to `ALLOWED_HOST_LIST`. However, it actually allows all requests to any port on the host, when the proxy host is probably an internal address. But things may be even worse. `ALLOWED_HOST_LIST` doesn't really work when requests are sent to the allowed proxy, and the proxy could forward them to any hosts. This PR fixes it by: - If the proxy has been set, always allow connectioins to the host and port. - Check `ALLOWED_HOST_LIST` before forwarding. Co-authored-by: Jason Song <i@wolfogre.com>
Diffstat (limited to 'services')
-rw-r--r--services/webhook/deliver.go9
-rw-r--r--services/webhook/deliver_test.go67
2 files changed, 58 insertions, 18 deletions
diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go
index fd7a3d7fba..176ba83e67 100644
--- a/services/webhook/deliver.go
+++ b/services/webhook/deliver.go
@@ -239,7 +239,7 @@ var (
hostMatchers []glob.Glob
)
-func webhookProxy() func(req *http.Request) (*url.URL, error) {
+func webhookProxy(allowList *hostmatcher.HostMatchList) func(req *http.Request) (*url.URL, error) {
if setting.Webhook.ProxyURL == "" {
return proxy.Proxy()
}
@@ -257,6 +257,9 @@ func webhookProxy() func(req *http.Request) (*url.URL, error) {
return func(req *http.Request) (*url.URL, error) {
for _, v := range hostMatchers {
if v.Match(req.URL.Host) {
+ if !allowList.MatchHostName(req.URL.Host) {
+ return nil, fmt.Errorf("webhook can only call allowed HTTP servers (check your %s setting), deny '%s'", allowList.SettingKeyHint, req.URL.Host)
+ }
return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req)
}
}
@@ -278,8 +281,8 @@ func Init() error {
Timeout: timeout,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
- Proxy: webhookProxy(),
- DialContext: hostmatcher.NewDialContext("webhook", allowedHostMatcher, nil),
+ Proxy: webhookProxy(allowedHostMatcher),
+ DialContext: hostmatcher.NewDialContextWithProxy("webhook", allowedHostMatcher, nil, setting.Webhook.ProxyURLFixed),
},
}
diff --git a/services/webhook/deliver_test.go b/services/webhook/deliver_test.go
index ee63975ad3..72aa00478a 100644
--- a/services/webhook/deliver_test.go
+++ b/services/webhook/deliver_test.go
@@ -14,35 +14,72 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
webhook_model "code.gitea.io/gitea/models/webhook"
+ "code.gitea.io/gitea/modules/hostmatcher"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
webhook_module "code.gitea.io/gitea/modules/webhook"
"github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
)
func TestWebhookProxy(t *testing.T) {
+ oldWebhook := setting.Webhook
+ t.Cleanup(func() {
+ setting.Webhook = oldWebhook
+ })
+
setting.Webhook.ProxyURL = "http://localhost:8080"
setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}
- kases := map[string]string{
- "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx": "http://localhost:8080",
- "http://s.discordapp.com/assets/xxxxxx": "http://localhost:8080",
- "http://github.com/a/b": "",
+ allowedHostMatcher := hostmatcher.ParseHostMatchList("webhook.ALLOWED_HOST_LIST", "discordapp.com,s.discordapp.com")
+
+ tests := []struct {
+ req string
+ want string
+ wantErr bool
+ }{
+ {
+ req: "https://discordapp.com/api/webhooks/xxxxxxxxx/xxxxxxxxxxxxxxxxxxx",
+ want: "http://localhost:8080",
+ wantErr: false,
+ },
+ {
+ req: "http://s.discordapp.com/assets/xxxxxx",
+ want: "http://localhost:8080",
+ wantErr: false,
+ },
+ {
+ req: "http://github.com/a/b",
+ want: "",
+ wantErr: false,
+ },
+ {
+ req: "http://www.discordapp.com/assets/xxxxxx",
+ want: "",
+ wantErr: true,
+ },
}
+ for _, tt := range tests {
+ t.Run(tt.req, func(t *testing.T) {
+ req, err := http.NewRequest("POST", tt.req, nil)
+ require.NoError(t, err)
+
+ u, err := webhookProxy(allowedHostMatcher)(req)
+ if tt.wantErr {
+ assert.Error(t, err)
+ return
+ }
+
+ assert.NoError(t, err)
- for reqURL, proxyURL := range kases {
- req, err := http.NewRequest("POST", reqURL, nil)
- assert.NoError(t, err)
-
- u, err := webhookProxy()(req)
- assert.NoError(t, err)
- if proxyURL == "" {
- assert.Nil(t, u)
- } else {
- assert.EqualValues(t, proxyURL, u.String())
- }
+ got := ""
+ if u != nil {
+ got = u.String()
+ }
+ assert.Equal(t, tt.want, got)
+ })
}
}