From c0ea280077eebd60c0e0e1a7189254c82b30f4a2 Mon Sep 17 00:00:00 2001 From: Lorenzo Miniero Date: Tue, 14 Feb 2023 10:00:37 +0100 Subject: [PATCH] Optionally support X-Forwarded-For in both HTTP and WebSocket transports (see #3160) --- conf/janus.transport.http.jcfg.sample | 4 +++ conf/janus.transport.websockets.jcfg.sample | 4 +++ transports/janus_http.c | 27 +++++++++++++++++++++ transports/janus_websockets.c | 21 ++++++++++++++++ 4 files changed, 56 insertions(+) diff --git a/conf/janus.transport.http.jcfg.sample b/conf/janus.transport.http.jcfg.sample index 8f3274cd8e..08ff47d87c 100644 --- a/conf/janus.transport.http.jcfg.sample +++ b/conf/janus.transport.http.jcfg.sample @@ -20,6 +20,8 @@ general: { #secure_interface = "eth0" # Whether we should bind this server to a specific interface only #secure_ip = "192.168.0.1" # Whether we should bind this server to a specific IP address (v4 or v6) only #acl = "127.,192.168.0." # Only allow requests coming from this comma separated list of addresses + #acl_forwarded = true # Whether we should check the X-Forwarded-For header too for the ACL + # (default=false, since without a proxy in the middle this could be abused) #mhd_connection_limit = 1020 # Open connections limit in libmicrohttpd (default=1020) #mhd_debug = false # Ask libmicrohttpd to write warning and error messages to stderr (default=false) } @@ -46,6 +48,8 @@ admin: { #admin_secure_interface = "eth0" # Whether we should bind this server to a specific interface only #admin_secure_ip = "192.168.0.1" # Whether we should bind this server to a specific IP address (v4 or v6) only #admin_acl = "127.,192.168.0." # Only allow requests coming from this comma separated list of addresses + #admin_acl_forwarded = true # Whether we should check the X-Forwarded-For header too for the admin ACL + # (default=false, since without a proxy in the middle this could be abused) } # The HTTP servers created in Janus support CORS out of the box, but by diff --git a/conf/janus.transport.websockets.jcfg.sample b/conf/janus.transport.websockets.jcfg.sample index 073b8d1bad..c522831bdc 100644 --- a/conf/janus.transport.websockets.jcfg.sample +++ b/conf/janus.transport.websockets.jcfg.sample @@ -21,6 +21,8 @@ general: { # to debug, supported values: err, warn, notice, info, debug, parser, # header, ext, client, latency, user, count (plus 'none' and 'all') #ws_acl = "127.,192.168.0." # Only allow requests coming from this comma separated list of addresses + #ws_acl_forwarded = true # Whether we should check the X-Forwarded-For header too for the ACL + # (default=false, since without a proxy in the middle this could be abused) } # If you want to expose the Admin API via WebSockets as well, you need to @@ -39,6 +41,8 @@ admin: { #admin_wss_ip = "192.168.0.1" # Whether we should bind this server to a specific IP address only #admin_wss_unix = "/run/awss.sock" # Use WebSocket server over UNIX socket instead of TCP #admin_ws_acl = "127.,192.168.0." # Only allow requests coming from this comma separated list of addresses + #admin_ws_acl_forwarded = true # Whether we should check the X-Forwarded-For header too for the ACL + # (default=false, since without a proxy in the middle this could be abused) } # The HTTP servers created in Janus support CORS out of the box, but by diff --git a/transports/janus_http.c b/transports/janus_http.c index 8bfd12772f..8feaaaa418 100644 --- a/transports/janus_http.c +++ b/transports/janus_http.c @@ -149,6 +149,7 @@ typedef struct janus_http_msg { char *acro; /* Value of the Origin HTTP header, if any (needed for CORS) */ char *acrh; /* Value of the Access-Control-Request-Headers HTTP header, if any (needed for CORS) */ char *acrm; /* Value of the Access-Control-Request-Method HTTP header, if any (needed for CORS) */ + char *xff; /* Value of the X-Forwarded-For HTTP header, if any */ char *contenttype; /* Content-Type of the payload */ char *payload; /* Payload of the message */ size_t len; /* Length of the message in octets */ @@ -173,6 +174,7 @@ static void janus_http_msg_free(const janus_refcount *msg_ref) { g_free(request->acro); g_free(request->acrh); g_free(request->acrm); + g_free(request->xff); g_free(request->response); g_free(request); } @@ -303,6 +305,7 @@ static gboolean enforce_cors = FALSE; /* REST and Admin/Monitor ACL list */ static GList *janus_http_access_list = NULL, *janus_http_admin_access_list = NULL; +static gboolean janus_http_check_xff = FALSE, janus_http_admin_check_xff = FALSE; static janus_mutex access_list_mutex; static void janus_http_allow_address(const char *ip, gboolean admin) { if(ip == NULL) @@ -736,6 +739,10 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path } g_strfreev(list); list = NULL; + /* Check if we should use the value of X-Forwarded-For for checks too */ + item = janus_config_get(config, config_general, janus_config_type_item, "acl_forwarded"); + if(item && item->value) + janus_http_check_xff = janus_is_true(item->value); } item = janus_config_get(config, config_admin, janus_config_type_item, "admin_acl"); if(item && item->value) { @@ -754,6 +761,10 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path } g_strfreev(list); list = NULL; + /* Check if we should use the value of X-Forwarded-For for checks too */ + item = janus_config_get(config, config_general, janus_config_type_item, "admin_acl_forwarded"); + if(item && item->value) + janus_http_admin_check_xff = janus_is_true(item->value); } /* Any custom value for the Access-Control-Allow-Origin header? */ @@ -1375,6 +1386,13 @@ static MHD_Result janus_http_handler(void *cls, struct MHD_Connection *connectio janus_mutex_unlock(&messages_mutex); *ptr = ts; MHD_get_connection_values(connection, MHD_HEADER_KIND, &janus_http_headers, msg); + if(janus_http_check_xff && msg->xff) { + /* Any access limitation based on this IP address? */ + if(!janus_http_is_allowed(msg->xff, FALSE)) { + JANUS_LOG(LOG_ERR, "IP %s is unauthorized to connect to the Janus API interface\n", msg->xff); + return MHD_NO; + } + } ret = MHD_YES; /* Notify handlers about this new transport instance */ if(notify_events && gateway->events_is_enabled()) { @@ -1773,6 +1791,13 @@ static MHD_Result janus_http_admin_handler(void *cls, struct MHD_Connection *con janus_mutex_unlock(&messages_mutex); *ptr = ts; MHD_get_connection_values(connection, MHD_HEADER_KIND, &janus_http_headers, msg); + if(janus_http_admin_check_xff && msg->xff) { + /* Any access limitation based on this IP address? */ + if(!janus_http_is_allowed(msg->xff, TRUE)) { + JANUS_LOG(LOG_ERR, "IP %s is unauthorized to connect to the Janus API interface\n", msg->xff); + return MHD_NO; + } + } ret = MHD_YES; /* Notify handlers about this new transport instance */ if(notify_events && gateway->events_is_enabled()) { @@ -2011,6 +2036,8 @@ static MHD_Result janus_http_headers(void *cls, enum MHD_ValueKind kind, const c request->acrm = g_strdup(value); } else if(!strcasecmp(key, "Access-Control-Request-Headers")) { request->acrh = g_strdup(value); + } else if(!strcasecmp(key, "X-Forwarded-For")) { + request->xff = g_strdup(value); } janus_refcount_decrease(&request->ref); return MHD_YES; diff --git a/transports/janus_websockets.c b/transports/janus_websockets.c index be3bb87c30..773830b7ed 100644 --- a/transports/janus_websockets.c +++ b/transports/janus_websockets.c @@ -332,6 +332,7 @@ static gboolean enforce_cors = FALSE; /* WebSockets ACL list for both Janus and Admin API */ static GList *janus_websockets_access_list = NULL, *janus_websockets_admin_access_list = NULL; +static gboolean janus_websockets_check_xff = FALSE, janus_websockets_admin_check_xff = FALSE; static janus_mutex access_list_mutex; static void janus_websockets_allow_address(const char *ip, gboolean admin) { if(ip == NULL) @@ -649,6 +650,10 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi } g_strfreev(list); list = NULL; + /* Check if we should use the value of X-Forwarded-For for checks too */ + item = janus_config_get(config, config_general, janus_config_type_item, "ws_acl_forwarded"); + if(item && item->value) + janus_websockets_check_xff = janus_is_true(item->value); } item = janus_config_get(config, config_admin, janus_config_type_item, "admin_ws_acl"); if(item && item->value) { @@ -667,6 +672,10 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi } g_strfreev(list); list = NULL; + /* Check if we should use the value of X-Forwarded-For for checks too */ + item = janus_config_get(config, config_general, janus_config_type_item, "admin_ws_acl_forwarded"); + if(item && item->value) + janus_websockets_admin_check_xff = janus_is_true(item->value); } /* Any custom value for the Access-Control-Allow-Origin header? */ @@ -1183,6 +1192,18 @@ static int janus_websockets_common_callback( lws_callback_on_writable(wsi); return -1; } + /* Check if an X-Forwarded-For header was provided */ + char xff[1024] = {0}; + if(lws_hdr_copy(wsi, xff, 1023, WSI_TOKEN_X_FORWARDED_FOR) > 0) { + /* If the ACL is enabled, are we supposed to use this header too for checks? */ + if(((!admin && janus_websockets_check_xff) || (admin && janus_websockets_admin_check_xff)) && !janus_websockets_is_allowed(xff, admin)) { + JANUS_LOG(LOG_ERR, "[%s-%p] IP %s is unauthorized to connect to the WebSockets %s API interface\n", + log_prefix, wsi, xff, admin ? "Admin" : "Janus"); + /* Close the connection */ + lws_callback_on_writable(wsi); + return -1; + } + } JANUS_LOG(LOG_VERB, "[%s-%p] WebSocket connection accepted\n", log_prefix, wsi); if(ws_client == NULL) { JANUS_LOG(LOG_ERR, "[%s-%p] Invalid WebSocket client instance...\n", log_prefix, wsi);