From 458cd26e22678f4d266413626533891352725624 Mon Sep 17 00:00:00 2001 From: Someone <7dn1yh5j@debauchez.fr> Date: Tue, 21 Jan 2025 14:07:03 +0100 Subject: [PATCH 1/4] Add trusted proxies --- client/src/pages/config/users/configman.jsx | 15 ++++++ client/src/utils/locales/en/translation.json | 2 + client/src/utils/locales/fr/translation.json | 2 + src/httpServer.go | 2 + src/proxy/shield.go | 22 +++++---- src/utils/middleware.go | 49 ++++++++++++++------ src/utils/types.go | 1 + src/utils/utils.go | 20 ++++++-- 8 files changed, 85 insertions(+), 28 deletions(-) diff --git a/client/src/pages/config/users/configman.jsx b/client/src/pages/config/users/configman.jsx index 29ccea21..0760c0d7 100644 --- a/client/src/pages/config/users/configman.jsx +++ b/client/src/pages/config/users/configman.jsx @@ -114,6 +114,7 @@ const ConfigManagement = () => { GenerateMissingAuthCert: config.HTTPConfig.GenerateMissingAuthCert, HTTPPort: config.HTTPConfig.HTTPPort, HTTPSPort: config.HTTPConfig.HTTPSPort, + TrustedProxies: config.HTTPConfig.TrustedProxies && config.HTTPConfig.TrustedProxies.join(', '), SSLEmail: config.HTTPConfig.SSLEmail, UseWildcardCertificate: config.HTTPConfig.UseWildcardCertificate, HTTPSCertificateMode: config.HTTPConfig.HTTPSCertificateMode, @@ -214,6 +215,8 @@ const ConfigManagement = () => { AllowSearchEngine: values.AllowSearchEngine, AllowHTTPLocalIPAccess: values.AllowHTTPLocalIPAccess, PublishMDNS: values.PublishMDNS, + TrustedProxies: (values.TrustedProxies && values.TrustedProxies != "") ? + values.TrustedProxies.split(',').map((x) => x.trim()) : [], }, EmailConfig: { ...config.EmailConfig, @@ -656,6 +659,18 @@ const ConfigManagement = () => { )} + + + + {t('mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText')}
+
+ +
+
{t('mgmt.config.http.allowSearchIndexCheckbox')}
diff --git a/client/src/utils/locales/en/translation.json b/client/src/utils/locales/en/translation.json index b5ee8ec5..b7e69397 100644 --- a/client/src/utils/locales/en/translation.json +++ b/client/src/utils/locales/en/translation.json @@ -192,6 +192,8 @@ "mgmt.config.http.hostnameInput.HostnameLabel": "Hostname: This will be used to restrict access to your Cosmos Server (Your IP, or your domain name)", "mgmt.config.http.hostnameInput.HostnameValidation": "Hostname is required", "mgmt.config.http.publishMDNSCheckbox": "This allows you to publish your server on your local network using mDNS. This means all your .local domains will be available on your local network with no additional config.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "Trusted proxies allow X-Forwarded-For from IP/IP range.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Use this setting when you have an upstream proxy server to avoid it being blocked by Shield. IPs or IP ranges separated by commas.", "mgmt.config.email.notifyLoginCheckbox.notifyLoginLabel": "Notify Users upon Successful Login", "mgmt.config.proxy.noRoutesConfiguredText": "No routes configured.", "mgmt.config.proxy.originTitle": "Origin", diff --git a/client/src/utils/locales/fr/translation.json b/client/src/utils/locales/fr/translation.json index 4c5c28d0..7fa9293b 100644 --- a/client/src/utils/locales/fr/translation.json +++ b/client/src/utils/locales/fr/translation.json @@ -174,6 +174,8 @@ "mgmt.config.http.hostnameInput.HostnameLabel": "Nom d'hôte : Cela sera utilisé pour restreindre l'accès à votre serveur Cosmos (Votre IP, ou votre nom de domaine)", "mgmt.config.http.hostnameInput.HostnameValidation": "Le nom d'hôte est obligatoire", "mgmt.config.http.publishMDNSCheckbox": "Cela vous permet de publier votre serveur sur votre réseau local en utilisant mDNS. Cela signifie que tous vos domaines .local seront disponibles sur votre réseau local sans configuration supplémentaire.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "Trusted proxies allow X-Forwarded-For from IP/IP range.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Use this setting when you have an upstream proxy server to avoid it being blocked by Shield. IPs or IP ranges separated by commas.", "mgmt.config.email.notifyLoginCheckbox.notifyLoginLabel": "Notifier les utilisateurs en cas de connexion réussie", "mgmt.config.proxy.noRoutesConfiguredText": "Aucune route configurée.", "mgmt.config.proxy.originTitle": "Origine", diff --git a/src/httpServer.go b/src/httpServer.go index 9fc98f6b..2a80c400 100644 --- a/src/httpServer.go +++ b/src/httpServer.go @@ -431,6 +431,8 @@ func InitServer() *mux.Router { router := mux.NewRouter().StrictSlash(true) + router.Use(utils.ClientRealIP) + router.Use(utils.BlockBannedIPs) router.Use(utils.Logger) diff --git a/src/proxy/shield.go b/src/proxy/shield.go index 58683576..8a790aaf 100644 --- a/src/proxy/shield.go +++ b/src/proxy/shield.go @@ -7,6 +7,7 @@ import ( "fmt" "math" "strconv" + "strings" "github.com/azukaar/cosmos-server/src/utils" "github.com/azukaar/cosmos-server/src/metrics" @@ -296,14 +297,19 @@ func calculateLowestExhaustedPercentage(policy utils.SmartShieldPolicy, userCons func GetClientID(r *http.Request, route utils.ProxyRouteConfig) string { // when using Docker we need to get the real IP remoteAddr, _ := utils.SplitIP(r.RemoteAddr) - UseForwardedFor := utils.GetMainConfig().HTTPConfig.UseForwardedFor - isTunneledIp := constellation.GetDeviceIp(route.TunnelVia) == remoteAddr - isConstIP := utils.IsConstellationIP(remoteAddr) - isConstTokenValid := constellation.CheckConstellationToken(r) == nil - - if (UseForwardedFor && r.Header.Get("x-forwarded-for") != "") || - (isTunneledIp && isConstIP && isConstTokenValid) { - ip, _ := utils.SplitIP(r.Header.Get("x-forwarded-for")) + useForwardedForHeader := false + if r.Header.Get("x-forwarded-for") != "" { + useForwardedForHeader = utils.IsTrustedProxy(remoteAddr) + if !useForwardedForHeader { + isTunneledIp := constellation.GetDeviceIp(route.TunnelVia) == remoteAddr + isConstIP := utils.IsConstellationIP(remoteAddr) + isConstTokenValid := constellation.CheckConstellationToken(r) == nil + useForwardedForHeader = isTunneledIp && isConstIP && isConstTokenValid + } + } + + if useForwardedForHeader { + ip, _ := utils.SplitIP(strings.TrimSpace(strings.Split(r.Header.Get("X-Forwarded-For"), ",")[0])) utils.Debug("SmartShield: Getting forwarded client ID " + ip) return ip } else { diff --git a/src/utils/middleware.go b/src/utils/middleware.go index 4fd59aec..a4efc0d5 100644 --- a/src/utils/middleware.go +++ b/src/utils/middleware.go @@ -48,10 +48,25 @@ func GetIPAbuseCounter(ip string) int64 { return atomic.LoadInt64(&counter.val) } +func ClientRealIP(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + clientID := GetClientIP(r) + if(clientID == ""){ + http.Error(w, "Invalid request", http.StatusBadRequest) + return + } + + ctx := context.WithValue(r.Context(), "ClientID", clientID) + r = r.WithContext(ctx) + + next.ServeHTTP(w, r) + }) +} + func BlockBannedIPs(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ip, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { + ip, ok := r.Context().Value("ClientID").(string) + if !ok { if hj, ok := w.(http.Hijacker); ok { conn, _, err := hj.Hijack() if err == nil { @@ -204,8 +219,8 @@ func GetIPLocation(ip string) (string, error) { func BlockByCountryMiddleware(blockedCountries []string, CountryBlacklistIsWhitelist bool) func(http.Handler) http.Handler { return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ip, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { + ip, ok := r.Context().Value("ClientID").(string) + if !ok { http.Error(w, "Invalid request", http.StatusBadRequest) return } @@ -289,7 +304,7 @@ func BlockPostWithoutReferer(next http.Handler) http.Handler { Error("Blocked POST request without Referer header", nil) http.Error(w, "Bad Request: Invalid request.", http.StatusBadRequest) - ip, _, _ := net.SplitHostPort(r.RemoteAddr) + ip, _ := r.Context().Value("ClientID").(string) if ip != "" { TriggerEvent( "cosmos.proxy.shield.referer", @@ -349,7 +364,7 @@ func EnsureHostname(next http.Handler) http.Handler { w.WriteHeader(http.StatusBadRequest) http.Error(w, "Bad Request: Invalid hostname. Use your domain instead of your IP to access your server. Check logs if more details are needed.", http.StatusBadRequest) - ip, _, _ := net.SplitHostPort(r.RemoteAddr) + ip, _ := r.Context().Value("ClientID").(string) if ip != "" { TriggerEvent( "cosmos.proxy.shield.hostname", @@ -415,7 +430,7 @@ func EnsureHostnameCosmosAPI(next http.Handler) http.Handler { w.WriteHeader(http.StatusBadRequest) http.Error(w, "Bad Request: Invalid hostname. Use your domain instead of your IP to access your server. Check logs if more details are needed.", http.StatusBadRequest) - ip, _, _ := net.SplitHostPort(r.RemoteAddr) + ip, _ := r.Context().Value("ClientID").(string) if ip != "" { TriggerEvent( "cosmos.proxy.shield.hostname", @@ -469,16 +484,19 @@ func IsValidHostname(hostname string) bool { } func IPInRange(ipStr, cidrStr string) (bool, error) { - _, cidrNet, err := net.ParseCIDR(cidrStr) - if err != nil { - return false, fmt.Errorf("parse CIDR range: %w", err) - } - ip := net.ParseIP(ipStr) if ip == nil { return false, fmt.Errorf("parse IP: invalid IP address") } + _, cidrNet, err := net.ParseCIDR(cidrStr) + if err != nil { + if ipStr == cidrStr { + return true, nil + } + return false, fmt.Errorf("parse CIDR range: %w", err) + } + return cidrNet.Contains(ip), nil } @@ -486,8 +504,9 @@ func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) fu return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ip, _, err := net.SplitHostPort(r.RemoteAddr) - if err != nil { + remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr) + ip, ok := r.Context().Value("ClientID").(string) + if (err != nil) || !ok { http.Error(w, "Invalid request", http.StatusBadRequest) return } @@ -495,7 +514,7 @@ func Restrictions(RestrictToConstellation bool, WhitelistInboundIPs []string) fu isUsingWhiteList := len(WhitelistInboundIPs) > 0 isInWhitelist := false - isInConstellation := strings.HasPrefix(ip, "192.168.201.") || strings.HasPrefix(ip, "192.168.202.") + isInConstellation := strings.HasPrefix(remoteAddr, "192.168.201.") || strings.HasPrefix(remoteAddr, "192.168.202.") for _, ipRange := range WhitelistInboundIPs { Debug("Checking if " + ip + " is in " + ipRange) diff --git a/src/utils/types.go b/src/utils/types.go index fedf134d..9ef87e16 100644 --- a/src/utils/types.go +++ b/src/utils/types.go @@ -184,6 +184,7 @@ type HTTPConfig struct { UseForwardedFor bool AllowSearchEngine bool PublishMDNS bool + TrustedProxies []string } const ( diff --git a/src/utils/utils.go b/src/utils/utils.go index 31a6a8f5..afc8a0c2 100644 --- a/src/utils/utils.go +++ b/src/utils/utils.go @@ -801,11 +801,12 @@ func DownloadFileToLocation(path, url string) error { } func GetClientIP(req *http.Request) string { - /*ip := req.Header.Get("X-Forwarded-For") - if ip == "" { - ip = req.RemoteAddr - }*/ - return req.RemoteAddr + remoteAddr, _ := SplitIP(req.RemoteAddr) + + if req.Header.Get("x-forwarded-for") != "" && IsTrustedProxy(remoteAddr) { + remoteAddr, _ = SplitIP(strings.TrimSpace(strings.Split(req.Header.Get("X-Forwarded-For"), ",")[0])) + } + return remoteAddr } func IsDomain(domain string) bool { @@ -930,6 +931,15 @@ func IsConstellationIP(ip string) bool { return false } +func IsTrustedProxy(ip string) bool { + for _, trustedProxy := range GetMainConfig().HTTPConfig.TrustedProxies { + if isInRange, _ := IPInRange(ip, trustedProxy); isInRange { + return true + } + } + return false +} + func SplitIP(ipPort string) (string, string) { host, port, err := osnet.SplitHostPort(ipPort) if err != nil { From 4a4f5268f4bc6276f183e7495afa6d8478478884 Mon Sep 17 00:00:00 2001 From: Someone <10882916+InterN0te@users.noreply.github.com> Date: Sat, 1 Feb 2025 20:35:12 +0100 Subject: [PATCH 2/4] Fix french translation --- client/src/utils/locales/fr/translation.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/utils/locales/fr/translation.json b/client/src/utils/locales/fr/translation.json index 7fa9293b..f1d5322a 100644 --- a/client/src/utils/locales/fr/translation.json +++ b/client/src/utils/locales/fr/translation.json @@ -174,8 +174,8 @@ "mgmt.config.http.hostnameInput.HostnameLabel": "Nom d'hôte : Cela sera utilisé pour restreindre l'accès à votre serveur Cosmos (Votre IP, ou votre nom de domaine)", "mgmt.config.http.hostnameInput.HostnameValidation": "Le nom d'hôte est obligatoire", "mgmt.config.http.publishMDNSCheckbox": "Cela vous permet de publier votre serveur sur votre réseau local en utilisant mDNS. Cela signifie que tous vos domaines .local seront disponibles sur votre réseau local sans configuration supplémentaire.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "Trusted proxies allow X-Forwarded-For from IP/IP range.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Use this setting when you have an upstream proxy server to avoid it being blocked by Shield. IPs or IP ranges separated by commas.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "IPs/Plages IP des proxys de confiance pour l'utilisation de X-Forwarded-For.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Utilisez ce paramètre lorsque vous avez un serveur proxy en amont pour éviter le blocage de celui-ci par le Shield. IPs ou plages IP séparées par des virgules.", "mgmt.config.email.notifyLoginCheckbox.notifyLoginLabel": "Notifier les utilisateurs en cas de connexion réussie", "mgmt.config.proxy.noRoutesConfiguredText": "Aucune route configurée.", "mgmt.config.proxy.originTitle": "Origine", @@ -788,4 +788,4 @@ "mgmt.sales.subs_pro": "Abonnement flexible", "mgmt.sales.life_pro": "Toutes les fonctionnalités à vie", "mgmt.sales.upgrade_button": "Mettre à niveau maintenant" -} \ No newline at end of file +} From 614d1c14928face9a2bf05e1760d27eac030ecb5 Mon Sep 17 00:00:00 2001 From: Someone <7dn1yh5j@debauchez.fr> Date: Sun, 2 Feb 2025 11:37:02 +0100 Subject: [PATCH 3/4] Revert "Fix french translation" This reverts commit 4a4f5268f4bc6276f183e7495afa6d8478478884. --- client/src/utils/locales/fr/translation.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/src/utils/locales/fr/translation.json b/client/src/utils/locales/fr/translation.json index f1d5322a..7fa9293b 100644 --- a/client/src/utils/locales/fr/translation.json +++ b/client/src/utils/locales/fr/translation.json @@ -174,8 +174,8 @@ "mgmt.config.http.hostnameInput.HostnameLabel": "Nom d'hôte : Cela sera utilisé pour restreindre l'accès à votre serveur Cosmos (Votre IP, ou votre nom de domaine)", "mgmt.config.http.hostnameInput.HostnameValidation": "Le nom d'hôte est obligatoire", "mgmt.config.http.publishMDNSCheckbox": "Cela vous permet de publier votre serveur sur votre réseau local en utilisant mDNS. Cela signifie que tous vos domaines .local seront disponibles sur votre réseau local sans configuration supplémentaire.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "IPs/Plages IP des proxys de confiance pour l'utilisation de X-Forwarded-For.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Utilisez ce paramètre lorsque vous avez un serveur proxy en amont pour éviter le blocage de celui-ci par le Shield. IPs ou plages IP séparées par des virgules.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "Trusted proxies allow X-Forwarded-For from IP/IP range.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Use this setting when you have an upstream proxy server to avoid it being blocked by Shield. IPs or IP ranges separated by commas.", "mgmt.config.email.notifyLoginCheckbox.notifyLoginLabel": "Notifier les utilisateurs en cas de connexion réussie", "mgmt.config.proxy.noRoutesConfiguredText": "Aucune route configurée.", "mgmt.config.proxy.originTitle": "Origine", @@ -788,4 +788,4 @@ "mgmt.sales.subs_pro": "Abonnement flexible", "mgmt.sales.life_pro": "Toutes les fonctionnalités à vie", "mgmt.sales.upgrade_button": "Mettre à niveau maintenant" -} +} \ No newline at end of file From 35971cf350ddf3344fa00f9bfdf4a5d28b135881 Mon Sep 17 00:00:00 2001 From: Someone <7dn1yh5j@debauchez.fr> Date: Sun, 2 Feb 2025 11:39:10 +0100 Subject: [PATCH 4/4] Fix french translation Remove unwanted edition of last line --- client/src/utils/locales/fr/translation.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/src/utils/locales/fr/translation.json b/client/src/utils/locales/fr/translation.json index 7fa9293b..0a083a63 100644 --- a/client/src/utils/locales/fr/translation.json +++ b/client/src/utils/locales/fr/translation.json @@ -174,8 +174,8 @@ "mgmt.config.http.hostnameInput.HostnameLabel": "Nom d'hôte : Cela sera utilisé pour restreindre l'accès à votre serveur Cosmos (Votre IP, ou votre nom de domaine)", "mgmt.config.http.hostnameInput.HostnameValidation": "Le nom d'hôte est obligatoire", "mgmt.config.http.publishMDNSCheckbox": "Cela vous permet de publier votre serveur sur votre réseau local en utilisant mDNS. Cela signifie que tous vos domaines .local seront disponibles sur votre réseau local sans configuration supplémentaire.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "Trusted proxies allow X-Forwarded-For from IP/IP range.", - "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Use this setting when you have an upstream proxy server to avoid it being blocked by Shield. IPs or IP ranges separated by commas.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesLabel": "IPs/Plages IP des proxys de confiance pour l'utilisation de X-Forwarded-For.", + "mgmt.config.http.trustedProxiesInput.trustedProxiesHelperText": "Utilisez ce paramètre lorsque vous avez un serveur proxy en amont pour éviter le blocage de celui-ci par le Shield. IPs ou plages IP séparées par des virgules.", "mgmt.config.email.notifyLoginCheckbox.notifyLoginLabel": "Notifier les utilisateurs en cas de connexion réussie", "mgmt.config.proxy.noRoutesConfiguredText": "Aucune route configurée.", "mgmt.config.proxy.originTitle": "Origine",