This PR adds functionality to allow Gitea to sit behind an HAProxy and HAProxy protocolled connections directly. Fix #7508 Signed-off-by: Andrew Thornton <art27@cantab.net>tags/v1.18.0-rc0
@@ -76,7 +76,7 @@ func runHTTPRedirector() { | |||
http.Redirect(w, r, target, http.StatusTemporaryRedirect) | |||
}) | |||
err := runHTTP("tcp", source, "HTTP Redirector", handler) | |||
err := runHTTP("tcp", source, "HTTP Redirector", handler, setting.RedirectorUseProxyProtocol) | |||
if err != nil { | |||
log.Fatal("Failed to start port redirection: %v", err) | |||
} | |||
@@ -231,40 +231,38 @@ func listen(m http.Handler, handleRedirector bool) error { | |||
if handleRedirector { | |||
NoHTTPRedirector() | |||
} | |||
err = runHTTP("tcp", listenAddr, "Web", m) | |||
err = runHTTP("tcp", listenAddr, "Web", m, setting.UseProxyProtocol) | |||
case setting.HTTPS: | |||
if setting.EnableAcme { | |||
err = runACME(listenAddr, m) | |||
break | |||
} else { | |||
if handleRedirector { | |||
if setting.RedirectOtherPort { | |||
go runHTTPRedirector() | |||
} else { | |||
NoHTTPRedirector() | |||
} | |||
} | |||
if handleRedirector { | |||
if setting.RedirectOtherPort { | |||
go runHTTPRedirector() | |||
} else { | |||
NoHTTPRedirector() | |||
} | |||
err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m) | |||
} | |||
err = runHTTPS("tcp", listenAddr, "Web", setting.CertFile, setting.KeyFile, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) | |||
case setting.FCGI: | |||
if handleRedirector { | |||
NoHTTPRedirector() | |||
} | |||
err = runFCGI("tcp", listenAddr, "FCGI Web", m) | |||
err = runFCGI("tcp", listenAddr, "FCGI Web", m, setting.UseProxyProtocol) | |||
case setting.HTTPUnix: | |||
if handleRedirector { | |||
NoHTTPRedirector() | |||
} | |||
err = runHTTP("unix", listenAddr, "Web", m) | |||
err = runHTTP("unix", listenAddr, "Web", m, setting.UseProxyProtocol) | |||
case setting.FCGIUnix: | |||
if handleRedirector { | |||
NoHTTPRedirector() | |||
} | |||
err = runFCGI("unix", listenAddr, "Web", m) | |||
err = runFCGI("unix", listenAddr, "Web", m, setting.UseProxyProtocol) | |||
default: | |||
log.Fatal("Invalid protocol: %s", setting.Protocol) | |||
} | |||
if err != nil { | |||
log.Critical("Failed to start server: %v", err) | |||
} |
@@ -113,14 +113,14 @@ func runACME(listenAddr string, m http.Handler) error { | |||
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) | |||
err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(http.HandlerFunc(runLetsEncryptFallbackHandler))) | |||
err := runHTTP("tcp", setting.HTTPAddr+":"+setting.PortToRedirect, "Let's Encrypt HTTP Challenge", myACME.HTTPChallengeHandler(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, "Web", tlsConfig, m) | |||
return runHTTPSWithTLSConfig("tcp", listenAddr, "Web", tlsConfig, m, setting.UseProxyProtocol, setting.ProxyProtocolTLSBridging) | |||
} | |||
func runLetsEncryptFallbackHandler(w http.ResponseWriter, r *http.Request) { |
@@ -15,8 +15,8 @@ import ( | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
func runHTTP(network, listenAddr, name string, m http.Handler) error { | |||
return graceful.HTTPListenAndServe(network, listenAddr, name, m) | |||
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) 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) error { | |||
} | |||
m.ServeHTTP(resp, req) | |||
})) | |||
}) | |||
}, useProxyProtocol) | |||
if err != nil { | |||
log.Fatal("Failed to start FCGI main server: %v", err) | |||
} |
@@ -129,14 +129,14 @@ var ( | |||
defaultCiphersChaChaFirst = append(defaultCiphersChaCha, defaultCiphersAES...) | |||
) | |||
// runHTTPs listens on the provided network address and then calls | |||
// runHTTPS listens on the provided network address and then calls | |||
// Serve to handle requests on incoming TLS connections. | |||
// | |||
// Filenames containing a certificate and matching private key for the server must | |||
// 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) 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) | |||
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m, useProxyProtocol, proxyProtocolTLSBridging) | |||
} | |||
func runHTTPSWithTLSConfig(network, listenAddr, name string, tlsConfig *tls.Config, m http.Handler) error { | |||
return graceful.HTTPListenAndServeTLSConfig(network, listenAddr, name, tlsConfig, m) | |||
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) | |||
} |
@@ -29,6 +29,18 @@ RUN_MODE = ; prod | |||
;; The protocol the server listens on. One of 'http', 'https', 'unix' or 'fcgi'. Defaults to 'http' | |||
;PROTOCOL = http | |||
;; | |||
;; 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 | |||
;; | |||
;; Set the domain for the server | |||
;DOMAIN = localhost | |||
;; | |||
@@ -51,6 +63,8 @@ RUN_MODE = ; prod | |||
;REDIRECT_OTHER_PORT = false | |||
;PORT_TO_REDIRECT = 80 | |||
;; | |||
;; expect PROXY protocol header on connections to https redirector. | |||
;REDIRECTOR_USE_PROXY_PROTOCOL = %(USE_PROXY_PROTOCOL) | |||
;; Minimum and maximum supported TLS versions | |||
;SSL_MIN_VERSION=TLSv1.2 | |||
;SSL_MAX_VERSION= | |||
@@ -76,13 +90,19 @@ RUN_MODE = ; prod | |||
;; 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 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 | |||
;; | |||
;; Username to use for the builtin SSH server. | |||
;; 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 = %(RUN_USER)s | |||
;; | |||
;; Domain name to be exposed in clone URL |
@@ -238,6 +238,10 @@ The following configuration set `Content-Type: application/vnd.android.package-a | |||
## Server (`server`) | |||
- `PROTOCOL`: **http**: \[http, https, fcgi, http+unix, fcgi+unix\] | |||
- `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. | |||
@@ -262,12 +266,15 @@ The following configuration set `Content-Type: application/vnd.android.package-a | |||
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 `http+unix`. | |||
- `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. | |||
- `PER_WRITE_TIMEOUT`: **30s**: Timeout for any write to the connection. (Set to -1 to | |||
disable all timeouts.) | |||
- `PER_WRITE_PER_KB_TIMEOUT`: **10s**: Timeout per Kb written to connections. | |||
- `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_USE_PROXY_PROTOCOL`: **false**: Expect PROXY protocol header on connections to the built-in SSH Server. | |||
- `BUILTIN_SSH_SERVER_USER`: **%(RUN_USER)s**: Username to use for the built-in SSH Server. | |||
- `SSH_USER`: **%(BUILTIN_SSH_SERVER_USER)**: SSH username displayed in clone URLs. This is only for people who configure the SSH server themselves; in most cases, you want to leave this blank and modify the `BUILTIN_SSH_SERVER_USER`. | |||
- `SSH_DOMAIN`: **%(DOMAIN)s**: Domain name of this server, used for displayed clone URL. | |||
@@ -313,6 +320,7 @@ The following configuration set `Content-Type: application/vnd.android.package-a | |||
- `LFS_LOCKS_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. | |||
- `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. | |||
- `SSL_MIN_VERSION`: **TLSv1.2**: Set the minimum version of ssl support. | |||
- `SSL_MAX_VERSION`: **\<empty\>**: Set the maximum version of ssl support. |
@@ -16,6 +16,7 @@ import ( | |||
"time" | |||
"code.gitea.io/gitea/modules/log" | |||
"code.gitea.io/gitea/modules/proxyprotocol" | |||
"code.gitea.io/gitea/modules/setting" | |||
) | |||
@@ -79,16 +80,27 @@ 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) error { | |||
func (srv *Server) ListenAndServe(serve ServeFunction, useProxyProtocol 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 ProxyProtocol settings... | |||
if useProxyProtocol { | |||
listener = &proxyprotocol.Listener{ | |||
Listener: listener, | |||
ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, | |||
AcceptUnknown: setting.ProxyProtocolAcceptUnknown, | |||
} | |||
} | |||
srv.listener = listener | |||
srv.BeforeBegin(srv.network, srv.address) | |||
@@ -97,22 +109,44 @@ func (srv *Server) ListenAndServe(serve ServeFunction) 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) error { | |||
func (srv *Server) ListenAndServeTLSConfig(tlsConfig *tls.Config, serve ServeFunction, useProxyProtocol, proxyProtocolTLSBridging bool) error { | |||
go srv.awaitShutdown() | |||
if tlsConfig.MinVersion == 0 { | |||
tlsConfig.MinVersion = tls.VersionTLS12 | |||
} | |||
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 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, | |||
} | |||
} | |||
// 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 useProxyProtocol && proxyProtocolTLSBridging { | |||
listener = &proxyprotocol.Listener{ | |||
Listener: listener, | |||
ProxyHeaderTimeout: setting.ProxyProtocolHeaderTimeout, | |||
AcceptUnknown: setting.ProxyProtocolAcceptUnknown, | |||
} | |||
} | |||
srv.listener = listener | |||
srv.BeforeBegin(srv.network, srv.address) | |||
return srv.Serve(serve) |
@@ -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) error { | |||
func HTTPListenAndServe(network, address, name string, handler http.Handler, useProxyProtocol bool) error { | |||
server, lHandler := newHTTPServer(network, address, name, handler) | |||
return server.ListenAndServe(lHandler) | |||
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) 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) | |||
return server.ListenAndServeTLSConfig(tlsConfig, lHandler, useProxyProtocol, proxyProtocolTLSBridging) | |||
} |
@@ -14,6 +14,7 @@ import ( | |||
"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" | |||
) | |||
@@ -50,7 +51,32 @@ func newInternalRequest(ctx context.Context, url, method string) *httplib.Reques | |||
req.SetTransport(&http.Transport{ | |||
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { | |||
var d net.Dialer | |||
return d.DialContext(ctx, "unix", setting.HTTPAddr) | |||
conn, err := d.DialContext(ctx, "unix", setting.HTTPAddr) | |||
if err != nil { | |||
return conn, err | |||
} | |||
if setting.LocalUseProxyProtocol { | |||
if err = proxyprotocol.WriteLocalHeader(conn); err != nil { | |||
_ = conn.Close() | |||
return nil, err | |||
} | |||
} | |||
return conn, err | |||
}, | |||
}) | |||
} else if setting.LocalUseProxyProtocol { | |||
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 = proxyprotocol.WriteLocalHeader(conn); err != nil { | |||
_ = conn.Close() | |||
return nil, err | |||
} | |||
return conn, err | |||
}, | |||
}) | |||
} |
@@ -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 proxyprotocol | |||
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 proxyprotocol.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 err | |||
} | |||
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 <type> <remote addr> <local addr> <remote port> <local port>) | |||
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 | |||
} |
@@ -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 proxyprotocol | |||
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) | |||
} |
@@ -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 proxyprotocol | |||
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() | |||
} |
@@ -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 proxyprotocol | |||
import "io" | |||
var localHeader = append(v2Prefix, '\x20', '\x00', '\x00', '\x00', '\x00') | |||
// 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 | |||
} |
@@ -94,45 +94,52 @@ var ( | |||
LocalURL string | |||
// Server settings | |||
Protocol Scheme | |||
Domain string | |||
HTTPAddr string | |||
HTTPPort string | |||
RedirectOtherPort bool | |||
PortToRedirect string | |||
OfflineMode bool | |||
CertFile string | |||
KeyFile string | |||
StaticRootPath string | |||
StaticCacheTime time.Duration | |||
EnableGzip bool | |||
LandingPageURL LandingPage | |||
LandingPageCustom string | |||
UnixSocketPermission uint32 | |||
EnablePprof bool | |||
PprofDataPath string | |||
EnableAcme bool | |||
AcmeTOS bool | |||
AcmeLiveDirectory string | |||
AcmeEmail string | |||
AcmeURL string | |||
AcmeCARoot string | |||
SSLMinimumVersion string | |||
SSLMaximumVersion string | |||
SSLCurvePreferences []string | |||
SSLCipherSuites []string | |||
GracefulRestartable bool | |||
GracefulHammerTime time.Duration | |||
StartupTimeout time.Duration | |||
PerWriteTimeout = 30 * time.Second | |||
PerWritePerKbTimeout = 10 * time.Second | |||
StaticURLPrefix string | |||
AbsoluteAssetURL 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 | |||
LocalUseProxyProtocol bool | |||
RedirectOtherPort bool | |||
RedirectorUseProxyProtocol bool | |||
PortToRedirect string | |||
OfflineMode bool | |||
CertFile string | |||
KeyFile string | |||
StaticRootPath string | |||
StaticCacheTime time.Duration | |||
EnableGzip bool | |||
LandingPageURL LandingPage | |||
LandingPageCustom string | |||
UnixSocketPermission uint32 | |||
EnablePprof bool | |||
PprofDataPath string | |||
EnableAcme bool | |||
AcmeTOS bool | |||
AcmeLiveDirectory string | |||
AcmeEmail string | |||
AcmeURL string | |||
AcmeCARoot string | |||
SSLMinimumVersion string | |||
SSLMaximumVersion string | |||
SSLCurvePreferences []string | |||
SSLCipherSuites []string | |||
GracefulRestartable bool | |||
GracefulHammerTime time.Duration | |||
StartupTimeout time.Duration | |||
PerWriteTimeout = 30 * time.Second | |||
PerWritePerKbTimeout = 10 * time.Second | |||
StaticURLPrefix string | |||
AbsoluteAssetURL string | |||
SSH = struct { | |||
Disabled bool `ini:"DISABLE_SSH"` | |||
StartBuiltinServer bool `ini:"START_SSH_SERVER"` | |||
BuiltinServerUser string `ini:"BUILTIN_SSH_SERVER_USER"` | |||
UseProxyProtocol bool `ini:"SSH_SERVER_USE_PROXY_PROTOCOL"` | |||
Domain string `ini:"SSH_DOMAIN"` | |||
Port int `ini:"SSH_PORT"` | |||
User string `ini:"SSH_USER"` | |||
@@ -717,6 +724,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||
HTTPAddr = filepath.Join(AppWorkPath, HTTPAddr) | |||
} | |||
} | |||
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) | |||
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) | |||
@@ -770,8 +781,10 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||
} | |||
LocalURL = sec.Key("LOCAL_ROOT_URL").MustString(defaultLocalURL) | |||
LocalURL = strings.TrimRight(LocalURL, "/") + "/" | |||
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") | |||
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 { | |||
@@ -836,6 +849,7 @@ func loadFromConf(allowEmpty bool, extraConfig string) { | |||
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.UseProxyProtocol = sec.Key("SSH_SERVER_USE_PROXY_PROTOCOL").MustBool(false) | |||
// When disable SSH, start builtin server value is ignored. | |||
if SSH.Disabled { |
@@ -17,7 +17,7 @@ func listen(server *ssh.Server) { | |||
gracefulServer.PerWriteTimeout = setting.SSH.PerWriteTimeout | |||
gracefulServer.PerWritePerKbTimeout = setting.SSH.PerWritePerKbTimeout | |||
err := gracefulServer.ListenAndServe(server.Serve) | |||
err := gracefulServer.ListenAndServe(server.Serve, setting.SSH.UseProxyProtocol) | |||
if err != nil { | |||
select { | |||
case <-graceful.GetManager().IsShutdown(): |