From dd337e146dd4c977c813ee23ade00cf1184a9830 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Tue, 18 Aug 2020 19:46:01 +0100 Subject: [PATCH 1/9] Support HAProxy protocol This PR adds functionality to allow Gitea to sit behind an HAProxy and HAProxy protocolled connections directly. Fix #7508 Signed-off-by: Andrew Thornton --- cmd/web.go | 16 +- cmd/web_graceful.go | 16 +- custom/conf/app.example.ini | 14 + .../doc/advanced/config-cheat-sheet.en-us.md | 8 + modules/graceful/server.go | 53 +- modules/graceful/server_http.go | 12 +- modules/haproxy/conn.go | 506 ++++++++++++++++++ modules/haproxy/errors.go | 45 ++ modules/haproxy/listener.go | 47 ++ modules/haproxy/util.go | 15 + modules/private/internal.go | 33 +- modules/setting/setting.go | 14 + modules/ssh/ssh_graceful.go | 3 +- 13 files changed, 748 insertions(+), 34 deletions(-) create mode 100644 modules/haproxy/conn.go create mode 100644 modules/haproxy/errors.go create mode 100644 modules/haproxy/listener.go create mode 100644 modules/haproxy/util.go diff --git a/cmd/web.go b/cmd/web.go index f0e1b16e7fe12..e8839b388fd03 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -60,7 +60,7 @@ func runHTTPRedirector() { http.Redirect(w, r, target, http.StatusTemporaryRedirect) }) - var err = runHTTP("tcp", source, context2.ClearHandler(handler)) + var err = runHTTP("tcp", source, context2.ClearHandler(handler), setting.RedirectHAProxy) if err != nil { log.Fatal("Failed to start port redirection: %v", err) @@ -77,12 +77,12 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) go func() { log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) - var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) + var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectHAProxy) if err != nil { log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) } }() - return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m)) + return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging) } func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { @@ -177,7 +177,7 @@ func runWeb(ctx *cli.Context) error { switch setting.Protocol { case setting.HTTP: NoHTTPRedirector() - err = runHTTP("tcp", listenAddr, context2.ClearHandler(m)) + err = runHTTP("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy) case setting.HTTPS: if setting.EnableLetsEncrypt { err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m)) @@ -188,16 +188,16 @@ func runWeb(ctx *cli.Context) error { } else { NoHTTPRedirector() } - err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m)) + err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging) case setting.FCGI: NoHTTPRedirector() - err = runFCGI("tcp", listenAddr, context2.ClearHandler(m)) + err = runFCGI("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy) case setting.UnixSocket: NoHTTPRedirector() - err = runHTTP("unix", listenAddr, context2.ClearHandler(m)) + err = runHTTP("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy) case setting.FCGIUnix: NoHTTPRedirector() - err = runFCGI("unix", listenAddr, context2.ClearHandler(m)) + err = runFCGI("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy) default: log.Fatal("Invalid protocol: %s", setting.Protocol) } diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index f3c41766af490..85bf970f84238 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -14,16 +14,16 @@ import ( "code.gitea.io/gitea/modules/log" ) -func runHTTP(network, listenAddr string, m http.Handler) error { - return graceful.HTTPListenAndServe(network, listenAddr, m) +func runHTTP(network, listenAddr string, m http.Handler, haProxy bool) error { + return graceful.HTTPListenAndServe(network, listenAddr, m, haProxy) } -func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler) error { - return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m) +func runHTTPS(network, listenAddr, certFile, keyFile string, m http.Handler, haProxy, haProxyTLSBridging bool) error { + return graceful.HTTPListenAndServeTLS(network, listenAddr, certFile, keyFile, m, haProxy, haProxyTLSBridging) } -func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler) error { - return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m) +func runHTTPSWithTLSConfig(network, listenAddr string, tlsConfig *tls.Config, m http.Handler, haProxy, haProxyTLSBridging bool) error { + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, tlsConfig, m, haProxy, haProxyTLSBridging) } // NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector @@ -37,13 +37,13 @@ func NoMainListener() { graceful.GetManager().InformCleanup() } -func runFCGI(network, listenAddr string, m http.Handler) error { +func runFCGI(network, listenAddr string, m http.Handler, haProxy bool) error { // This needs to handle stdin as fcgi point fcgiServer := graceful.NewServer(network, listenAddr) err := fcgiServer.ListenAndServe(func(listener net.Listener) error { return fcgi.Serve(listener, m) - }) + }, haProxy) if err != nil { log.Fatal("Failed to start FCGI main server: %v", err) } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index bbd3b5bc3652e..68c2bfefc3fec 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -247,6 +247,14 @@ FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd [server] ; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. PROTOCOL = http +; Expect HAPROXY headers on connections +HAPROXY = false +; Use HAPROXY in TLS Bridging mode +HAPROXY_TLS_BRIDGING = false +; Timeout to wait for HAProxy header (set to 0 to have no timeout) +HAPROXY_HEADER_TIMEOUT=5s +; Accept Unknown HAProxy +HAPROXY_ACCEPT_UNKNOWN=false DOMAIN = localhost ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ ; when STATIC_URL_PREFIX is empty it will follow ROOT_URL @@ -261,6 +269,8 @@ HTTP_PORT = 3000 ; PORT_TO_REDIRECT. REDIRECT_OTHER_PORT = false PORT_TO_REDIRECT = 80 +; expect HAPROXY header on connections to https redirector. +REDIRECT_HAPROXY = %(HAPROXY) ; Permission for unix socket UNIX_SOCKET_PERMISSION = 666 ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. @@ -268,10 +278,14 @@ UNIX_SOCKET_PERMISSION = 666 ; Alter it only if your SSH server node is not the same as HTTP node. ; Do not set this variable if PROTOCOL is set to 'unix'. LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ +; When making local connections pass the HAPROXY header. +LOCAL_HAPROXY = %(HAPROXY) ; Disable SSH feature when not available DISABLE_SSH = false ; Whether to use the builtin SSH server or not. START_SSH_SERVER = false +; Expect HAPROXY header on connections to the built-in SSH server +SSH_SERVER_HAPROXY = false ; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. BUILTIN_SSH_SERVER_USER = ; Domain name to be exposed in clone URL diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 2bf825123522f..072e099c3ccf6 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -168,6 +168,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. ## Server (`server`) - `PROTOCOL`: **http**: \[http, https, fcgi, unix, fcgi+unix\] +- `HAPROXY`: **false**: Expect HAPROXY headers on connections +- `HAPROXY_TLS_BRIDGING`: **false**: When protocol is https, expect HAPROXY header after TLS negotiation. +- `HAPROXY_HEADER_TIMEOUT`: **5s**: Timeout to wait for HAProxy header (set to 0 to have no timeout) +- `HAPROXY_ACCEPT_UNKNOWN`: **false**: Accept Unknown HAProxy - `DOMAIN`: **localhost**: Domain name of this server. - `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**: Overwrite the automatically generated public URL. @@ -192,8 +196,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. most cases you do not need to change the default value. Alter it only if your SSH server node is not the same as HTTP node. Do not set this variable if `PROTOCOL` is set to `unix`. +- `LOCAL_HAPROXY`: **%(HAPROXY)**: When making local connections pass the HAPROXY header. + This should be set to false if the local connection will go through the HAPROXY. - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. +- `SSH_SERVER_HAPROXY`: **false**: Expect HAPROXY header on connections to the built-in SSH Server. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_PORT`: **22**: SSH port displayed in clone URL. - `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 `"""`. - `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit). - `LFS_LOCK_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. - `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. +- `REDIRECT_HAPROXY`: **%(HAPROXY)**: expect HAPROXY header on connections to https redirector. - `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true. - `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). By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf). diff --git a/modules/graceful/server.go b/modules/graceful/server.go index 4d0d8677f0e6c..c3aa2163c15fd 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -16,7 +16,9 @@ import ( "syscall" "time" + "code.gitea.io/gitea/modules/haproxy" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" ) var ( @@ -71,16 +73,27 @@ func NewServer(network, address string) *Server { // ListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func (srv *Server) ListenAndServe(serve ServeFunction) error { +func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error { go srv.awaitShutdown() - l, err := GetListener(srv.network, srv.address) + listener, err := GetListener(srv.network, srv.address) if err != nil { log.Error("Unable to GetListener: %v", err) return err } - srv.listener = newWrappedListener(l, srv) + // we need to wrap the listener to take account of our lifecycle + listener = newWrappedListener(listener, srv) + + // Now we need to take account of HAProxy settings... + if haProxy { + listener = &haproxy.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, + AcceptUnknown: setting.HAProxyAcceptUnknown, + } + } + srv.listener = listener srv.BeforeBegin(srv.network, srv.address) @@ -94,7 +107,7 @@ func (srv *Server) ListenAndServe(serve ServeFunction) error { // be provided. If the certificate is signed by a certificate authority, the // certFile should be the concatenation of the server's certificate followed by the // CA's certificate. -func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction) error { +func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFunction, haProxy, haProxyTLSBridging bool) error { config := &tls.Config{} if config.NextProtos == nil { config.NextProtos = []string{"http/1.1"} @@ -120,23 +133,45 @@ func (srv *Server) ListenAndServeTLS(certFile, keyFile string, serve ServeFuncti return err } - return srv.ListenAndServeTLSConfig(config, serve) + return srv.ListenAndServeTLSConfig(config, serve, haProxy, haProxyTLSBridging) } // ListenAndServeTLSConfig listens on the provided network address and then calls // Serve to handle requests on incoming TLS connections. -func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction) error { +func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, haProxy, haProxyTLSBridging bool) error { go srv.awaitShutdown() - l, err := GetListener(srv.network, srv.address) + listener, err := GetListener(srv.network, srv.address) if err != nil { log.Error("Unable to get Listener: %v", err) return err } - wl := newWrappedListener(l, srv) - srv.listener = tls.NewListener(wl, tlsConfig) + // we need to wrap the listener to take account of our lifecycle + listener = newWrappedListener(listener, srv) + + // 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 + if haProxy && !haProxyTLSBridging { + listener = &haproxy.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, + AcceptUnknown: setting.HAProxyAcceptUnknown, + } + } + + // Now handle the tls protocol + listener = tls.NewListener(listener, tlsConfig) + + // Now if we're bridging then we need the proxy to tell us who we're bridging for... + if haProxy && haProxyTLSBridging { + listener = &haproxy.Listener{ + Listener: listener, + ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, + AcceptUnknown: setting.HAProxyAcceptUnknown, + } + } + srv.listener = listener srv.BeforeBegin(srv.network, srv.address) return srv.Serve(serve) diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index 1052637d5e74a..872f709c91079 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -25,21 +25,21 @@ func newHTTPServer(network, address string, handler http.Handler) (*Server, Serv // HTTPListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServe(network, address string, handler http.Handler) error { +func HTTPListenAndServe(network, address string, handler http.Handler, haProxy bool) error { server, lHandler := newHTTPServer(network, address, handler) - return server.ListenAndServe(lHandler) + return server.ListenAndServe(lHandler, haProxy) } // HTTPListenAndServeTLS listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServeTLS(network, address, certFile, keyFile string, handler http.Handler) error { +func HTTPListenAndServeTLS(network, address, certFile, keyFile string, handler http.Handler, haProxy, haProxyTLSBridging bool) error { server, lHandler := newHTTPServer(network, address, handler) - return server.ListenAndServeTLS(certFile, keyFile, lHandler) + return server.ListenAndServeTLS(certFile, keyFile, lHandler, haProxy, haProxyTLSBridging) } // HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServeTLSConfig(network, address string, tlsConfig *tls.Config, handler http.Handler) error { +func HTTPListenAndServeTLSConfig(network, address string, tlsConfig *tls.Config, handler http.Handler, haProxy, haProxyTLSBridging bool) error { server, lHandler := newHTTPServer(network, address, handler) - return server.ListenAndServeTLSConfig(tlsConfig, lHandler) + return server.ListenAndServeTLSConfig(tlsConfig, lHandler, haProxy, haProxyTLSBridging) } diff --git a/modules/haproxy/conn.go b/modules/haproxy/conn.go new file mode 100644 index 0000000000000..b39a26351108d --- /dev/null +++ b/modules/haproxy/conn.go @@ -0,0 +1,506 @@ +// Copyright 2020 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 haproxy + +import ( + "bufio" + "bytes" + "encoding/binary" + "io" + "net" + "strconv" + "strings" + "sync" + "time" + + "code.gitea.io/gitea/modules/log" +) + +var ( + // v1Prefix is the string we look for at the start of a connection + // to check if this connection is using the proxy protocol + v1Prefix = []byte("PROXY ") + v1PrefixLen = len(v1Prefix) + v2Prefix = []byte("\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A") + v2PrefixLen = len(v2Prefix) +) + +// Conn is used to wrap and underlying connection which is speaking the +// Proxy Protocol. RemoteAddr() will return the address of the client +// instead of the proxy address. +type Conn struct { + bufReader *bufio.Reader + conn net.Conn + localAddr net.Addr + remoteAddr net.Addr + once sync.Once + proxyHeaderTimeout time.Duration + acceptUnknown bool +} + +// NewConn is used to wrap a net.Conn speaking the proxy protocol into +// a haproxy.Conn +func NewConn(conn net.Conn, timeout time.Duration) *Conn { + pConn := &Conn{ + bufReader: bufio.NewReader(conn), + conn: conn, + proxyHeaderTimeout: timeout, + } + return pConn +} + +// Read reads data from the connection. +// It will initially read the proxy protocol header. +// If there is an error parsing the header, it is returned and the socket is closed. +func (p *Conn) Read(b []byte) (int, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.bufReader.Read(b) +} + +// ReadFrom reads data from a provided reader and copies it to the connection. +func (p *Conn) ReadFrom(r io.Reader) (int64, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + if rf, ok := p.conn.(io.ReaderFrom); ok { + return rf.ReadFrom(r) + } + return io.Copy(p.conn, r) +} + +// WriteTo reads data from the connection and writes it to the writer. +// It will initially read the proxy protocol header. +// If there is an error parsing the header, it is returned and the socket is closed. +func (p *Conn) WriteTo(w io.Writer) (int64, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.bufReader.WriteTo(w) +} + +// Write writes data to the connection. +// Write can be made to time out and return an error after a fixed +// time limit; see SetDeadline and SetWriteDeadline. +func (p *Conn) Write(b []byte) (int, error) { + if err := p.readProxyHeaderOnce(); err != nil { + return 0, err + } + return p.conn.Write(b) +} + +// Close closes the connection. +// Any blocked Read or Write operations will be unblocked and return errors. +func (p *Conn) Close() error { + return p.conn.Close() +} + +// LocalAddr returns the local network address. +func (p *Conn) LocalAddr() net.Addr { + _ = p.readProxyHeaderOnce() + if p.localAddr != nil { + return p.localAddr + } + return p.conn.LocalAddr() +} + +// RemoteAddr returns the address of the client if the proxy +// protocol is being used, otherwise just returns the address of +// the socket peer. If there is an error parsing the header, the +// address of the client is not returned, and the socket is closed. +// One implication of this is that the call could block if the +// client is slow. Using a Deadline is recommended if this is called +// before Read() +func (p *Conn) RemoteAddr() net.Addr { + _ = p.readProxyHeaderOnce() + if p.remoteAddr != nil { + return p.remoteAddr + } + return p.conn.RemoteAddr() +} + +// SetDeadline sets the read and write deadlines associated +// with the connection. It is equivalent to calling both +// SetReadDeadline and SetWriteDeadline. +// +// A deadline is an absolute time after which I/O operations +// fail instead of blocking. The deadline applies to all future +// and pending I/O, not just the immediately following call to +// Read or Write. After a deadline has been exceeded, the +// connection can be refreshed by setting a deadline in the future. +// +// If the deadline is exceeded a call to Read or Write or to other +// I/O methods will return an error that wraps os.ErrDeadlineExceeded. +// This can be tested using errors.Is(err, os.ErrDeadlineExceeded). +// The error's Timeout method will return true, but note that there +// are other possible errors for which the Timeout method will +// return true even if the deadline has not been exceeded. +// +// An idle timeout can be implemented by repeatedly extending +// the deadline after successful Read or Write calls. +// +// A zero value for t means I/O operations will not time out. +func (p *Conn) SetDeadline(t time.Time) error { + return p.conn.SetDeadline(t) +} + +// SetReadDeadline sets the deadline for future Read calls +// and any currently-blocked Read call. +// A zero value for t means Read will not time out. +func (p *Conn) SetReadDeadline(t time.Time) error { + return p.conn.SetReadDeadline(t) +} + +// SetWriteDeadline sets the deadline for future Write calls +// and any currently-blocked Write call. +// Even if write times out, it may return n > 0, indicating that +// some of the data was successfully written. +// A zero value for t means Write will not time out. +func (p *Conn) SetWriteDeadline(t time.Time) error { + return p.conn.SetWriteDeadline(t) +} + +// readProxyHeaderOnce will ensure that the proxy header has been read +func (p *Conn) readProxyHeaderOnce() (err error) { + p.once.Do(func() { + if err = p.readProxyHeader(); err != nil && err != io.EOF { + log.Error("Failed to read proxy prefix: %v", err) + p.Close() + p.bufReader = bufio.NewReader(p.conn) + } + }) + return +} + +func (p *Conn) readProxyHeader() error { + if p.proxyHeaderTimeout != 0 { + readDeadLine := time.Now().Add(p.proxyHeaderTimeout) + _ = p.conn.SetReadDeadline(readDeadLine) + defer func() { + _ = p.conn.SetReadDeadline(time.Time{}) + }() + } + + inp, err := p.bufReader.Peek(v1PrefixLen) + if err != nil { + return err + } + + if bytes.Equal(inp, v1Prefix) { + return p.readV1ProxyHeader() + } + + inp, err = p.bufReader.Peek(v2PrefixLen) + if err != nil { + return err + } + if bytes.Equal(inp, v2Prefix) { + return p.readV2ProxyHeader() + } + + return &ErrBadHeader{inp} +} + +func (p *Conn) readV2ProxyHeader() error { + // The binary header format starts with a constant 12 bytes block containing the + // protocol signature : + // + // \x0D \x0A \x0D \x0A \x00 \x0D \x0A \x51 \x55 \x49 \x54 \x0A + // + // Note that this block contains a null byte at the 5th position, so it must not + // be handled as a null-terminated string. + + if _, err := p.bufReader.Discard(v2PrefixLen); err != nil { + // This shouldn't happen as we have already asserted that there should be enough in the buffer + return err + } + + // The next byte (the 13th one) is the protocol version and command. + version, err := p.bufReader.ReadByte() + if err != nil { + return err + } + + // The 14th byte contains the transport protocol and address family.otocol. + familyByte, err := p.bufReader.ReadByte() + if err != nil { + return err + } + + // The 15th and 16th bytes is the address length in bytes in network endian order. + var addressLen uint16 + if err := binary.Read(p.bufReader, binary.BigEndian, &addressLen); err != nil { + return err + } + + // Now handle the version byte: (14th byte). + // The highest four bits contains the version. As of this specification, it must + // always be sent as \x2 and the receiver must only accept this value. + if version>>4 != 0x2 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // The lowest four bits represents the command : + switch version & 0xf { + case 0x0: + // - \x0 : LOCAL : the connection was established on purpose by the proxy + // without being relayed. The connection endpoints are the sender and the + // receiver. Such connections exist when the proxy sends health-checks to the + // server. The receiver must accept this connection as valid and must use the + // real connection endpoints and discard the protocol block including the + // family which is ignored. + + // We therefore ignore the 14th, 15th and 16th bytes + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + return nil + case 0x1: + // - \x1 : PROXY : the connection was established on behalf of another node, + // and reflects the original connection endpoints. The receiver must then use + // the information provided in the protocol block to get original the address. + default: + // - other values are unassigned and must not be emitted by senders. Receivers + // must drop connections presenting unexpected values here. + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // Now handle the familyByte byte: (15th byte). + // The highest 4 bits contain the address family, the lowest 4 bits contain the protocol + + // The address family maps to the original socket family without necessarily + // matching the values internally used by the system. It may be one of : + // + // - 0x0 : AF_UNSPEC : the connection is forwarded for an unknown, unspecified + // or unsupported protocol. The sender should use this family when sending + // LOCAL commands or when dealing with unsupported protocol families. The + // receiver is free to accept the connection anyway and use the real endpoint + // addresses or to reject it. The receiver should ignore address information. + // + // - 0x1 : AF_INET : the forwarded connection uses the AF_INET address family + // (IPv4). The addresses are exactly 4 bytes each in network byte order, + // followed by transport protocol information (typically ports). + // + // - 0x2 : AF_INET6 : the forwarded connection uses the AF_INET6 address family + // (IPv6). The addresses are exactly 16 bytes each in network byte order, + // followed by transport protocol information (typically ports). + // + // - 0x3 : AF_UNIX : the forwarded connection uses the AF_UNIX address family + // (UNIX). The addresses are exactly 108 bytes each. + // + // - other values are unspecified and must not be emitted in version 2 of this + // protocol and must be rejected as invalid by receivers. + + // The transport protocol is specified in the lowest 4 bits of the 14th byte : + // + // - 0x0 : UNSPEC : the connection is forwarded for an unknown, unspecified + // or unsupported protocol. The sender should use this family when sending + // LOCAL commands or when dealing with unsupported protocol families. The + // receiver is free to accept the connection anyway and use the real endpoint + // addresses or to reject it. The receiver should ignore address information. + // + // - 0x1 : STREAM : the forwarded connection uses a SOCK_STREAM protocol (eg: + // TCP or UNIX_STREAM). When used with AF_INET/AF_INET6 (TCP), the addresses + // are followed by the source and destination ports represented on 2 bytes + // each in network byte order. + // + // - 0x2 : DGRAM : the forwarded connection uses a SOCK_DGRAM protocol (eg: + // UDP or UNIX_DGRAM). When used with AF_INET/AF_INET6 (UDP), the addresses + // are followed by the source and destination ports represented on 2 bytes + // each in network byte order. + // + // - other values are unspecified and must not be emitted in version 2 of this + // protocol and must be rejected as invalid by receivers. + + if familyByte>>4 == 0x0 || familyByte&0xf == 0x0 { + // - hi 0x0 : AF_UNSPEC : the connection is forwarded for an unknown address type + // or + // - lo 0x0 : UNSPEC : the connection is forwarded for an unspecified protocol + if !p.acceptUnknown { + p.conn.Close() + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + _, err = p.bufReader.Discard(int(addressLen)) + return err + } + + // other address or protocol + if (familyByte>>4) > 0x3 || (familyByte&0xf) > 0x2 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + // Handle AF_UNIX addresses + if familyByte>>4 == 0x3 { + // - \x31 : UNIX stream : the forwarded connection uses SOCK_STREAM over the + // AF_UNIX protocol family. Address length is 2*108 = 216 bytes. + // - \x32 : UNIX datagram : the forwarded connection uses SOCK_DGRAM over the + // AF_UNIX protocol family. Address length is 2*108 = 216 bytes. + if addressLen != 216 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + remoteName := make([]byte, 108) + localName := make([]byte, 108) + if _, err := p.bufReader.Read(remoteName); err != nil { + return err + } + if _, err := p.bufReader.Read(localName); err != nil { + return err + } + protocol := "unix" + if familyByte&0xf == 2 { + protocol = "unixgram" + } + + p.remoteAddr = &net.UnixAddr{ + Name: string(remoteName), + Net: protocol, + } + p.localAddr = &net.UnixAddr{ + Name: string(localName), + Net: protocol, + } + return nil + } + + var remoteIP []byte + var localIP []byte + var remotePort uint16 + var localPort uint16 + + if familyByte>>4 == 0x1 { + // AF_INET + // - \x11 : TCP over IPv4 : the forwarded connection uses TCP over the AF_INET + // protocol family. Address length is 2*4 + 2*2 = 12 bytes. + // - \x12 : UDP over IPv4 : the forwarded connection uses UDP over the AF_INET + // protocol family. Address length is 2*4 + 2*2 = 12 bytes. + if addressLen != 12 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + remoteIP = make([]byte, 4) + localIP = make([]byte, 4) + } else { + // AF_INET6 + // - \x21 : TCP over IPv6 : the forwarded connection uses TCP over the AF_INET6 + // protocol family. Address length is 2*16 + 2*2 = 36 bytes. + // - \x22 : UDP over IPv6 : the forwarded connection uses UDP over the AF_INET6 + // protocol family. Address length is 2*16 + 2*2 = 36 bytes. + if addressLen != 36 { + return &ErrBadHeader{append(v2Prefix, version, familyByte, uint8(addressLen>>8), uint8(addressLen&0xff))} + } + + remoteIP = make([]byte, 16) + localIP = make([]byte, 16) + } + + if _, err := p.bufReader.Read(remoteIP); err != nil { + return err + } + if _, err := p.bufReader.Read(localIP); err != nil { + return err + } + if err := binary.Read(p.bufReader, binary.BigEndian, &remotePort); err != nil { + return err + } + if err := binary.Read(p.bufReader, binary.BigEndian, &localPort); err != nil { + return err + } + + if familyByte&0xf == 1 { + p.remoteAddr = &net.TCPAddr{ + IP: remoteIP, + Port: int(remotePort), + } + p.localAddr = &net.TCPAddr{ + IP: localIP, + Port: int(localPort), + } + } else { + p.remoteAddr = &net.UDPAddr{ + IP: remoteIP, + Port: int(remotePort), + } + p.localAddr = &net.UDPAddr{ + IP: localIP, + Port: int(localPort), + } + } + return nil +} + +func (p *Conn) readV1ProxyHeader() error { + // Read until a newline + header, err := p.bufReader.ReadString('\n') + if err != nil { + p.conn.Close() + return err + } + + if header[len(header)-2] != '\r' { + return &ErrBadHeader{[]byte(header)} + } + + // Strip the carriage return and new line + header = header[:len(header)-2] + + // Split on spaces, should be (PROXY ) + parts := strings.Split(header, " ") + if len(parts) < 2 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + + // Verify the type is known + switch parts[1] { + case "UNKNOWN": + if !p.acceptUnknown || len(parts) != 2 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + p.remoteAddr = p.conn.LocalAddr() + p.localAddr = p.conn.RemoteAddr() + return nil + case "TCP4": + case "TCP6": + default: + p.conn.Close() + return &ErrBadAddressType{parts[1]} + } + + if len(parts) != 6 { + p.conn.Close() + return &ErrBadHeader{[]byte(header)} + } + + // Parse out the remote address + ip := net.ParseIP(parts[2]) + if ip == nil { + p.conn.Close() + return &ErrBadRemote{parts[2], parts[4]} + } + port, err := strconv.Atoi(parts[4]) + if err != nil { + p.conn.Close() + return &ErrBadRemote{parts[2], parts[4]} + } + p.remoteAddr = &net.TCPAddr{IP: ip, Port: port} + + // Parse out the destination address + ip = net.ParseIP(parts[3]) + if ip == nil { + p.conn.Close() + return &ErrBadLocal{parts[3], parts[5]} + } + port, err = strconv.Atoi(parts[5]) + if err != nil { + p.conn.Close() + return &ErrBadLocal{parts[3], parts[5]} + } + p.localAddr = &net.TCPAddr{IP: ip, Port: port} + + return nil +} diff --git a/modules/haproxy/errors.go b/modules/haproxy/errors.go new file mode 100644 index 0000000000000..da42e51a76d8f --- /dev/null +++ b/modules/haproxy/errors.go @@ -0,0 +1,45 @@ +// Copyright 2020 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 haproxy + +import "fmt" + +// ErrBadHeader is an error demonstrating a bad proxy header +type ErrBadHeader struct { + Header []byte +} + +func (e *ErrBadHeader) Error() string { + return fmt.Sprintf("Unexpected proxy header: %v", e.Header) +} + +// ErrBadAddressType is an error demonstrating a bad proxy header with bad Address type +type ErrBadAddressType struct { + Address string +} + +func (e *ErrBadAddressType) Error() string { + return fmt.Sprintf("Unexpected proxy header address type: %s", e.Address) +} + +// ErrBadRemote is an error demonstrating a bad proxy header with bad Remote +type ErrBadRemote struct { + IP string + Port string +} + +func (e *ErrBadRemote) Error() string { + return fmt.Sprintf("Unexpected proxy header remote IP and port: %s %s", e.IP, e.Port) +} + +// ErrBadLocal is an error demonstrating a bad proxy header with bad Local +type ErrBadLocal struct { + IP string + Port string +} + +func (e *ErrBadLocal) Error() string { + return fmt.Sprintf("Unexpected proxy header local IP and port: %s %s", e.IP, e.Port) +} diff --git a/modules/haproxy/listener.go b/modules/haproxy/listener.go new file mode 100644 index 0000000000000..8c9e26f0ae1f4 --- /dev/null +++ b/modules/haproxy/listener.go @@ -0,0 +1,47 @@ +// Copyright 2020 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 haproxy + +import ( + "net" + "time" +) + +// Listener is used to wrap an underlying listener, +// whose connections may be using the HAProxy Proxy Protocol (version 1 or 2). +// If the connection is using the protocol, the RemoteAddr() will return +// the correct client address. +// +// Optionally define ProxyHeaderTimeout to set a maximum time to +// receive the Proxy Protocol Header. Zero means no timeout. +type Listener struct { + Listener net.Listener + ProxyHeaderTimeout time.Duration + AcceptUnknown bool // allow PROXY UNKNOWN +} + +// Accept implements the Accept method in the Listener interface +// it waits for the next call and returns a wrapped Conn. +func (p *Listener) Accept() (net.Conn, error) { + // Get the underlying connection + conn, err := p.Listener.Accept() + if err != nil { + return nil, err + } + + newConn := NewConn(conn, p.ProxyHeaderTimeout) + newConn.acceptUnknown = p.AcceptUnknown + return newConn, nil +} + +// Close closes the underlying listener. +func (p *Listener) Close() error { + return p.Listener.Close() +} + +// Addr returns the underlying listener's network address. +func (p *Listener) Addr() net.Addr { + return p.Listener.Addr() +} diff --git a/modules/haproxy/util.go b/modules/haproxy/util.go new file mode 100644 index 0000000000000..d1330cf03dd99 --- /dev/null +++ b/modules/haproxy/util.go @@ -0,0 +1,15 @@ +// Copyright 2020 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 haproxy + +import "io" + +var localHeader = append(v2Prefix, '\x20', '\x00', '\x00', '\x00', '\x00') + +// WriteLocalProxyHeader will write the HAProxy Header for a local connection to the provided writer +func WriteLocalProxyHeader(w io.Writer) error { + _, err := w.Write(localHeader) + return err +} diff --git a/modules/private/internal.go b/modules/private/internal.go index b4fee2680fbac..08fd83205fa75 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -5,12 +5,14 @@ package private import ( + "context" "crypto/tls" "encoding/json" "fmt" "net" "net/http" + "code.gitea.io/gitea/modules/haproxy" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/setting" ) @@ -41,10 +43,37 @@ func newInternalRequest(url, method string) *httplib.Request { }) if setting.Protocol == setting.UnixSocket { req.SetTransport(&http.Transport{ - Dial: func(_, _ string) (net.Conn, error) { - return net.Dial("unix", setting.HTTPAddr) + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr) + if err != nil { + return conn, err + } + if setting.LocalHAProxy { + if err = haproxy.WriteLocalProxyHeader(conn); err != nil { + _ = conn.Close() + return nil, err + } + } + return conn, err + }, + }) + } else if setting.LocalHAProxy { + req.SetTransport(&http.Transport{ + DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { + var d net.Dialer + conn, err := d.DialContext(ctx, network, address) + if err != nil { + return conn, err + } + if err = haproxy.WriteLocalProxyHeader(conn); err != nil { + _ = conn.Close() + return nil, err + } + return conn, err }, }) } + req.SetTransport(&http.Transport{}) return req } diff --git a/modules/setting/setting.go b/modules/setting/setting.go index f7edd8e507a98..638f86ad3ca6a 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -78,11 +78,17 @@ var ( // Server settings Protocol Scheme + HAProxy bool // `ini:"HAPROXY"` + HAProxyTLSBridging bool // `ini:"HAPROXY_TLS_BRIDGING"` + HAProxyHeaderTimeout time.Duration + HAProxyAcceptUnknown bool Domain string HTTPAddr string HTTPPort string LocalURL string + LocalHAProxy bool RedirectOtherPort bool + RedirectHAProxy bool PortToRedirect string OfflineMode bool CertFile string @@ -106,6 +112,7 @@ var ( SSH = struct { Disabled bool `ini:"DISABLE_SSH"` StartBuiltinServer bool `ini:"START_SSH_SERVER"` + HAProxy bool `ini:"SSH_SERVER_HAPROXY"` BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` Domain string `ini:"SSH_DOMAIN"` Port int `ini:"SSH_PORT"` @@ -589,6 +596,10 @@ func NewContext() { } UnixSocketPermission = uint32(UnixSocketPermissionParsed) } + HAProxy = sec.Key("HAPROXY").MustBool(false) + HAProxyTLSBridging = sec.Key("HAPROXY_TLS_BRIDGING").MustBool(false) + HAProxyHeaderTimeout = sec.Key("HAPROXY_HEADER_TIMEOUT").MustDuration(5 * time.Second) + HAProxyAcceptUnknown = sec.Key("HAPROXY_ACCEPT_UNKNOWN").MustBool(false) EnableLetsEncrypt = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) LetsEncryptTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false) if !LetsEncryptTOS && EnableLetsEncrypt { @@ -644,8 +655,10 @@ func NewContext() { } } LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) + LocalHAProxy = sec.Key("LOCAL_HAPROXY").MustBool(HAProxy) RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") + RedirectHAProxy = sec.Key("REDIRECT_HAPROXY").MustBool(HAProxy) OfflineMode = sec.Key("OFFLINE_MODE").MustBool() DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() if len(StaticRootPath) == 0 { @@ -696,6 +709,7 @@ func NewContext() { SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") SSH.Port = sec.Key("SSH_PORT").MustInt(22) SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) + SSH.HAProxy = sec.Key("SSH_SERVER_HAPROXY").MustBool(false) // When disable SSH, start builtin server value is ignored. if SSH.Disabled { diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index a30e6fc29741a..16afcf64e0a73 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -7,6 +7,7 @@ package ssh import ( "code.gitea.io/gitea/modules/graceful" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" "github.com/gliderlabs/ssh" ) @@ -14,7 +15,7 @@ import ( func listen(server *ssh.Server) { gracefulServer := graceful.NewServer("tcp", server.Addr) - err := gracefulServer.ListenAndServe(server.Serve) + err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.HAProxy) if err != nil { select { case <-graceful.GetManager().IsShutdown(): From fc0065d4ea611c65837c98882129a43f054edfd9 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Tue, 18 Aug 2020 21:21:27 +0100 Subject: [PATCH 2/9] oops Signed-off-by: Andrew Thornton --- modules/private/internal.go | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/private/internal.go b/modules/private/internal.go index 08fd83205fa75..af5b6832b433f 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -74,6 +74,5 @@ func newInternalRequest(url, method string) *httplib.Request { }, }) } - req.SetTransport(&http.Transport{}) return req } From 2e488e8d538029fcf962661e9f1d4ed1fb0d995b Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Tue, 18 Aug 2020 21:36:17 +0100 Subject: [PATCH 3/9] Rename everything to UseProxyProtocol Signed-off-by: Andrew Thornton --- cmd/web.go | 16 ++-- custom/conf/app.example.ini | 28 +++---- .../doc/advanced/config-cheat-sheet.en-us.md | 16 ++-- modules/graceful/server.go | 12 +-- modules/private/internal.go | 4 +- modules/setting/setting.go | 78 +++++++++---------- modules/ssh/ssh_graceful.go | 2 +- 7 files changed, 78 insertions(+), 78 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index e8839b388fd03..51a1a3ce272c0 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -60,7 +60,7 @@ func runHTTPRedirector() { http.Redirect(w, r, target, http.StatusTemporaryRedirect) }) - var err = runHTTP("tcp", source, context2.ClearHandler(handler), setting.RedirectHAProxy) + var err = runHTTP("tcp", source, context2.ClearHandler(handler), setting.RedirectorUseProxyProtocol) if err != nil { log.Fatal("Failed to start port redirection: %v", err) @@ -77,12 +77,12 @@ func runLetsEncrypt(listenAddr, domain, directory, email string, m http.Handler) go func() { log.Info("Running Let's Encrypt handler on %s", setting.HTTPAddr+":"+setting.PortToRedirect) // all traffic coming into HTTP will be redirect to HTTPS automatically (LE HTTP-01 validation happens here) - var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectHAProxy) + var err = runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, certManager.HTTPHandler(http.HandlerFunc(runLetsEncryptFallbackHandler)), setting.RedirectorUseProxyProtocol) if err != nil { log.Fatal("Failed to start the Let's Encrypt handler on port %s: %v", setting.PortToRedirect, err) } }() - return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging) + return runHTTPSWithTLSConfig("tcp", listenAddr, certManager.TLSConfig(), context2.ClearHandler(m), setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) } func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { @@ -177,7 +177,7 @@ func runWeb(ctx *cli.Context) error { switch setting.Protocol { case setting.HTTP: NoHTTPRedirector() - err = runHTTP("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy) + err = runHTTP("tcp", listenAddr, context2.ClearHandler(m), setting.UseProxyProtocol) case setting.HTTPS: if setting.EnableLetsEncrypt { err = runLetsEncrypt(listenAddr, setting.Domain, setting.LetsEncryptDirectory, setting.LetsEncryptEmail, context2.ClearHandler(m)) @@ -188,16 +188,16 @@ func runWeb(ctx *cli.Context) error { } else { NoHTTPRedirector() } - err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m), setting.HAProxy, setting.HAProxyTLSBridging) + err = runHTTPS("tcp", listenAddr, setting.CertFile, setting.KeyFile, context2.ClearHandler(m), setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) case setting.FCGI: NoHTTPRedirector() - err = runFCGI("tcp", listenAddr, context2.ClearHandler(m), setting.HAProxy) + err = runFCGI("tcp", listenAddr, context2.ClearHandler(m), setting.UseProxyProtocol) case setting.UnixSocket: NoHTTPRedirector() - err = runHTTP("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy) + err = runHTTP("unix", listenAddr, context2.ClearHandler(m), setting.UseProxyProtocol) case setting.FCGIUnix: NoHTTPRedirector() - err = runFCGI("unix", listenAddr, context2.ClearHandler(m), setting.HAProxy) + err = runFCGI("unix", listenAddr, context2.ClearHandler(m), setting.UseProxyProtocol) default: log.Fatal("Invalid protocol: %s", setting.Protocol) } diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 68c2bfefc3fec..b45e07a183692 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -247,14 +247,14 @@ FILE_EXTENSIONS = .md,.markdown,.mdown,.mkd [server] ; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. PROTOCOL = http -; Expect HAPROXY headers on connections -HAPROXY = false -; Use HAPROXY in TLS Bridging mode -HAPROXY_TLS_BRIDGING = false -; Timeout to wait for HAProxy header (set to 0 to have no timeout) -HAPROXY_HEADER_TIMEOUT=5s -; Accept Unknown HAProxy -HAPROXY_ACCEPT_UNKNOWN=false +; Expect PROXY protocol headers on connections +USE_PROXY_PROTOCOL = false +; Use PROXY protocol in TLS Bridging mode +PROXY_PROTOCOL_TLS_BRIDGING = false +; Timeout to wait for PROXY protocol header (set to 0 to have no timeout) +PROXY_PROTOCOL_HEADER_TIMEOUT=5s +; Accept PROXY protocol headers with UNKNOWN type +PROXY_PROTOCOL_ACCEPT_UNKNOWN=false DOMAIN = localhost ROOT_URL = %(PROTOCOL)s://%(DOMAIN)s:%(HTTP_PORT)s/ ; when STATIC_URL_PREFIX is empty it will follow ROOT_URL @@ -269,8 +269,8 @@ HTTP_PORT = 3000 ; PORT_TO_REDIRECT. REDIRECT_OTHER_PORT = false PORT_TO_REDIRECT = 80 -; expect HAPROXY header on connections to https redirector. -REDIRECT_HAPROXY = %(HAPROXY) +; expect PROXY protocol header on connections to https redirector. +REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL) ; Permission for unix socket UNIX_SOCKET_PERMISSION = 666 ; Local (DMZ) URL for Gitea workers (such as SSH update) accessing web service. @@ -278,14 +278,14 @@ UNIX_SOCKET_PERMISSION = 666 ; Alter it only if your SSH server node is not the same as HTTP node. ; Do not set this variable if PROTOCOL is set to 'unix'. LOCAL_ROOT_URL = %(PROTOCOL)s://%(HTTP_ADDR)s:%(HTTP_PORT)s/ -; When making local connections pass the HAPROXY header. -LOCAL_HAPROXY = %(HAPROXY) +; When making local connections pass the PROXY protocol header. +LOCAL_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL) ; Disable SSH feature when not available DISABLE_SSH = false ; Whether to use the builtin SSH server or not. START_SSH_SERVER = false -; Expect HAPROXY header on connections to the built-in SSH server -SSH_SERVER_HAPROXY = false +; Expect PROXY protocol header on connections to the built-in SSH server +SSH_SERVER_USE_PROXY_PROTOCOL = false ; Username to use for the builtin SSH server. If blank, then it is the value of RUN_USER. BUILTIN_SSH_SERVER_USER = ; Domain name to be exposed in clone URL diff --git a/docs/content/doc/advanced/config-cheat-sheet.en-us.md b/docs/content/doc/advanced/config-cheat-sheet.en-us.md index 072e099c3ccf6..b8c0897a30ae9 100644 --- a/docs/content/doc/advanced/config-cheat-sheet.en-us.md +++ b/docs/content/doc/advanced/config-cheat-sheet.en-us.md @@ -168,10 +168,10 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. ## Server (`server`) - `PROTOCOL`: **http**: \[http, https, fcgi, unix, fcgi+unix\] -- `HAPROXY`: **false**: Expect HAPROXY headers on connections -- `HAPROXY_TLS_BRIDGING`: **false**: When protocol is https, expect HAPROXY header after TLS negotiation. -- `HAPROXY_HEADER_TIMEOUT`: **5s**: Timeout to wait for HAProxy header (set to 0 to have no timeout) -- `HAPROXY_ACCEPT_UNKNOWN`: **false**: Accept Unknown HAProxy +- `USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol headers on connections +- `PROXY_PROTOCOL_TLS_BRIDGING`: **false**: When protocol is https, expect PROXY protocol headers after TLS negotiation. +- `PROXY_PROTOCOL_HEADER_TIMEOUT`: **5s**: Timeout to wait for PROXY protocol header (set to 0 to have no timeout) +- `PROXY_PROTOCOL_ACCEPT_UNKNOWN`: **false**: Accept PROXY protocol headers with Unknown type. - `DOMAIN`: **localhost**: Domain name of this server. - `ROOT_URL`: **%(PROTOCOL)s://%(DOMAIN)s:%(HTTP\_PORT)s/**: Overwrite the automatically generated public URL. @@ -196,11 +196,11 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. most cases you do not need to change the default value. Alter it only if your SSH server node is not the same as HTTP node. Do not set this variable if `PROTOCOL` is set to `unix`. -- `LOCAL_HAPROXY`: **%(HAPROXY)**: When making local connections pass the HAPROXY header. - This should be set to false if the local connection will go through the HAPROXY. +- `LOCAL_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: When making local connections pass the PROXY protocol header. + This should be set to false if the local connection will go through the proxy. - `DISABLE_SSH`: **false**: Disable SSH feature when it's not available. - `START_SSH_SERVER`: **false**: When enabled, use the built-in SSH server. -- `SSH_SERVER_HAPROXY`: **false**: Expect HAPROXY header on connections to the built-in SSH Server. +- `SSH_SERVER_USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol header on connections to the built-in SSH Server. - `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. - `SSH_PORT`: **22**: SSH port displayed in clone URL. - `SSH_LISTEN_HOST`: **0.0.0.0**: Listen address for the built-in SSH server. @@ -220,7 +220,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`. - `LFS_MAX_FILE_SIZE`: **0**: Maximum allowed LFS file size in bytes (Set to 0 for no limit). - `LFS_LOCK_PAGING_NUM`: **50**: Maximum number of LFS Locks returned per page. - `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. -- `REDIRECT_HAPROXY`: **%(HAPROXY)**: expect HAPROXY header on connections to https redirector. +- `REDIRECTOR_USE_PROXY_PROTOCOL`: **%(USE_PROXY_PROTOCOL)**: expect PROXY protocol header on connections to https redirector. - `PORT_TO_REDIRECT`: **80**: Port for the http redirection service to listen on. Used when `REDIRECT_OTHER_PORT` is true. - `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). By using Lets Encrypt **you must consent** to their [terms of service](https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf). diff --git a/modules/graceful/server.go b/modules/graceful/server.go index c3aa2163c15fd..d167893ccd24b 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -89,8 +89,8 @@ func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error { if haProxy { listener = &haproxy.Listener{ Listener: listener, - ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, - AcceptUnknown: setting.HAProxyAcceptUnknown, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, } } srv.listener = listener @@ -154,8 +154,8 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun if haProxy && !haProxyTLSBridging { listener = &haproxy.Listener{ Listener: listener, - ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, - AcceptUnknown: setting.HAProxyAcceptUnknown, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, } } @@ -166,8 +166,8 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun if haProxy && haProxyTLSBridging { listener = &haproxy.Listener{ Listener: listener, - ProxyHeaderTimeout: setting.HAProxyHeaderTimeout, - AcceptUnknown: setting.HAProxyAcceptUnknown, + ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, + AcceptUnknown: setting.ProxyProtocolAcceptUnknown, } } diff --git a/modules/private/internal.go b/modules/private/internal.go index af5b6832b433f..96fd1dfc84201 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -49,7 +49,7 @@ func newInternalRequest(url, method string) *httplib.Request { if err != nil { return conn, err } - if setting.LocalHAProxy { + if setting.LocalUseProxyProtocol { if err = haproxy.WriteLocalProxyHeader(conn); err != nil { _ = conn.Close() return nil, err @@ -58,7 +58,7 @@ func newInternalRequest(url, method string) *httplib.Request { return conn, err }, }) - } else if setting.LocalHAProxy { + } else if setting.LocalUseProxyProtocol { req.SetTransport(&http.Transport{ DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { var d net.Dialer diff --git a/modules/setting/setting.go b/modules/setting/setting.go index 638f86ad3ca6a..e5d6da0b19767 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -77,42 +77,42 @@ var ( AppWorkPath string // Server settings - Protocol Scheme - HAProxy bool // `ini:"HAPROXY"` - HAProxyTLSBridging bool // `ini:"HAPROXY_TLS_BRIDGING"` - HAProxyHeaderTimeout time.Duration - HAProxyAcceptUnknown bool - Domain string - HTTPAddr string - HTTPPort string - LocalURL string - LocalHAProxy bool - RedirectOtherPort bool - RedirectHAProxy bool - PortToRedirect string - OfflineMode bool - CertFile string - KeyFile string - StaticRootPath string - StaticCacheTime time.Duration - EnableGzip bool - LandingPageURL LandingPage - UnixSocketPermission uint32 - EnablePprof bool - PprofDataPath string - EnableLetsEncrypt bool - LetsEncryptTOS bool - LetsEncryptDirectory string - LetsEncryptEmail string - GracefulRestartable bool - GracefulHammerTime time.Duration - StartupTimeout time.Duration - StaticURLPrefix string + Protocol Scheme + UseProxyProtocol bool // `ini:"USE_PROXY_PROTOCOL"` + ProxyProtocolTLSBridging bool // `ini:"PROXY_PROTOCOL_TLS_BRIDGING"` + ProxyProtocolHeaderTimeout time.Duration + ProxyProtocolAcceptUnknown bool + Domain string + HTTPAddr string + HTTPPort string + LocalURL string + LocalUseProxyProtocol bool + RedirectOtherPort bool + RedirectorUseProxyProtocol bool + PortToRedirect string + OfflineMode bool + CertFile string + KeyFile string + StaticRootPath string + StaticCacheTime time.Duration + EnableGzip bool + LandingPageURL LandingPage + UnixSocketPermission uint32 + EnablePprof bool + PprofDataPath string + EnableLetsEncrypt bool + LetsEncryptTOS bool + LetsEncryptDirectory string + LetsEncryptEmail string + GracefulRestartable bool + GracefulHammerTime time.Duration + StartupTimeout time.Duration + StaticURLPrefix string SSH = struct { Disabled bool `ini:"DISABLE_SSH"` StartBuiltinServer bool `ini:"START_SSH_SERVER"` - HAProxy bool `ini:"SSH_SERVER_HAPROXY"` + UseProxyProtocol bool `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"` BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` Domain string `ini:"SSH_DOMAIN"` Port int `ini:"SSH_PORT"` @@ -596,10 +596,10 @@ func NewContext() { } UnixSocketPermission = uint32(UnixSocketPermissionParsed) } - HAProxy = sec.Key("HAPROXY").MustBool(false) - HAProxyTLSBridging = sec.Key("HAPROXY_TLS_BRIDGING").MustBool(false) - HAProxyHeaderTimeout = sec.Key("HAPROXY_HEADER_TIMEOUT").MustDuration(5 * time.Second) - HAProxyAcceptUnknown = sec.Key("HAPROXY_ACCEPT_UNKNOWN").MustBool(false) + UseProxyProtocol = sec.Key("USE_PROXY_PROTOCOL").MustBool(false) + ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false) + ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second) + ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false) EnableLetsEncrypt = sec.Key("ENABLE_LETSENCRYPT").MustBool(false) LetsEncryptTOS = sec.Key("LETSENCRYPT_ACCEPTTOS").MustBool(false) if !LetsEncryptTOS && EnableLetsEncrypt { @@ -655,10 +655,10 @@ func NewContext() { } } LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) - LocalHAProxy = sec.Key("LOCAL_HAPROXY").MustBool(HAProxy) + LocalUseProxyProtocol = sec.Key("LOCAL_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) RedirectOtherPort = sec.Key("REDIRECT_OTHER_PORT").MustBool(false) PortToRedirect = sec.Key("PORT_TO_REDIRECT").MustString("80") - RedirectHAProxy = sec.Key("REDIRECT_HAPROXY").MustBool(HAProxy) + RedirectorUseProxyProtocol = sec.Key("REDIRECTOR_USE_PROXY_PROTOCOL").MustBool(UseProxyProtocol) OfflineMode = sec.Key("OFFLINE_MODE").MustBool() DisableRouterLog = sec.Key("DISABLE_ROUTER_LOG").MustBool() if len(StaticRootPath) == 0 { @@ -709,7 +709,7 @@ func NewContext() { SSH.KeygenPath = sec.Key("SSH_KEYGEN_PATH").MustString("ssh-keygen") SSH.Port = sec.Key("SSH_PORT").MustInt(22) SSH.ListenPort = sec.Key("SSH_LISTEN_PORT").MustInt(SSH.Port) - SSH.HAProxy = sec.Key("SSH_SERVER_HAPROXY").MustBool(false) + SSH.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) // When disable SSH, start builtin server value is ignored. if SSH.Disabled { diff --git a/modules/ssh/ssh_graceful.go b/modules/ssh/ssh_graceful.go index 16afcf64e0a73..1231e5cd04bd5 100644 --- a/modules/ssh/ssh_graceful.go +++ b/modules/ssh/ssh_graceful.go @@ -15,7 +15,7 @@ import ( func listen(server *ssh.Server) { gracefulServer := graceful.NewServer("tcp", server.Addr) - err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.HAProxy) + err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.UseProxyProtocol) if err != nil { select { case <-graceful.GetManager().IsShutdown(): From e8194a5e41f31d498cfa1621ccc9d31784a9747b Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Fri, 19 Feb 2021 21:49:00 +0000 Subject: [PATCH 4/9] lint fix Signed-off-by: Andrew Thornton --- modules/setting/setting.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index bf42fa95ebfc0..d56553744fed0 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -110,7 +110,7 @@ var ( GracefulHammerTime time.Duration StartupTimeout time.Duration StaticURLPrefix string - AbsoluteAssetURL string + AbsoluteAssetURL string SSH = struct { Disabled bool `ini:"DISABLE_SSH"` From af2e349e899972ea9e7591f8db690d6e4835f543 Mon Sep 17 00:00:00 2001 From: 6543 <6543@obermui.de> Date: Tue, 29 Jun 2021 14:26:56 +0200 Subject: [PATCH 5/9] fix --- cmd/web.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/web.go b/cmd/web.go index 202868897e747..fc27f5817d7cd 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -229,6 +229,8 @@ func listen(m http.Handler, handleRedirector bool) error { NoHTTPRedirector() } err = runFCGI("unix", listenAddr, "Web", context2.ClearHandler(m), setting.UseProxyProtocol) + default: + log.Fatal("Invalid protocol: %s", setting.Protocol) } if err != nil { log.Critical("Failed to start server: %v", err) From d5bc6eb26683f8e476f432181e90f690443fd770 Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 16 Aug 2022 19:46:15 +0100 Subject: [PATCH 6/9] Update custom/conf/app.example.ini --- custom/conf/app.example.ini | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/custom/conf/app.example.ini b/custom/conf/app.example.ini index 5fcb3bff9f739..0949c3d399f5d 100644 --- a/custom/conf/app.example.ini +++ b/custom/conf/app.example.ini @@ -1538,17 +1538,32 @@ ROUTER = console ;; Prefix displayed before subject in mail ;SUBJECT_PREFIX = ;; -;; Mail server -;; Gmail: smtp.gmail.com:587 -;; QQ: smtp.qq.com:465 -;; As per RFC 8314 using Implicit TLS/SMTPS on port 465 (if supported) is recommended, -;; otherwise STARTTLS on port 587 should be used. -;HOST = -;; -;; Disable HELO operation when hostnames are different. -;DISABLE_HELO = -;; -;; Custom hostname for HELO operation, if no value is provided, one is retrieved from system. +;; Mail server protocol. One of "smtp", "smtps", "smtp+startls", "smtp+unix", "sendmail", "dummy". +;; - sendmail: use the operating system's `sendmail` command instead of SMTP. This is common on Linux systems. +;; - dummy: send email messages to the log as a testing phase. +;; If your provider does not explicitly say which protocol it uses but does provide a port, +;; you can set SMTP_PORT instead and this will be inferred. +;; (Before 1.18, see the notice, this was controlled via MAILER_TYPE and IS_TLS_ENABLED.) +;PROTOCOL = +;; +;; Mail server address, e.g. smtp.gmail.com. +;; For smtp+unix, this should be a path to a unix socket instead. +;; (Before 1.18, see the notice, this was combined with SMTP_PORT as HOST.) +;SMTP_ADDR = +;; +;; Mail server port. Common ports are: +;; 25: insecure SMTP +;; 465: SMTP Secure +;; 587: StartTLS +;; If no protocol is specified, it will be inferred by this setting. +;; (Before 1.18, this was combined with SMTP_ADDR as HOST.) +;SMTP_PORT = +;; +;; Enable HELO operation. Defaults to true. +;ENABLE_HELO = true +;; +;; Custom hostname for HELO operation. +;; If no value is provided, one is retrieved from system. ;HELO_HOSTNAME = ;; ;; If set to `true`, completely ignores server certificate validation errors. From 95e0f037827b70eb23dadc3475723b8f2a921e8d Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 16 Aug 2022 19:50:12 +0100 Subject: [PATCH 7/9] Update modules/setting/setting.go --- modules/setting/setting.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/setting/setting.go b/modules/setting/setting.go index dd3b2600b81bc..931b6523ea9af 100644 --- a/modules/setting/setting.go +++ b/modules/setting/setting.go @@ -728,10 +728,6 @@ func loadFromConf(allowEmpty bool, extraConfig string) { ProxyProtocolTLSBridging = sec.Key("PROXY_PROTOCOL_TLS_BRIDGING").MustBool(false) ProxyProtocolHeaderTimeout = sec.Key("PROXY_PROTOCOL_HEADER_TIMEOUT").MustDuration(5 * time.Second) ProxyProtocolAcceptUnknown = sec.Key("PROXY_PROTOCOL_ACCEPT_UNKNOWN").MustBool(false) - SSLMinimumVersion = sec.Key("SSL_MIN_VERSION").MustString("") - SSLMaximumVersion = sec.Key("SSL_MAX_VERSION").MustString("") - SSLCurvePreferences = sec.Key("SSL_CURVE_PREFERENCES").Strings(",") - SSLCipherSuites = sec.Key("SSL_CIPHER_SUITES").Strings(",") GracefulRestartable = sec.Key("ALLOW_GRACEFUL_RESTARTS").MustBool(true) GracefulHammerTime = sec.Key("GRACEFUL_HAMMER_TIME").MustDuration(60 * time.Second) StartupTimeout = sec.Key("STARTUP_TIMEOUT").MustDuration(0 * time.Second) From e7184698572b5a2e6ec2990b21c1f4c6e0ef619f Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 17 Aug 2022 17:44:00 +0100 Subject: [PATCH 8/9] placate the linter Signed-off-by: Andrew Thornton --- modules/haproxy/conn.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/haproxy/conn.go b/modules/haproxy/conn.go index b39a26351108d..2ad02fb387064 100644 --- a/modules/haproxy/conn.go +++ b/modules/haproxy/conn.go @@ -172,7 +172,7 @@ func (p *Conn) readProxyHeaderOnce() (err error) { p.bufReader = bufio.NewReader(p.conn) } }) - return + return err } func (p *Conn) readProxyHeader() error { From 9c2d512ccb3fd47169982198e21da392e35bbf8f Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Wed, 17 Aug 2022 19:21:50 +0100 Subject: [PATCH 9/9] as per silverwind Signed-off-by: Andrew Thornton --- cmd/web_graceful.go | 8 +++---- cmd/web_https.go | 8 +++---- modules/graceful/server.go | 22 +++++++++---------- modules/graceful/server_http.go | 8 +++---- modules/private/internal.go | 6 ++--- modules/{haproxy => proxyprotocol}/conn.go | 4 ++-- modules/{haproxy => proxyprotocol}/errors.go | 2 +- .../{haproxy => proxyprotocol}/listener.go | 2 +- modules/{haproxy => proxyprotocol}/util.go | 6 ++--- 9 files changed, 33 insertions(+), 33 deletions(-) rename modules/{haproxy => proxyprotocol}/conn.go (99%) rename modules/{haproxy => proxyprotocol}/errors.go (98%) rename modules/{haproxy => proxyprotocol}/listener.go (98%) rename modules/{haproxy => proxyprotocol}/util.go (64%) diff --git a/cmd/web_graceful.go b/cmd/web_graceful.go index f070eb111fc0b..ba88cc59c21cc 100644 --- a/cmd/web_graceful.go +++ b/cmd/web_graceful.go @@ -15,8 +15,8 @@ import ( "code.gitea.io/gitea/modules/setting" ) -func runHTTP(network, listenAddr, name string, m http.Handler, haProxy bool) error { - return graceful.HTTPListenAndServe(network, listenAddr, name, m, haProxy) +func runHTTP(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error { + return graceful.HTTPListenAndServe(network, listenAddr, name, m, useProxyProtocol) } // NoHTTPRedirector tells our cleanup routine that we will not be using a fallback http redirector @@ -36,7 +36,7 @@ func NoInstallListener() { graceful.GetManager().InformCleanup() } -func runFCGI(network, listenAddr, name string, m http.Handler, haProxy bool) error { +func runFCGI(network, listenAddr, name string, m http.Handler, useProxyProtocol bool) error { // This needs to handle stdin as fcgi point fcgiServer := graceful.NewServer(network, listenAddr, name) @@ -47,7 +47,7 @@ func runFCGI(network, listenAddr, name string, m http.Handler, haProxy bool) err } m.ServeHTTP(resp, req) })) - }, haProxy) + }, useProxyProtocol) if err != nil { log.Fatal("Failed to start FCGI main server: %v", err) } diff --git a/cmd/web_https.go b/cmd/web_https.go index 671f92dfcac3e..aac11517a69f8 100644 --- a/cmd/web_https.go +++ b/cmd/web_https.go @@ -136,7 +136,7 @@ var ( // be provided. If the certificate is signed by a certificate authority, the // certFile should be the concatenation of the server's certificate followed by the // CA's certificate. -func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, haProxy, haProxyTLSBridging bool) error { +func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { tlsConfig := &tls.Config{} if tlsConfig.NextProtos == nil { tlsConfig.NextProtos = []string{"h2", "http/1.1"} @@ -184,9 +184,9 @@ func runHTTPS(network, listenAddr, name, certFile, keyFile string, m http.Handle return err } - return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, haProxy, haProxyTLSBridging) + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) } -func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, haProxy, haProxyTLSBridging bool) error { - return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, haProxy, haProxyTLSBridging) +func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { + return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) } diff --git a/modules/graceful/server.go b/modules/graceful/server.go index e2ea72e7b74d7..30a460a943c51 100644 --- a/modules/graceful/server.go +++ b/modules/graceful/server.go @@ -15,8 +15,8 @@ import ( "syscall" "time" - "code.gitea.io/gitea/modules/haproxy" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/proxyprotocol" "code.gitea.io/gitea/modules/setting" ) @@ -80,7 +80,7 @@ func NewServer(network, address, name string) *Server { // ListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error { +func (srv *Server) ListenAndServe(serve ServeFunction, useProxyProtocol bool) error { go srv.awaitShutdown() listener, err := GetListener(srv.network, srv.address) @@ -92,9 +92,9 @@ func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error { // we need to wrap the listener to take account of our lifecycle listener = newWrappedListener(listener, srv) - // Now we need to take account of HAProxy settings... - if haProxy { - listener = &haproxy.Listener{ + // Now we need to take account of ProxyProtocol settings... + if useProxyProtocol { + listener = &proxyprotocol.Listener{ Listener: listener, ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, AcceptUnknown: setting.ProxyProtocolAcceptUnknown, @@ -109,7 +109,7 @@ func (srv *Server) ListenAndServe(serve ServeFunction, haProxy bool) error { // ListenAndServeTLSConfig listens on the provided network address and then calls // Serve to handle requests on incoming TLS connections. -func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, haProxy, haProxyTLSBridging bool) error { +func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, useProxyProtocol, proxyProtocolTLSBridging bool) error { go srv.awaitShutdown() if tlsConfig.MinVersion == 0 { @@ -125,9 +125,9 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun // we need to wrap the listener to take account of our lifecycle listener = newWrappedListener(listener, srv) - // 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 - if haProxy && !haProxyTLSBridging { - listener = &haproxy.Listener{ + // Now we need to take account of ProxyProtocol settings... If we're not bridging then we expect that the proxy will forward the connection to us + if useProxyProtocol && !proxyProtocolTLSBridging { + listener = &proxyprotocol.Listener{ Listener: listener, ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, AcceptUnknown: setting.ProxyProtocolAcceptUnknown, @@ -138,8 +138,8 @@ func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFun listener = tls.NewListener(listener, tlsConfig) // Now if we're bridging then we need the proxy to tell us who we're bridging for... - if haProxy && haProxyTLSBridging { - listener = &haproxy.Listener{ + if useProxyProtocol && proxyProtocolTLSBridging { + listener = &proxyprotocol.Listener{ Listener: listener, ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, AcceptUnknown: setting.ProxyProtocolAcceptUnknown, diff --git a/modules/graceful/server_http.go b/modules/graceful/server_http.go index 828cddcfb44e7..8ab2bdf41ff9a 100644 --- a/modules/graceful/server_http.go +++ b/modules/graceful/server_http.go @@ -28,14 +28,14 @@ func newHTTPServer(network, address, name string, handler http.Handler) (*Server // HTTPListenAndServe listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServe(network, address, name string, handler http.Handler, haProxy bool) error { +func HTTPListenAndServe(network, address, name string, handler http.Handler, useProxyProtocol bool) error { server, lHandler := newHTTPServer(network, address, name, handler) - return server.ListenAndServe(lHandler, haProxy) + return server.ListenAndServe(lHandler, useProxyProtocol) } // HTTPListenAndServeTLSConfig listens on the provided network address and then calls Serve // to handle requests on incoming connections. -func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler, haProxy, haProxyTLSBridging bool) error { +func HTTPListenAndServeTLSConfig(network, address, name string, tlsConfig *tls.Config, handler http.Handler, useProxyProtocol, proxyProtocolTLSBridging bool) error { server, lHandler := newHTTPServer(network, address, name, handler) - return server.ListenAndServeTLSConfig(tlsConfig, lHandler, haProxy, haProxyTLSBridging) + return server.ListenAndServeTLSConfig(tlsConfig, lHandler, useProxyProtocol, proxyProtocolTLSBridging) } diff --git a/modules/private/internal.go b/modules/private/internal.go index 6e0c8852c686c..2ea516ba80e19 100644 --- a/modules/private/internal.go +++ b/modules/private/internal.go @@ -11,10 +11,10 @@ import ( "net" "net/http" - "code.gitea.io/gitea/modules/haproxy" "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/proxyprotocol" "code.gitea.io/gitea/modules/setting" ) @@ -56,7 +56,7 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques return conn, err } if setting.LocalUseProxyProtocol { - if err = haproxy.WriteLocalProxyHeader(conn); err != nil { + if err = proxyprotocol.WriteLocalHeader(conn); err != nil { _ = conn.Close() return nil, err } @@ -72,7 +72,7 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques if err != nil { return conn, err } - if err = haproxy.WriteLocalProxyHeader(conn); err != nil { + if err = proxyprotocol.WriteLocalHeader(conn); err != nil { _ = conn.Close() return nil, err } diff --git a/modules/haproxy/conn.go b/modules/proxyprotocol/conn.go similarity index 99% rename from modules/haproxy/conn.go rename to modules/proxyprotocol/conn.go index 2ad02fb387064..10333b204d65f 100644 --- a/modules/haproxy/conn.go +++ b/modules/proxyprotocol/conn.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package haproxy +package proxyprotocol import ( "bufio" @@ -41,7 +41,7 @@ type Conn struct { } // NewConn is used to wrap a net.Conn speaking the proxy protocol into -// a haproxy.Conn +// a proxyprotocol.Conn func NewConn(conn net.Conn, timeout time.Duration) *Conn { pConn := &Conn{ bufReader: bufio.NewReader(conn), diff --git a/modules/haproxy/errors.go b/modules/proxyprotocol/errors.go similarity index 98% rename from modules/haproxy/errors.go rename to modules/proxyprotocol/errors.go index da42e51a76d8f..2acf9d84b0ca0 100644 --- a/modules/haproxy/errors.go +++ b/modules/proxyprotocol/errors.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package haproxy +package proxyprotocol import "fmt" diff --git a/modules/haproxy/listener.go b/modules/proxyprotocol/listener.go similarity index 98% rename from modules/haproxy/listener.go rename to modules/proxyprotocol/listener.go index 8c9e26f0ae1f4..64d9b323e512e 100644 --- a/modules/haproxy/listener.go +++ b/modules/proxyprotocol/listener.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package haproxy +package proxyprotocol import ( "net" diff --git a/modules/haproxy/util.go b/modules/proxyprotocol/util.go similarity index 64% rename from modules/haproxy/util.go rename to modules/proxyprotocol/util.go index d1330cf03dd99..b12771b686a86 100644 --- a/modules/haproxy/util.go +++ b/modules/proxyprotocol/util.go @@ -2,14 +2,14 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package haproxy +package proxyprotocol import "io" var localHeader = append(v2Prefix, '\x20', '\x00', '\x00', '\x00', '\x00') -// WriteLocalProxyHeader will write the HAProxy Header for a local connection to the provided writer -func WriteLocalProxyHeader(w io.Writer) error { +// WriteLocalHeader will write the ProxyProtocol Header for a local connection to the provided writer +func WriteLocalHeader(w io.Writer) error { _, err := w.Write(localHeader) return err }