diff options
author | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
---|---|---|
committer | Daniel Baumann <daniel@debian.org> | 2024-10-18 20:33:49 +0200 |
commit | dd136858f1ea40ad3c94191d647487fa4f31926c (patch) | |
tree | 58fec94a7b2a12510c9664b21793f1ed560c6518 /modules/proxy | |
parent | Initial commit. (diff) | |
download | forgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.tar.xz forgejo-dd136858f1ea40ad3c94191d647487fa4f31926c.zip |
Adding upstream version 9.0.0.
Signed-off-by: Daniel Baumann <daniel@debian.org>
Diffstat (limited to '')
-rw-r--r-- | modules/proxy/proxy.go | 98 | ||||
-rw-r--r-- | modules/proxyprotocol/conn.go | 505 | ||||
-rw-r--r-- | modules/proxyprotocol/errors.go | 44 | ||||
-rw-r--r-- | modules/proxyprotocol/listener.go | 46 | ||||
-rw-r--r-- | modules/proxyprotocol/util.go | 14 |
5 files changed, 707 insertions, 0 deletions
diff --git a/modules/proxy/proxy.go b/modules/proxy/proxy.go new file mode 100644 index 0000000..1a6bdad --- /dev/null +++ b/modules/proxy/proxy.go @@ -0,0 +1,98 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package proxy + +import ( + "net/http" + "net/url" + "os" + "strings" + "sync" + + "code.gitea.io/gitea/modules/log" + "code.gitea.io/gitea/modules/setting" + + "github.com/gobwas/glob" +) + +var ( + once sync.Once + hostMatchers []glob.Glob +) + +// GetProxyURL returns proxy url +func GetProxyURL() string { + if !setting.Proxy.Enabled { + return "" + } + + if setting.Proxy.ProxyURL == "" { + if os.Getenv("http_proxy") != "" { + return os.Getenv("http_proxy") + } + return os.Getenv("https_proxy") + } + return setting.Proxy.ProxyURL +} + +// Match return true if url needs to be proxied +func Match(u string) bool { + if !setting.Proxy.Enabled { + return false + } + + // enforce do once + Proxy() + + for _, v := range hostMatchers { + if v.Match(u) { + return true + } + } + return false +} + +// Proxy returns the system proxy +func Proxy() func(req *http.Request) (*url.URL, error) { + if !setting.Proxy.Enabled { + return func(req *http.Request) (*url.URL, error) { + return nil, nil + } + } + if setting.Proxy.ProxyURL == "" { + return http.ProxyFromEnvironment + } + + once.Do(func() { + for _, h := range setting.Proxy.ProxyHosts { + if g, err := glob.Compile(h); err == nil { + hostMatchers = append(hostMatchers, g) + } else { + log.Error("glob.Compile %s failed: %v", h, err) + } + } + }) + + return func(req *http.Request) (*url.URL, error) { + for _, v := range hostMatchers { + if v.Match(req.URL.Host) { + return http.ProxyURL(setting.Proxy.ProxyURLFixed)(req) + } + } + return http.ProxyFromEnvironment(req) + } +} + +// EnvWithProxy returns os.Environ(), with a https_proxy env, if the given url +// needs to be proxied. +func EnvWithProxy(u *url.URL) []string { + envs := os.Environ() + if strings.EqualFold(u.Scheme, "http") || strings.EqualFold(u.Scheme, "https") { + if Match(u.Host) { + envs = append(envs, "https_proxy="+GetProxyURL()) + } + } + + return envs +} diff --git a/modules/proxyprotocol/conn.go b/modules/proxyprotocol/conn.go new file mode 100644 index 0000000..f437f13 --- /dev/null +++ b/modules/proxyprotocol/conn.go @@ -0,0 +1,505 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +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 +} diff --git a/modules/proxyprotocol/errors.go b/modules/proxyprotocol/errors.go new file mode 100644 index 0000000..5439a86 --- /dev/null +++ b/modules/proxyprotocol/errors.go @@ -0,0 +1,44 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +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) +} diff --git a/modules/proxyprotocol/listener.go b/modules/proxyprotocol/listener.go new file mode 100644 index 0000000..ec85c42 --- /dev/null +++ b/modules/proxyprotocol/listener.go @@ -0,0 +1,46 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +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() +} diff --git a/modules/proxyprotocol/util.go b/modules/proxyprotocol/util.go new file mode 100644 index 0000000..a280663 --- /dev/null +++ b/modules/proxyprotocol/util.go @@ -0,0 +1,14 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +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 +} |