Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New option to enforce CORS in HTTP and WS transport plugins #2410

Merged
merged 1 commit into from
Oct 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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