mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-06-09 01:12:25 +02:00
Merge pull request #738 from strukturag/trusted-proxies
Make trusted proxies configurable and default to loopback / private IPs.
This commit is contained in:
commit
c2e93cd92a
|
@ -22,6 +22,7 @@
|
|||
package signaling
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
@ -31,6 +32,19 @@ type AllowedIps struct {
|
|||
allowed []*net.IPNet
|
||||
}
|
||||
|
||||
func (a *AllowedIps) String() string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString("[")
|
||||
for idx, n := range a.allowed {
|
||||
if idx > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(n.String())
|
||||
}
|
||||
b.WriteString("]")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (a *AllowedIps) Empty() bool {
|
||||
return len(a.allowed) == 0
|
||||
}
|
||||
|
@ -99,3 +113,22 @@ func DefaultAllowedIps() *AllowedIps {
|
|||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var (
|
||||
privateIpNets = []string{
|
||||
// Loopback addresses.
|
||||
"127.0.0.0/8",
|
||||
// Private addresses.
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
}
|
||||
)
|
||||
|
||||
func DefaultPrivateIps() *AllowedIps {
|
||||
allowed, err := ParseAllowedIps(strings.Join(privateIpNets, ","))
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("could not parse private ips %+v: %w", privateIpNets, err))
|
||||
}
|
||||
return allowed
|
||||
}
|
||||
|
|
|
@ -34,6 +34,9 @@ func TestAllowedIps(t *testing.T) {
|
|||
if a.Empty() {
|
||||
t.Fatal("should not be empty")
|
||||
}
|
||||
if expected := `[127.0.0.1/32, 192.168.0.1/32, 192.168.1.0/24]`; a.String() != expected {
|
||||
t.Errorf("expected %s, got %s", expected, a.String())
|
||||
}
|
||||
|
||||
allowed := []string{
|
||||
"127.0.0.1",
|
||||
|
|
|
@ -881,15 +881,9 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body
|
|||
}
|
||||
|
||||
func (b *BackendServer) allowStatsAccess(r *http.Request) bool {
|
||||
addr := getRealUserIP(r)
|
||||
if strings.Contains(addr, ":") {
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
}
|
||||
|
||||
addr := b.hub.getRealUserIP(r)
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
if len(ip) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/textproto"
|
||||
|
@ -1737,6 +1738,7 @@ func TestBackendServer_TurnCredentials(t *testing.T) {
|
|||
func TestBackendServer_StatsAllowedIps(t *testing.T) {
|
||||
CatchLogForTest(t)
|
||||
config := goconf.NewConfigFile()
|
||||
config.AddOption("app", "trustedproxies", "1.2.3.4")
|
||||
config.AddOption("stats", "allowed_ips", "127.0.0.1, 192.168.0.1, 192.168.1.1/24")
|
||||
_, backend, _, _, _, _ := CreateBackendServerForTestFromConfig(t, config)
|
||||
|
||||
|
@ -1763,6 +1765,10 @@ func TestBackendServer_StatsAllowedIps(t *testing.T) {
|
|||
t.Errorf("should allow %s", addr)
|
||||
}
|
||||
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
|
||||
r2 := &http.Request{
|
||||
RemoteAddr: "1.2.3.4:12345",
|
||||
Header: http.Header{
|
||||
|
|
47
hub.go
47
hub.go
|
@ -103,6 +103,8 @@ var (
|
|||
|
||||
// Delay after which a "cleared" / "rejected" dialout status should be removed.
|
||||
removeCallStatusTTL = 5 * time.Second
|
||||
|
||||
DefaultTrustedProxies = DefaultPrivateIps()
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -163,6 +165,7 @@ type Hub struct {
|
|||
backendTimeout time.Duration
|
||||
backend *BackendClient
|
||||
|
||||
trustedProxies *AllowedIps
|
||||
geoip *GeoLookup
|
||||
geoipOverrides map[*net.IPNet]string
|
||||
geoipUpdating atomic.Bool
|
||||
|
@ -226,6 +229,19 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer
|
|||
log.Printf("WARNING: Allow subscribing any streams, this is insecure and should only be enabled for testing")
|
||||
}
|
||||
|
||||
trustedProxies, _ := config.GetString("app", "trustedproxies")
|
||||
trustedProxiesIps, err := ParseAllowedIps(trustedProxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !trustedProxiesIps.Empty() {
|
||||
log.Printf("Trusted proxies: %s", trustedProxiesIps)
|
||||
} else {
|
||||
trustedProxiesIps = DefaultTrustedProxies
|
||||
log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps)
|
||||
}
|
||||
|
||||
decodeCaches := make([]*LruCache, 0, numDecodeCaches)
|
||||
for i := 0; i < numDecodeCaches; i++ {
|
||||
decodeCaches = append(decodeCaches, NewLruCache(decodeCacheSize))
|
||||
|
@ -353,6 +369,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer
|
|||
backendTimeout: backendTimeout,
|
||||
backend: backend,
|
||||
|
||||
trustedProxies: trustedProxiesIps,
|
||||
geoip: geoip,
|
||||
geoipOverrides: geoipOverrides,
|
||||
|
||||
|
@ -2512,9 +2529,21 @@ func (h *Hub) GetStats() map[string]interface{} {
|
|||
return result
|
||||
}
|
||||
|
||||
func getRealUserIP(r *http.Request) string {
|
||||
// Note this function assumes it is running behind a trusted proxy, so
|
||||
// the headers can be trusted.
|
||||
func GetRealUserIP(r *http.Request, trusted *AllowedIps) string {
|
||||
addr := r.RemoteAddr
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
|
||||
ip := net.ParseIP(addr)
|
||||
if len(ip) == 0 {
|
||||
return addr
|
||||
}
|
||||
|
||||
if trusted == nil || !trusted.Allowed(ip) {
|
||||
return addr
|
||||
}
|
||||
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
@ -2524,14 +2553,22 @@ func getRealUserIP(r *http.Request) string {
|
|||
if pos := strings.Index(ip, ","); pos >= 0 {
|
||||
ip = strings.TrimSpace(ip[:pos])
|
||||
}
|
||||
// Make sure to remove any port.
|
||||
if host, _, err := net.SplitHostPort(ip); err == nil {
|
||||
ip = host
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
return addr
|
||||
}
|
||||
|
||||
func (h *Hub) getRealUserIP(r *http.Request) string {
|
||||
return GetRealUserIP(r, h.trustedProxies)
|
||||
}
|
||||
|
||||
func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) {
|
||||
addr := getRealUserIP(r)
|
||||
addr := h.getRealUserIP(r)
|
||||
agent := r.Header.Get("User-Agent")
|
||||
|
||||
conn, err := h.upgrader.Upgrade(w, r, nil)
|
||||
|
|
34
hub_test.go
34
hub_test.go
|
@ -3580,10 +3580,15 @@ func TestJoinRoomSwitchClient(t *testing.T) {
|
|||
func TestGetRealUserIP(t *testing.T) {
|
||||
REMOTE_ATTR := "192.168.1.2"
|
||||
|
||||
request := &http.Request{
|
||||
RemoteAddr: REMOTE_ATTR,
|
||||
trustedProxies, err := ParseAllowedIps("192.168.0.0/16")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if ip := getRealUserIP(request); ip != REMOTE_ATTR {
|
||||
|
||||
request := &http.Request{
|
||||
RemoteAddr: REMOTE_ATTR + ":23456",
|
||||
}
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != REMOTE_ATTR {
|
||||
t.Errorf("Expected %s but got %s", REMOTE_ATTR, ip)
|
||||
}
|
||||
|
||||
|
@ -3591,27 +3596,42 @@ func TestGetRealUserIP(t *testing.T) {
|
|||
request.Header = http.Header{
|
||||
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
||||
}
|
||||
if ip := getRealUserIP(request); ip != X_REAL_IP {
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != X_REAL_IP {
|
||||
t.Errorf("Expected %s but got %s", X_REAL_IP, ip)
|
||||
}
|
||||
|
||||
// "X-Real-IP" has preference before "X-Forwarded-For"
|
||||
X_FORWARDED_FOR_IP := "192.168.20.21"
|
||||
X_FORWARDED_FOR := X_FORWARDED_FOR_IP + ", 192.168.30.32"
|
||||
X_FORWARDED_FOR := X_FORWARDED_FOR_IP + ":12345, 192.168.30.32"
|
||||
request.Header = http.Header{
|
||||
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
||||
http.CanonicalHeaderKey("x-forwarded-for"): []string{X_FORWARDED_FOR},
|
||||
}
|
||||
if ip := getRealUserIP(request); ip != X_REAL_IP {
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != X_REAL_IP {
|
||||
t.Errorf("Expected %s but got %s", X_REAL_IP, ip)
|
||||
}
|
||||
|
||||
request.Header = http.Header{
|
||||
http.CanonicalHeaderKey("x-forwarded-for"): []string{X_FORWARDED_FOR},
|
||||
}
|
||||
if ip := getRealUserIP(request); ip != X_FORWARDED_FOR_IP {
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != X_FORWARDED_FOR_IP {
|
||||
t.Errorf("Expected %s but got %s", X_FORWARDED_FOR_IP, ip)
|
||||
}
|
||||
|
||||
PUBLIC_IP := "1.2.3.4"
|
||||
request.RemoteAddr = PUBLIC_IP + ":1234"
|
||||
request.Header = http.Header{
|
||||
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
||||
}
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != PUBLIC_IP {
|
||||
t.Errorf("Expected %s but got %s", PUBLIC_IP, ip)
|
||||
}
|
||||
request.Header = http.Header{
|
||||
http.CanonicalHeaderKey("x-forwarded-for"): []string{X_FORWARDED_FOR},
|
||||
}
|
||||
if ip := GetRealUserIP(request, trustedProxies); ip != PUBLIC_IP {
|
||||
t.Errorf("Expected %s but got %s", PUBLIC_IP, ip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) {
|
||||
|
|
|
@ -8,6 +8,11 @@
|
|||
# See "https://golang.org/pkg/net/http/pprof/" for further information.
|
||||
#debug = false
|
||||
|
||||
# Comma separated list of trusted proxies (IPs or CIDR networks) that may set
|
||||
# the "X-Real-Ip" or "X-Forwarded-For" headers.
|
||||
# Leave empty to allow loopback and local addresses.
|
||||
#trustedproxies =
|
||||
|
||||
# ISO 3166 country this proxy is located at. This will be used by the signaling
|
||||
# servers to determine the closest proxy for publishers.
|
||||
#country = DE
|
||||
|
|
|
@ -99,6 +99,7 @@ type ProxyServer struct {
|
|||
|
||||
tokens ProxyTokens
|
||||
statsAllowedIps *signaling.AllowedIps
|
||||
trustedProxies *signaling.AllowedIps
|
||||
|
||||
sid atomic.Uint64
|
||||
cookie *securecookie.SecureCookie
|
||||
|
@ -153,6 +154,19 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
|
|||
statsAllowedIps = signaling.DefaultAllowedIps()
|
||||
}
|
||||
|
||||
trustedProxies, _ := config.GetString("app", "trustedproxies")
|
||||
trustedProxiesIps, err := signaling.ParseAllowedIps(trustedProxies)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !trustedProxiesIps.Empty() {
|
||||
log.Printf("Trusted proxies: %s", trustedProxiesIps)
|
||||
} else {
|
||||
trustedProxiesIps = signaling.DefaultTrustedProxies
|
||||
log.Printf("No trusted proxies configured, only allowing for %s", trustedProxiesIps)
|
||||
}
|
||||
|
||||
country, _ := config.GetString("app", "country")
|
||||
country = strings.ToUpper(country)
|
||||
if signaling.IsValidCountry(country) {
|
||||
|
@ -187,6 +201,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
|
|||
|
||||
tokens: tokens,
|
||||
statsAllowedIps: statsAllowedIps,
|
||||
trustedProxies: trustedProxiesIps,
|
||||
|
||||
cookie: securecookie.New(hashKey, blockKey).MaxAge(0),
|
||||
sessions: make(map[uint64]*ProxySession),
|
||||
|
@ -398,24 +413,6 @@ func (s *ProxyServer) setCommonHeaders(f func(http.ResponseWriter, *http.Request
|
|||
}
|
||||
}
|
||||
|
||||
func getRealUserIP(r *http.Request) string {
|
||||
// Note this function assumes it is running behind a trusted proxy, so
|
||||
// the headers can be trusted.
|
||||
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||
return ip
|
||||
}
|
||||
|
||||
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
||||
// Result could be a list "clientip, proxy1, proxy2", so only use first element.
|
||||
if pos := strings.Index(ip, ","); pos >= 0 {
|
||||
ip = strings.TrimSpace(ip[:pos])
|
||||
}
|
||||
return ip
|
||||
}
|
||||
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func (s *ProxyServer) welcomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
@ -423,7 +420,7 @@ func (s *ProxyServer) welcomeHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
func (s *ProxyServer) proxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
addr := getRealUserIP(r)
|
||||
addr := signaling.GetRealUserIP(r, s.trustedProxies)
|
||||
conn, err := s.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Printf("Could not upgrade request from %s: %s", addr, err)
|
||||
|
@ -1018,15 +1015,9 @@ func (s *ProxyServer) getStats() map[string]interface{} {
|
|||
}
|
||||
|
||||
func (s *ProxyServer) allowStatsAccess(r *http.Request) bool {
|
||||
addr := getRealUserIP(r)
|
||||
if strings.Contains(addr, ":") {
|
||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
||||
addr = host
|
||||
}
|
||||
}
|
||||
|
||||
addr := signaling.GetRealUserIP(r, s.trustedProxies)
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
if len(ip) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,11 @@ debug = false
|
|||
# room and call can be subscribed.
|
||||
#allowsubscribeany = false
|
||||
|
||||
# Comma separated list of trusted proxies (IPs or CIDR networks) that may set
|
||||
# the "X-Real-Ip" or "X-Forwarded-For" headers.
|
||||
# Leave empty to allow loopback and local addresses.
|
||||
#trustedproxies =
|
||||
|
||||
[sessions]
|
||||
# Secret value used to generate checksums of sessions. This should be a random
|
||||
# string of 32 or 64 bytes.
|
||||
|
|
Loading…
Reference in a new issue