mirror of
https://github.com/strukturag/nextcloud-spreed-signaling
synced 2024-06-29 02:40:03 +02:00
Make trusted proxies configurable and default to loopback / private IPs.
This commit is contained in:
parent
936f83feb9
commit
aac4874e72
|
@ -22,6 +22,7 @@
|
||||||
package signaling
|
package signaling
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -31,6 +32,19 @@ type AllowedIps struct {
|
||||||
allowed []*net.IPNet
|
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 {
|
func (a *AllowedIps) Empty() bool {
|
||||||
return len(a.allowed) == 0
|
return len(a.allowed) == 0
|
||||||
}
|
}
|
||||||
|
@ -99,3 +113,22 @@ func DefaultAllowedIps() *AllowedIps {
|
||||||
}
|
}
|
||||||
return result
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -881,15 +881,9 @@ func (b *BackendServer) roomHandler(w http.ResponseWriter, r *http.Request, body
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BackendServer) allowStatsAccess(r *http.Request) bool {
|
func (b *BackendServer) allowStatsAccess(r *http.Request) bool {
|
||||||
addr := getRealUserIP(r)
|
addr := b.hub.getRealUserIP(r)
|
||||||
if strings.Contains(addr, ":") {
|
|
||||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
|
||||||
addr = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(addr)
|
ip := net.ParseIP(addr)
|
||||||
if ip == nil {
|
if len(ip) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
47
hub.go
47
hub.go
|
@ -103,6 +103,8 @@ var (
|
||||||
|
|
||||||
// Delay after which a "cleared" / "rejected" dialout status should be removed.
|
// Delay after which a "cleared" / "rejected" dialout status should be removed.
|
||||||
removeCallStatusTTL = 5 * time.Second
|
removeCallStatusTTL = 5 * time.Second
|
||||||
|
|
||||||
|
DefaultTrustedProxies = DefaultPrivateIps()
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -163,6 +165,7 @@ type Hub struct {
|
||||||
backendTimeout time.Duration
|
backendTimeout time.Duration
|
||||||
backend *BackendClient
|
backend *BackendClient
|
||||||
|
|
||||||
|
trustedProxies *AllowedIps
|
||||||
geoip *GeoLookup
|
geoip *GeoLookup
|
||||||
geoipOverrides map[*net.IPNet]string
|
geoipOverrides map[*net.IPNet]string
|
||||||
geoipUpdating atomic.Bool
|
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")
|
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)
|
decodeCaches := make([]*LruCache, 0, numDecodeCaches)
|
||||||
for i := 0; i < numDecodeCaches; i++ {
|
for i := 0; i < numDecodeCaches; i++ {
|
||||||
decodeCaches = append(decodeCaches, NewLruCache(decodeCacheSize))
|
decodeCaches = append(decodeCaches, NewLruCache(decodeCacheSize))
|
||||||
|
@ -353,6 +369,7 @@ func NewHub(config *goconf.ConfigFile, events AsyncEvents, rpcServer *GrpcServer
|
||||||
backendTimeout: backendTimeout,
|
backendTimeout: backendTimeout,
|
||||||
backend: backend,
|
backend: backend,
|
||||||
|
|
||||||
|
trustedProxies: trustedProxiesIps,
|
||||||
geoip: geoip,
|
geoip: geoip,
|
||||||
geoipOverrides: geoipOverrides,
|
geoipOverrides: geoipOverrides,
|
||||||
|
|
||||||
|
@ -2512,9 +2529,21 @@ func (h *Hub) GetStats() map[string]interface{} {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func getRealUserIP(r *http.Request) string {
|
func GetRealUserIP(r *http.Request, trusted *AllowedIps) string {
|
||||||
// Note this function assumes it is running behind a trusted proxy, so
|
addr := r.RemoteAddr
|
||||||
// the headers can be trusted.
|
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 != "" {
|
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||||
return ip
|
return ip
|
||||||
}
|
}
|
||||||
|
@ -2524,14 +2553,22 @@ func getRealUserIP(r *http.Request) string {
|
||||||
if pos := strings.Index(ip, ","); pos >= 0 {
|
if pos := strings.Index(ip, ","); pos >= 0 {
|
||||||
ip = strings.TrimSpace(ip[:pos])
|
ip = strings.TrimSpace(ip[:pos])
|
||||||
}
|
}
|
||||||
|
// Make sure to remove any port.
|
||||||
|
if host, _, err := net.SplitHostPort(ip); err == nil {
|
||||||
|
ip = host
|
||||||
|
}
|
||||||
return ip
|
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) {
|
func (h *Hub) serveWs(w http.ResponseWriter, r *http.Request) {
|
||||||
addr := getRealUserIP(r)
|
addr := h.getRealUserIP(r)
|
||||||
agent := r.Header.Get("User-Agent")
|
agent := r.Header.Get("User-Agent")
|
||||||
|
|
||||||
conn, err := h.upgrader.Upgrade(w, r, nil)
|
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) {
|
func TestGetRealUserIP(t *testing.T) {
|
||||||
REMOTE_ATTR := "192.168.1.2"
|
REMOTE_ATTR := "192.168.1.2"
|
||||||
|
|
||||||
request := &http.Request{
|
trustedProxies, err := ParseAllowedIps("192.168.0.0/16")
|
||||||
RemoteAddr: REMOTE_ATTR,
|
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)
|
t.Errorf("Expected %s but got %s", REMOTE_ATTR, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3591,27 +3596,42 @@ func TestGetRealUserIP(t *testing.T) {
|
||||||
request.Header = http.Header{
|
request.Header = http.Header{
|
||||||
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
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)
|
t.Errorf("Expected %s but got %s", X_REAL_IP, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
// "X-Real-IP" has preference before "X-Forwarded-For"
|
// "X-Real-IP" has preference before "X-Forwarded-For"
|
||||||
X_FORWARDED_FOR_IP := "192.168.20.21"
|
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{
|
request.Header = http.Header{
|
||||||
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
http.CanonicalHeaderKey("x-real-ip"): []string{X_REAL_IP},
|
||||||
http.CanonicalHeaderKey("x-forwarded-for"): []string{X_FORWARDED_FOR},
|
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)
|
t.Errorf("Expected %s but got %s", X_REAL_IP, ip)
|
||||||
}
|
}
|
||||||
|
|
||||||
request.Header = http.Header{
|
request.Header = http.Header{
|
||||||
http.CanonicalHeaderKey("x-forwarded-for"): []string{X_FORWARDED_FOR},
|
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)
|
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) {
|
func TestClientMessageToSessionIdWhileDisconnected(t *testing.T) {
|
||||||
|
|
|
@ -8,6 +8,11 @@
|
||||||
# See "https://golang.org/pkg/net/http/pprof/" for further information.
|
# See "https://golang.org/pkg/net/http/pprof/" for further information.
|
||||||
#debug = false
|
#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
|
# ISO 3166 country this proxy is located at. This will be used by the signaling
|
||||||
# servers to determine the closest proxy for publishers.
|
# servers to determine the closest proxy for publishers.
|
||||||
#country = DE
|
#country = DE
|
||||||
|
|
|
@ -99,6 +99,7 @@ type ProxyServer struct {
|
||||||
|
|
||||||
tokens ProxyTokens
|
tokens ProxyTokens
|
||||||
statsAllowedIps *signaling.AllowedIps
|
statsAllowedIps *signaling.AllowedIps
|
||||||
|
trustedProxies *signaling.AllowedIps
|
||||||
|
|
||||||
sid atomic.Uint64
|
sid atomic.Uint64
|
||||||
cookie *securecookie.SecureCookie
|
cookie *securecookie.SecureCookie
|
||||||
|
@ -153,6 +154,19 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
|
||||||
statsAllowedIps = signaling.DefaultAllowedIps()
|
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, _ := config.GetString("app", "country")
|
||||||
country = strings.ToUpper(country)
|
country = strings.ToUpper(country)
|
||||||
if signaling.IsValidCountry(country) {
|
if signaling.IsValidCountry(country) {
|
||||||
|
@ -187,6 +201,7 @@ func NewProxyServer(r *mux.Router, version string, config *goconf.ConfigFile) (*
|
||||||
|
|
||||||
tokens: tokens,
|
tokens: tokens,
|
||||||
statsAllowedIps: statsAllowedIps,
|
statsAllowedIps: statsAllowedIps,
|
||||||
|
trustedProxies: trustedProxiesIps,
|
||||||
|
|
||||||
cookie: securecookie.New(hashKey, blockKey).MaxAge(0),
|
cookie: securecookie.New(hashKey, blockKey).MaxAge(0),
|
||||||
sessions: make(map[uint64]*ProxySession),
|
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) {
|
func (s *ProxyServer) welcomeHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
w.WriteHeader(http.StatusOK)
|
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) {
|
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)
|
conn, err := s.upgrader.Upgrade(w, r, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Could not upgrade request from %s: %s", addr, err)
|
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 {
|
func (s *ProxyServer) allowStatsAccess(r *http.Request) bool {
|
||||||
addr := getRealUserIP(r)
|
addr := signaling.GetRealUserIP(r, s.trustedProxies)
|
||||||
if strings.Contains(addr, ":") {
|
|
||||||
if host, _, err := net.SplitHostPort(addr); err == nil {
|
|
||||||
addr = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ip := net.ParseIP(addr)
|
ip := net.ParseIP(addr)
|
||||||
if ip == nil {
|
if len(ip) == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,11 @@ debug = false
|
||||||
# room and call can be subscribed.
|
# room and call can be subscribed.
|
||||||
#allowsubscribeany = false
|
#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]
|
[sessions]
|
||||||
# Secret value used to generate checksums of sessions. This should be a random
|
# Secret value used to generate checksums of sessions. This should be a random
|
||||||
# string of 32 or 64 bytes.
|
# string of 32 or 64 bytes.
|
||||||
|
|
Loading…
Reference in a new issue