Skip to content

Commit

Permalink
New option to enforce CORS in HTTP and WS transport plugins (#2410)
Browse files Browse the repository at this point in the history
  • Loading branch information
lminiero committed Oct 30, 2020
1 parent 48b01f6 commit b207cbd
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 0 deletions.
4 changes: 4 additions & 0 deletions conf/janus.transport.http.jcfg.sample
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,12 @@ admin: {
# you need that, uncomment and set the 'allow_origin' below to specify
# what must be returned in 'Access-Control-Allow-Origin'. More details:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
# In case you want to enforce the Origin validation, rather than leave
# it to browsers, you can set 'enforce_cors' to 'true' to have Janus
# return a '403 Forbidden' for all requests that don't comply.
cors: {
#allow_origin = "http://foo.example"
#enforce_cors = true
}

# Certificate and key to use for HTTPS, if enabled (and passphrase if needed).
Expand Down
15 changes: 15 additions & 0 deletions conf/janus.transport.websockets.jcfg.sample
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,21 @@ admin: {
#admin_ws_acl = "127.,192.168.0." # Only allow requests coming from this comma separated list of addresses
}

# The HTTP servers created in Janus support CORS out of the box, but by
# default they return a wildcard (*) in the 'Access-Control-Allow-Origin'
# header. This works fine in most situations, except when we have to
# respond to a credential request (withCredentials=true in the XHR). If
# you need that, uncomment and set the 'allow_origin' below to specify
# what must be returned in 'Access-Control-Allow-Origin'. More details:
# https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS
# In case you want to enforce the Origin validation, rather than leave
# it to browsers, you can set 'enforce_cors' to 'true' to have Janus
# return a '403 Forbidden' for all requests that don't comply.
cors: {
#allow_origin = "http://foo.example"
#enforce_cors = true
}

# Certificate and key to use for any secure WebSocket server, if enabled (and passphrase if needed).
# You can also disable insecure protocols and ciphers by configuring the
# 'ciphers' property accordingly (no limitation by default).
Expand Down
33 changes: 33 additions & 0 deletions transports/janus_http.c
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ typedef struct janus_http_msg {
volatile int suspended; /* Whether this connection is currently suspended */
volatile void *longpoll; /* Whether this is a long poll connection for a session */
int max_events; /* In case this is a long poll, how many events we should send back */
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 *contenttype; /* Content-Type of the payload */
Expand All @@ -158,6 +159,7 @@ static void janus_http_msg_free(const janus_refcount *msg_ref) {
return;
g_free(request->payload);
g_free(request->contenttype);
g_free(request->acro);
g_free(request->acrh);
g_free(request->acrm);
g_free(request->response);
Expand Down Expand Up @@ -286,6 +288,7 @@ static char *admin_ws_path = NULL;

/* Custom Access-Control-Allow-Origin value, if specified */
static char *allow_origin = NULL;
static gboolean enforce_cors = FALSE;

/* REST and Admin/Monitor ACL list */
static GList *janus_http_access_list = NULL, *janus_http_admin_access_list = NULL;
Expand Down Expand Up @@ -734,6 +737,13 @@ int janus_http_init(janus_transport_callbacks *callback, const char *config_path
allow_origin = g_strdup(item->value);
JANUS_LOG(LOG_INFO, "Restricting Access-Control-Allow-Origin to '%s'\n", allow_origin);
}
if(allow_origin != NULL) {
item = janus_config_get(config, config_cors, janus_config_type_item, "enforce_cors");
if(item && item->value && janus_is_true(item->value)) {
enforce_cors = TRUE;
JANUS_LOG(LOG_INFO, "Going to enforce CORS by 403 errors\n");
}
}

/* Start with the Janus API web server now */
item = janus_config_get(config, config_general, janus_config_type_item, "http");
Expand Down Expand Up @@ -1367,6 +1377,14 @@ static int janus_http_handler(void *cls, struct MHD_Connection *connection,
ret = janus_http_return_error(ts, 0, NULL, JANUS_ERROR_TRANSPORT_SPECIFIC, "Unsupported method %s", method);
goto done;
}
if(firstround && enforce_cors && (msg->acro == NULL || strstr(msg->acro, allow_origin) != msg->acro)) {
/* Got a request from the wrong origin and we're enforcing CORS with 403 errors */
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
janus_http_add_cors_headers(msg, response);
ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, response);
MHD_destroy_response(response);
return ret;
}
if(!strcasecmp(method, "OPTIONS")) {
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
janus_http_add_cors_headers(msg, response);
Expand Down Expand Up @@ -1755,6 +1773,14 @@ static int janus_http_admin_handler(void *cls, struct MHD_Connection *connection
ret = janus_http_return_error(ts, 0, NULL, JANUS_ERROR_TRANSPORT_SPECIFIC, "Unsupported method %s", method);
goto done;
}
if(firstround && enforce_cors && (msg->acro == NULL || strstr(msg->acro, allow_origin) != msg->acro)) {
/* Got a request from the wrong origin and we're enforcing CORS with 403 errors */
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
janus_http_add_cors_headers(msg, response);
ret = MHD_queue_response(connection, MHD_HTTP_FORBIDDEN, response);
MHD_destroy_response(response);
return ret;
}
if(!strcasecmp(method, "OPTIONS")) {
response = MHD_create_response_from_buffer(0, NULL, MHD_RESPMEM_PERSISTENT);
janus_http_add_cors_headers(msg, response);
Expand Down Expand Up @@ -1942,6 +1968,13 @@ static int janus_http_headers(void *cls, enum MHD_ValueKind kind, const char *ke
janus_refcount_increase(&request->ref);
if(!strcasecmp(key, MHD_HTTP_HEADER_CONTENT_TYPE)) {
request->contenttype = g_strdup(value);
} else if(!strcasecmp(key, "Referer")) {
/* We only use this as a backup in case Origin is missing (e.g., in GET) */
if(request->acro == NULL)
request->acro = g_strdup(value);
} else if(!strcasecmp(key, "Origin")) {
g_free(request->acro);
request->acro = g_strdup(value);
} else if(!strcasecmp(key, "Access-Control-Request-Method")) {
request->acrm = g_strdup(value);
} else if(!strcasecmp(key, "Access-Control-Request-Headers")) {
Expand Down
78 changes: 78 additions & 0 deletions transports/janus_websockets.c
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ static const char *janus_websockets_reason_string(enum lws_callback_reasons reas
CASE_STR(LWS_CALLBACK_HTTP_BODY_COMPLETION);
CASE_STR(LWS_CALLBACK_HTTP_FILE_COMPLETION);
CASE_STR(LWS_CALLBACK_HTTP_WRITEABLE);
CASE_STR(LWS_CALLBACK_ADD_HEADERS);
CASE_STR(LWS_CALLBACK_FILTER_NETWORK_CONNECTION);
CASE_STR(LWS_CALLBACK_FILTER_HTTP_CONNECTION);
CASE_STR(LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED);
Expand Down Expand Up @@ -325,6 +326,10 @@ static char *janus_websockets_get_interface_name(const char *ip) {
return NULL;
}

/* Custom Access-Control-Allow-Origin value, if specified */
static char *allow_origin = NULL;
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 janus_mutex access_list_mutex;
Expand Down Expand Up @@ -408,6 +413,7 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi
janus_config_print(config);
janus_config_category *config_general = janus_config_get_create(config, NULL, janus_config_type_category, "general");
janus_config_category *config_admin = janus_config_get_create(config, NULL, janus_config_type_category, "admin");
janus_config_category *config_cors = janus_config_get_create(config, NULL, janus_config_type_category, "cors");
janus_config_category *config_certs = janus_config_get_create(config, NULL, janus_config_type_category, "certificates");

/* Handle configuration */
Expand Down Expand Up @@ -523,6 +529,20 @@ int janus_websockets_init(janus_transport_callbacks *callback, const char *confi
list = NULL;
}

/* Any custom value for the Access-Control-Allow-Origin header? */
item = janus_config_get(config, config_cors, janus_config_type_item, "allow_origin");
if(item && item->value) {
allow_origin = g_strdup(item->value);
JANUS_LOG(LOG_INFO, "Restricting Access-Control-Allow-Origin to '%s'\n", allow_origin);
}
if(allow_origin != NULL) {
item = janus_config_get(config, config_cors, janus_config_type_item, "enforce_cors");
if(item && item->value && janus_is_true(item->value)) {
enforce_cors = TRUE;
JANUS_LOG(LOG_INFO, "Going to enforce CORS by rejecting WebSocket connections\n");
}
}

/* Check if we need to enable the transport level ping/pong mechanism */
int pingpong_trigger = 0, pingpong_timeout = 0;
item = janus_config_get(config, config_general, janus_config_type_item, "pingpong_trigger");
Expand Down Expand Up @@ -1270,6 +1290,64 @@ static int janus_websockets_common_callback(
}
return 0;
}
case LWS_CALLBACK_ADD_HEADERS: {
/* If CORS is enabled, check the headers and add our own */
struct lws_process_html_args *args = (struct lws_process_html_args *)in;
if(allow_origin == NULL) {
/* Return a wildcard for the Access-Control-Allow-Origin header */
if(lws_add_http_header_by_name(wsi,
(unsigned char *)"Access-Control-Allow-Origin:",
(unsigned char *)"*", 1,
(unsigned char **)&args->p,
(unsigned char *)args->p + args->max_len))
return 1;
} else {
/* Return the configured origin in the header */
if(lws_add_http_header_by_name(wsi,
(unsigned char *)"Access-Control-Allow-Origin:",
(unsigned char *)allow_origin, strlen(allow_origin),
(unsigned char **)&args->p,
(unsigned char *)args->p + args->max_len))
return 1;
char origin[256], headers[256], methods[256];
origin[0] = '\0';
headers[0] = '\0';
methods[0] = '\0';
int olen = lws_hdr_total_length(wsi, WSI_TOKEN_ORIGIN);
if(olen > 0 && olen < 255) {
lws_hdr_copy(wsi, origin, sizeof(origin), WSI_TOKEN_ORIGIN);
}
int hlen = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AC_REQUEST_HEADERS);
if(hlen > 0 && hlen < 255) {
lws_hdr_copy(wsi, headers, sizeof(headers), WSI_TOKEN_HTTP_AC_REQUEST_HEADERS);
if(lws_add_http_header_by_name(wsi,
(unsigned char *)"Access-Control-Allow-Headers:",
(unsigned char *)headers, strlen(headers),
(unsigned char **)&args->p,
(unsigned char *)args->p + args->max_len))
return 1;
}
int mlen = lws_hdr_custom_length(wsi, "Access-Control-Request-Methods", strlen("Access-Control-Request-Methods"));
if(mlen > 0 && mlen < 255) {
lws_hdr_custom_copy(wsi, methods, sizeof(methods),
"Access-Control-Request-Methods", strlen("Access-Control-Request-Methods"));
if(lws_add_http_header_by_name(wsi,
(unsigned char *)"Access-Control-Allow-Methods:",
(unsigned char *)methods, strlen(methods),
(unsigned char **)&args->p,
(unsigned char *)args->p + args->max_len))
return 1;
}
/* WebSockets are not bound by CORS, but we can enforce this */
if(enforce_cors) {
if(strlen(origin) == 0 || strstr(origin, allow_origin) != origin) {
JANUS_LOG(LOG_ERR, "[%s-%p] Invalid origin, rejecting...\n", log_prefix, wsi);
return -1;
}
}
}
return 0;
}
case LWS_CALLBACK_RECEIVE: {
JANUS_LOG(LOG_HUGE, "[%s-%p] Got %zu bytes:\n", log_prefix, wsi, len);
if(ws_client == NULL || ws_client->wsi == NULL) {
Expand Down

0 comments on commit b207cbd

Please sign in to comment.