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" "bufio"
"fmt" "fmt"
"io" "io"
"net"
"os" "os"
"github.com/google/shlex" "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 { type TLS struct {
CertPath, KeyPath string CertPath, KeyPath string
} }
type Server struct { type Server struct {
Listen []string Listen []string
Hostname string Hostname string
TLS *TLS TLS *TLS
SQLDriver string SQLDriver string
SQLSource string SQLSource string
LogPath string LogPath string
HTTPOrigins []string HTTPOrigins []string
AcceptProxyIPs IPSet
} }
func Defaults() *Server { func Defaults() *Server {
@ -29,9 +54,10 @@ func Defaults() *Server {
hostname = "localhost" hostname = "localhost"
} }
return &Server{ return &Server{
Hostname: hostname, Hostname: hostname,
SQLDriver: "sqlite3", SQLDriver: "sqlite3",
SQLSource: "soju.db", SQLSource: "soju.db",
AcceptProxyIPs: loopbackIPs,
} }
} }
@ -93,6 +119,15 @@ func Parse(r io.Reader) (*Server, error) {
} }
case "http-origin": case "http-origin":
srv.HTTPOrigins = append(srv.HTTPOrigins, d.Params...) 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: default:
return nil, fmt.Errorf("unknown directive %q", d.Name) 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 List of allowed HTTP origins for WebSocket listeners. The parameters are
interpreted as shell patterns, see *glob*(7). 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 # IRC SERVICE
soju exposes an IRC service called *BouncerServ* to manage the bouncer. soju exposes an IRC service called *BouncerServ* to manage the bouncer.

View File

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