Add accept-proxy-ip config directive

This allows to set the list of IPs allowed to act as a proxy. This is
only used for WebSockets right now, but will be expanded to TCP as well
once the PROXY protocol is supported.
This commit is contained in:
Simon Ser 2020-07-22 17:03:01 +02:00
parent b0bf012bbc
commit ef2dd479bf
No known key found for this signature in database
GPG Key ID: 0FDE7BE0E88F5E48
3 changed files with 65 additions and 21 deletions

View File

@ -4,23 +4,48 @@ import (
"bufio"
"fmt"
"io"
"net"
"os"
"github.com/google/shlex"
)
type IPSet []*net.IPNet
func (set IPSet) Contains(ip net.IP) bool {
for _, n := range set {
if n.Contains(ip) {
return true
}
}
return false
}
// loopbackIPs contains the loopback networks 127.0.0.0/8 and ::1/128.
var loopbackIPs = IPSet{
&net.IPNet{
IP: net.IP{127, 0, 0, 0},
Mask: net.CIDRMask(8, 32),
},
&net.IPNet{
IP: net.IPv6loopback,
Mask: net.CIDRMask(128, 128),
},
}
type TLS struct {
CertPath, KeyPath string
}
type Server struct {
Listen []string
Hostname string
TLS *TLS
SQLDriver string
SQLSource string
LogPath string
HTTPOrigins []string
Listen []string
Hostname string
TLS *TLS
SQLDriver string
SQLSource string
LogPath string
HTTPOrigins []string
AcceptProxyIPs IPSet
}
func Defaults() *Server {
@ -29,9 +54,10 @@ func Defaults() *Server {
hostname = "localhost"
}
return &Server{
Hostname: hostname,
SQLDriver: "sqlite3",
SQLSource: "soju.db",
Hostname: hostname,
SQLDriver: "sqlite3",
SQLSource: "soju.db",
AcceptProxyIPs: loopbackIPs,
}
}
@ -93,6 +119,15 @@ func Parse(r io.Reader) (*Server, error) {
}
case "http-origin":
srv.HTTPOrigins = append(srv.HTTPOrigins, d.Params...)
case "accept-proxy-ip":
srv.AcceptProxyIPs = nil
for _, s := range d.Params {
_, n, err := net.ParseCIDR(s)
if err != nil {
return nil, fmt.Errorf("directive %q: failed to parse CIDR: %v", d.Name, err)
}
srv.AcceptProxyIPs = append(srv.AcceptProxyIPs, n)
}
default:
return nil, fmt.Errorf("unknown directive %q", d.Name)
}

View File

@ -109,6 +109,12 @@ The following directives are supported:
List of allowed HTTP origins for WebSocket listeners. The parameters are
interpreted as shell patterns, see *glob*(7).
*accept-proxy-ip* <cidr...>
Allow the specified IPs to act as a proxy. Proxys have the ability to
overwrite the remote and local connection addresses (via the X-Forwarded-*
HTTP header fields). By default, the loopback addresses 127.0.0.0/8 and
::1/128 are accepted.
# IRC SERVICE
soju exposes an IRC service called *BouncerServ* to manage the bouncer.

View File

@ -11,6 +11,8 @@ import (
"gopkg.in/irc.v3"
"nhooyr.io/websocket"
"git.sr.ht/~emersion/soju/config"
)
// TODO: make configurable
@ -41,13 +43,14 @@ func (l *prefixLogger) Printf(format string, v ...interface{}) {
}
type Server struct {
Hostname string
Logger Logger
RingCap int
HistoryLimit int
LogPath string
Debug bool
HTTPOrigins []string
Hostname string
Logger Logger
RingCap int
HistoryLimit int
LogPath string
Debug bool
HTTPOrigins []string
AcceptProxyIPs config.IPSet
db *DB
@ -153,19 +156,19 @@ func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request) {
return
}
isLoopback := false
isProxy := false
if host, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
if ip := net.ParseIP(host); ip != nil {
isLoopback = ip.IsLoopback()
isProxy = s.AcceptProxyIPs.Contains(ip)
}
}
// Only trust X-Forwarded-* header fields if this is a loopback connection,
// Only trust X-Forwarded-* header fields if this is a trusted proxy IP
// to prevent users from spoofing the remote address
remoteAddr := req.RemoteAddr
forwardedHost := req.Header.Get("X-Forwarded-For")
forwardedPort := req.Header.Get("X-Forwarded-Port")
if isLoopback && forwardedHost != "" && forwardedPort != "" {
if isProxy && forwardedHost != "" && forwardedPort != "" {
remoteAddr = net.JoinHostPort(forwardedHost, forwardedPort)
}