Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webhook support custom proxy #8760

Merged
merged 6 commits into from
Nov 8, 2019
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom/conf/app.ini.sample
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,10 @@ DELIVER_TIMEOUT = 5
SKIP_TLS_VERIFY = false
; Number of history information in each page
PAGING_NUM = 10
; Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy
PROXY_URL =
; Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.
PROXY_HOSTS =
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


[mailer]
ENABLED = false
Expand Down
2 changes: 2 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.en-us.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,8 @@ relation to port exhaustion.
- `DELIVER_TIMEOUT`: **5**: Delivery timeout (sec) for shooting webhooks.
- `SKIP_TLS_VERIFY`: **false**: Allow insecure certification.
- `PAGING_NUM`: **10**: Number of webhook history events that are shown in one page.
- `PROXY_URL`: ****: Proxy server URL, support http://, https//, socks://, blank will follow environment http_proxy/https_proxy
- `PROXY_HOSTS`: ****: Comma separated list of host names requiring proxy. Glob patterns (*) are accepted; use ** to match all hosts.

## Mailer (`mailer`)

Expand Down
2 changes: 2 additions & 0 deletions docs/content/doc/advanced/config-cheat-sheet.zh-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ menu:
- `DELIVER_TIMEOUT`: 请求webhooks的超时时间,单位秒。
- `SKIP_TLS_VERIFY`: 是否允许不安全的证书。
- `PAGING_NUM`: 每页显示的Webhook 历史数量。
- `PROXY_URL`: ****: 代理服务器网址,支持 http://, https//, socks://, 为空将使用环境变量中的 http_proxy/https_proxy 设置。
- `PROXY_HOSTS`: ****: 逗号分隔的需要代理的域名或IP地址。支持 * 号匹配符,使用 ** 匹配所有域名和IP地址。

## Mailer (`mailer`)

Expand Down
21 changes: 21 additions & 0 deletions modules/setting/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@

package setting

import (
"net/url"

"code.gitea.io/gitea/modules/log"
)

var (
// Webhook settings
Webhook = struct {
Expand All @@ -12,11 +18,16 @@ var (
SkipTLSVerify bool
Types []string
PagingNum int
ProxyURL string
ProxyURLFixed *url.URL
ProxyHosts []string
}{
QueueLength: 1000,
DeliverTimeout: 5,
SkipTLSVerify: false,
PagingNum: 10,
ProxyURL: "",
ProxyHosts: []string{},
}
)

Expand All @@ -27,4 +38,14 @@ func newWebhookService() {
Webhook.SkipTLSVerify = sec.Key("SKIP_TLS_VERIFY").MustBool()
Webhook.Types = []string{"gitea", "gogs", "slack", "discord", "dingtalk", "telegram", "msteams"}
Webhook.PagingNum = sec.Key("PAGING_NUM").MustInt(10)
Webhook.ProxyURL = sec.Key("PROXY_URL").MustString("")
if Webhook.ProxyURL != "" {
var err error
Webhook.ProxyURLFixed, err = url.Parse(Webhook.ProxyURL)
if err != nil {
log.Error("Webhook PROXY_URL is not valid")
Webhook.ProxyURL = ""
}
}
Webhook.ProxyHosts = sec.Key("PROXY_HOSTS").Strings(",")
}
36 changes: 33 additions & 3 deletions modules/webhook/deliver.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,13 @@ import (
"net/http"
"net/url"
"strings"
"sync"
"time"

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"github.com/gobwas/glob"
"github.com/unknwon/com"
)

Expand Down Expand Up @@ -182,7 +184,36 @@ func DeliverHooks() {
}
}

var webhookHTTPClient *http.Client
var (
webhookHTTPClient *http.Client
once sync.Once
hostMatchers []glob.Glob
)

func webhookProxy() func(req *http.Request) (*url.URL, error) {
if setting.Webhook.ProxyURL == "" {
return http.ProxyFromEnvironment
}

once.Do(func() {
for _, h := range setting.Webhook.ProxyHosts {
if g, err := glob.Compile(h); err == nil {
hostMatchers = append(hostMatchers, g)
} else {
log.Error("glob.Compile %s failed: %v", h, err)
}
}
})

return func(req *http.Request) (*url.URL, error) {
for _, v := range hostMatchers {
if v.Match(req.URL.Host) {
return http.ProxyURL(setting.Webhook.ProxyURLFixed)(req)
}
}
return http.ProxyFromEnvironment(req)
}
}

// InitDeliverHooks starts the hooks delivery thread
func InitDeliverHooks() {
Expand All @@ -191,15 +222,14 @@ func InitDeliverHooks() {
webhookHTTPClient = &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: setting.Webhook.SkipTLSVerify},
Proxy: http.ProxyFromEnvironment,
Proxy: webhookProxy(),
Dial: func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, timeout)
if err != nil {
return nil, err
}

return conn, conn.SetDeadline(time.Now().Add(timeout))

},
},
}
Expand Down
39 changes: 39 additions & 0 deletions modules/webhook/deliver_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2019 The Gitea Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package webhook

import (
"net/http"
"net/url"
"testing"

"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)

func TestWebhookProxy(t *testing.T) {
setting.Webhook.ProxyURL = "http://localhost:8080"
setting.Webhook.ProxyURLFixed, _ = url.Parse(setting.Webhook.ProxyURL)
setting.Webhook.ProxyHosts = []string{"*.discordapp.com", "discordapp.com"}

var 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": "",
}

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())
}
}
}