Skip to content

Commit dd337e1

Browse files
committed
Support HAProxy protocol
This PR adds functionality to allow Gitea to sit behind an HAProxy and HAProxy protocolled connections directly. Fix go-gitea#7508 Signed-off-by: Andrew Thornton <[email protected]>
1 parent e14f608 commit dd337e1

File tree

13 files changed

+748
-34
lines changed

13 files changed

+748
-34
lines changed

cmd/web.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func runHTTPRedirector() {
6060
http.Redirect(w, r, target, http.StatusTemporaryRedirect)
6161
})
6262

63-
var err = runHTTP("tcp", source, context2.ClearHandler(handler))
63+
var err = runHTTP("tcp", source, context2.ClearHandler(handler), setting.RedirectHAProxy)
6464

6565
if err != nil {
6666
log.Fatal("Failed to start port redirection: %v", err)
@@ -77,12 +77,12 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler)
7777
go func() {
7878
log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect)
7979
// all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here)
80-
var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)))
80+
var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectHAProxy)
8181
if err != nil {
8282
log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err)
8383
}
8484
}()
85-
return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m))
85+
return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging)
8686
}
8787

8888
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) {
@@ -177,7 +177,7 @@ func runWeb(ctx *cli.Context) error {
177177
switch setting.Protocol {
178178
case setting.HTTP:
179179
NoHTTPRedirector()
180-
err = runHTTP("tcp", listenAddr, context2.ClearHandler(m))
180+
err = runHTTP("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy)
181181
case setting.HTTPS:
182182
if setting.EnableLetsEncrypt {
183183
err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m))
@@ -188,16 +188,16 @@ func runWeb(ctx *cli.Context) error {
188188
} else {
189189
NoHTTPRedirector()
190190
}
191-
err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m))
191+
err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging)
192192
case setting.FCGI:
193193
NoHTTPRedirector()
194-
err = runFCGI("tcp", listenAddr, context2.ClearHandler(m))
194+
err = runFCGI("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy)
195195
case setting.UnixSocket:
196196
NoHTTPRedirector()
197-
err = runHTTP("unix", listenAddr, context2.ClearHandler(m))
197+
err = runHTTP("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy)
198198
case setting.FCGIUnix:
199199
NoHTTPRedirector()
200-
err = runFCGI("unix", listenAddr, context2.ClearHandler(m))
200+
err = runFCGI("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy)
201201
default:
202202
log.Fatal("Invalid protocol: %s", setting.Protocol)
203203
}

cmd/web_graceful.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,16 @@ import (
1414
"code.gitea.io/gitea/modules/log"
1515
)
1616

17-
func runHTTP(network, listenAddr string, m http.Handler) error {
18-
return graceful.HTTPListenAndServe(network, listenAddr, m)
17+
func runHTTP(network, listenAddr string, m http.Handler, haProxy bool) error {
18+
return graceful.HTTPListenAndServe(network, listenAddr, m, haProxy)
1919
}
2020

21-
func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler) error {
22-
return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m)
21+
func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler, haProxy, haProxyTLSBridging bool) error {
22+
return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m, haProxy, haProxyTLSBridging)
2323
}
2424

25-
func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler) error {
26-
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m)
25+
func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler, haProxy, haProxyTLSBridging bool) error {
26+
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m, haProxy, haProxyTLSBridging)
2727
}
2828

2929
// NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector
@@ -37,13 +37,13 @@ func NoMainListener() {
3737
graceful.GetManager().InformCleanup()
3838
}
3939

40-
func runFCGI(network, listenAddr string, m http.Handler) error {
40+
func runFCGI(network, listenAddr string, m http.Handler, haProxy bool) error {
4141
// This needs to handle stdin as fcgi point
4242
fcgiServer := graceful.NewServer(network, listenAddr)
4343

4444
err := fcgiServer.ListenAndServe(func(listener net.Listener) error {
4545
return fcgi.Serve(listener, m)
46-
})
46+
}, haProxy)
4747
if err != nil {
4848
log.Fatal("Failed to start FCGI main server: %v", err)
4949
}

custom/conf/app.example.ini

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,14 @@ FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd
247247
[server]
248248
; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'.
249249
PROTOCOL = http
250+
; Expect HAPROXY headers on connections
251+
HAPROXY = false
252+
; Use HAPROXY in TLS Bridging mode
253+
HAPROXY_TLS_BRIDGING = false
254+
; Timeout to wait for HAProxy header (set to 0 to have no timeout)
255+
HAPROXY_HEADER_TIMEOUT=5s
256+
; Accept Unknown HAProxy
257+
HAPROXY_ACCEPT_UNKNOWN=false
250258
DOMAIN = localhost
251259
ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/
252260
; when STATIC_URL_PREFIX is empty it will follow ROOT_URL
@@ -261,17 +269,23 @@ HTTP_PORT = 3000
261269
; PORT_TO_REDIRECT.
262270
REDIRECT_OTHER_PORT = false
263271
PORT_TO_REDIRECT = 80
272+
; expect HAPROXY header on connections to https redirector.
273+
REDIRECT_HAPROXY = %(HAPROXY)
264274
; Permission for unix socket
265275
UNIX_SOCKET_PERMISSION = 666
266276
; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service.
267277
; In most cases you do not need to change the default value.
268278
; Alter it only if your SSH server node is not the same as HTTP node.
269279
; Do not set this variable if PROTOCOL is set to 'unix'.
270280
LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/
281+
; When making local connections pass the HAPROXY header.
282+
LOCAL_HAPROXY = %(HAPROXY)
271283
; Disable SSH feature when not available
272284
DISABLE_SSH = false
273285
; Whether to use the builtin SSH server or not.
274286
START_SSH_SERVER = false
287+
; Expect HAPROXY header on connections to the built-in SSH server
288+
SSH_SERVER_HAPROXY = false
275289
; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER.
276290
BUILTIN_SSH_SERVER_USER =
277291
; Domain name to be exposed in clone URL

docs/content/doc/advanced/config-cheat-sheet.en-us.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
168168
## Server (`server`)
169169

170170
- `PROTOCOL`: **http**: \[http, https, fcgi, unix, fcgi+unix\]
171+
- `HAPROXY`: **false**: Expect HAPROXY headers on connections
172+
- `HAPROXY_TLS_BRIDGING`: **false**: When protocol is https, expect HAPROXY header after TLS negotiation.
173+
- `HAPROXY_HEADER_TIMEOUT`: **5s**: Timeout to wait for HAProxy header (set to 0 to have no timeout)
174+
- `HAPROXY_ACCEPT_UNKNOWN`: **false**: Accept Unknown HAProxy
171175
- `DOMAIN`: **localhost**: Domain name of this server.
172176
- `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**:
173177
Overwrite the automatically generated public URL.
@@ -192,8 +196,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
192196
most cases you do not need to change the default value. Alter it only if
193197
your SSH server node is not the same as HTTP node. Do not set this variable
194198
if `PROTOCOL` is set to `unix`.
199+
- `LOCAL_HAPROXY`: **%(HAPROXY)**: When making local connections pass the HAPROXY header.
200+
This should be set to false if the local connection will go through the HAPROXY.
195201
- `DISABLE_SSH`: **false**: Disable SSH feature when it's not available.
196202
- `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server.
203+
- `SSH_SERVER_HAPROXY`: **false**: Expect HAPROXY header on connections to the built-in SSH Server.
197204
- `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL.
198205
- `SSH_PORT`: **22**: SSH port displayed in clone URL.
199206
- `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address for the built-in SSH server.
@@ -213,6 +220,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
213220
- `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit).
214221
- `LFS_LOCK_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page.
215222
- `REDIRECT_OTHER_PORT`: **false**: If true and `PROTOCOL` is https, allows redirecting http requests on `PORT_TO_REDIRECT` to the https port Gitea listens on.
223+
- `REDIRECT_HAPROXY`: **%(HAPROXY)**: expect HAPROXY header on connections to https redirector.
216224
- `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true.
217225
- `ENABLE_LETSENCRYPT`: **false**: If enabled you must set `DOMAIN` to valid internet facing domain (ensure DNS is set and port 80 is accessible by letsencrypt validation server).
218226
By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf).

modules/graceful/server.go

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ import (
1616
"syscall"
1717
"time"
1818

19+
"code.gitea.io/gitea/modules/haproxy"
1920
"code.gitea.io/gitea/modules/log"
21+
"code.gitea.io/gitea/modules/setting"
2022
)
2123

2224
var (
@@ -71,16 +73,27 @@ func NewServer(network, address string) *Server {
7173

7274
// ListenAndServe listens on the provided network address and then calls Serve
7375
// to handle requests on incoming connections.
74-
func (srv *Server) ListenAndServe(serve ServeFunction) error {
76+
func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error {
7577
go srv.awaitShutdown()
7678

77-
l, err := GetListener(srv.network, srv.address)
79+
listener, err := GetListener(srv.network, srv.address)
7880
if err != nil {
7981
log.Error("Unable to GetListener: %v", err)
8082
return err
8183
}
8284

83-
srv.listener = newWrappedListener(l, srv)
85+
// we need to wrap the listener to take account of our lifecycle
86+
listener = newWrappedListener(listener, srv)
87+
88+
// Now we need to take account of HAProxy settings...
89+
if haProxy {
90+
listener = &haproxy.Listener{
91+
Listener: listener,
92+
ProxyHeaderTimeout: setting.HAProxyHeaderTimeout,
93+
AcceptUnknown: setting.HAProxyAcceptUnknown,
94+
}
95+
}
96+
srv.listener = listener
8497

8598
srv.BeforeBegin(srv.network, srv.address)
8699

@@ -94,7 +107,7 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error {
94107
// be provided. If the certificate is signed by a certificate authority, the
95108
// certFile should be the concatenation of the server's certificate followed by the
96109
// CA's certificate.
97-
func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction) error {
110+
func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction, haProxy, haProxyTLSBridging bool) error {
98111
config := &tls.Config{}
99112
if config.NextProtos == nil {
100113
config.NextProtos = []string{"http/1.1"}
@@ -120,23 +133,45 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti
120133
return err
121134
}
122135

123-
return srv.ListenAndServeTLSConfig(config, serve)
136+
return srv.ListenAndServeTLSConfig(config, serve, haProxy, haProxyTLSBridging)
124137
}
125138

126139
// ListenAndServeTLSConfig listens on the provided network address and then calls
127140
// Serve to handle requests on incoming TLS connections.
128-
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error {
141+
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, haProxy, haProxyTLSBridging bool) error {
129142
go srv.awaitShutdown()
130143

131-
l, err := GetListener(srv.network, srv.address)
144+
listener, err := GetListener(srv.network, srv.address)
132145
if err != nil {
133146
log.Error("Unable to get Listener: %v", err)
134147
return err
135148
}
136149

137-
wl := newWrappedListener(l, srv)
138-
srv.listener = tls.NewListener(wl, tlsConfig)
150+
// we need to wrap the listener to take account of our lifecycle
151+
listener = newWrappedListener(listener, srv)
152+
153+
// Now we need to take account of HAProxy settings... If we're not bridging then we expect that the proxy will forward the connection to us
154+
if haProxy && !haProxyTLSBridging {
155+
listener = &haproxy.Listener{
156+
Listener: listener,
157+
ProxyHeaderTimeout: setting.HAProxyHeaderTimeout,
158+
AcceptUnknown: setting.HAProxyAcceptUnknown,
159+
}
160+
}
161+
162+
// Now handle the tls protocol
163+
listener = tls.NewListener(listener, tlsConfig)
164+
165+
// Now if we're bridging then we need the proxy to tell us who we're bridging for...
166+
if haProxy && haProxyTLSBridging {
167+
listener = &haproxy.Listener{
168+
Listener: listener,
169+
ProxyHeaderTimeout: setting.HAProxyHeaderTimeout,
170+
AcceptUnknown: setting.HAProxyAcceptUnknown,
171+
}
172+
}
139173

174+
srv.listener = listener
140175
srv.BeforeBegin(srv.network, srv.address)
141176

142177
return srv.Serve(serve)

modules/graceful/server_http.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,21 @@ func newHTTPServer(network, address string, handler http.Handler) (*Server, Serv
2525

2626
// HTTPListenAndServe listens on the provided network address and then calls Serve
2727
// to handle requests on incoming connections.
28-
func HTTPListenAndServe(network, address string, handler http.Handler) error {
28+
func HTTPListenAndServe(network, address string, handler http.Handler, haProxy bool) error {
2929
server, lHandler := newHTTPServer(network, address, handler)
30-
return server.ListenAndServe(lHandler)
30+
return server.ListenAndServe(lHandler, haProxy)
3131
}
3232

3333
// HTTPListenAndServeTLS listens on the provided network address and then calls Serve
3434
// to handle requests on incoming connections.
35-
func HTTPListenAndServeTLS(network, address, certFile, keyFile string, handler http.Handler) error {
35+
func HTTPListenAndServeTLS(network, address, certFile, keyFile string, handler http.Handler, haProxy, haProxyTLSBridging bool) error {
3636
server, lHandler := newHTTPServer(network, address, handler)
37-
return server.ListenAndServeTLS(certFile, keyFile, lHandler)
37+
return server.ListenAndServeTLS(certFile, keyFile, lHandler, haProxy, haProxyTLSBridging)
3838
}
3939

4040
// HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve
4141
// to handle requests on incoming connections.
42-
func HTTPListenAndServeTLSConfig(network, address string, tlsConfig *tls.Config, handler http.Handler) error {
42+
func HTTPListenAndServeTLSConfig(network, address string, tlsConfig *tls.Config, handler http.Handler, haProxy, haProxyTLSBridging bool) error {
4343
server, lHandler := newHTTPServer(network, address, handler)
44-
return server.ListenAndServeTLSConfig(tlsConfig, lHandler)
44+
return server.ListenAndServeTLSConfig(tlsConfig, lHandler, haProxy, haProxyTLSBridging)
4545
}

0 commit comments

Comments
 (0)