diff options
author | Costa Tsaousis <costa@tsaousis.gr> | 2017-10-04 09:19:39 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-10-04 09:19:39 +0300 |
commit | d4696776576263ac7bebf529006513992e2610bb (patch) | |
tree | 78da671348dec6ad3f969c17cd3afbc1f47e3bb3 | |
parent | bc847681af3c4ac6370c4fc677fe3884592c911f (diff) | |
parent | bee0f18b2e303858f96996c3afa61b4c9cb502ef (diff) |
Merge pull request #2838 from ktsaou/master
add ACL allow dashboard from;
-rw-r--r-- | src/main.c | 7 | ||||
-rw-r--r-- | src/web_api_v1.c | 101 | ||||
-rw-r--r-- | src/web_client.c | 67 | ||||
-rw-r--r-- | src/web_client.h | 22 |
4 files changed, 135 insertions, 62 deletions
diff --git a/src/main.c b/src/main.c index 1a6c2d76bc..e52d7573ac 100644 --- a/src/main.c +++ b/src/main.c @@ -84,9 +84,10 @@ void web_server_config_options(void) { if(!*web_x_frame_options) web_x_frame_options = NULL; web_allow_connections_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), SIMPLE_PATTERN_EXACT); - web_allow_badges_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), SIMPLE_PATTERN_EXACT); - web_allow_registry_from = simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), SIMPLE_PATTERN_EXACT); - web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), SIMPLE_PATTERN_EXACT); + web_allow_dashboard_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), SIMPLE_PATTERN_EXACT); + web_allow_badges_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), SIMPLE_PATTERN_EXACT); + web_allow_registry_from = simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), SIMPLE_PATTERN_EXACT); + web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), SIMPLE_PATTERN_EXACT); web_allow_netdataconf_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow netdata.conf from", "localhost fd* 10.* 192.168.* 172.16.* 172.17.* 172.18.* 172.19.* 172.20.* 172.21.* 172.22.* 172.23.* 172.24.* 172.25.* 172.26.* 172.27.* 172.28.* 172.29.* 172.30.* 172.31.*"), SIMPLE_PATTERN_EXACT); #ifdef NETDATA_WITH_ZLIB diff --git a/src/web_api_v1.c b/src/web_api_v1.c index 8e845dfbb6..9376babdf4 100644 --- a/src/web_api_v1.c +++ b/src/web_api_v1.c @@ -298,9 +298,6 @@ inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w, } int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) { - if(unlikely(web_allow_badges_from && !simple_pattern_matches(web_allow_badges_from, w->client_ip))) - return web_client_permission_denied(w); - int ret = 400; buffer_flush(w->response.data); @@ -825,9 +822,16 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * return 400; } - // for all calls except HELLO, we have to check the ACL - if(unlikely(action != 'H' && web_allow_registry_from && !simple_pattern_matches(web_allow_registry_from, w->client_ip))) - return web_client_permission_denied(w); + if(unlikely(action == 'H')) { + // HELLO request, dashboard ACL + if(unlikely(!web_client_can_access_dashboard(w))) + return web_client_permission_denied(w); + } + else { + // everything else, registry ACL + if(unlikely(!web_client_can_access_registry(w))) + return web_client_permission_denied(w); + } switch(action) { case 'A': @@ -884,19 +888,40 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client * } } +static struct api_command { + const char *command; + uint32_t hash; + WEB_CLIENT_ACL acl; + int (*callback)(RRDHOST *host, struct web_client *w, char *url); +} api_commands[] = { + { "data", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_data }, + { "chart", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_chart }, + { "charts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_charts }, + + // registry checks the ACL by itself, so we allow everything + { "registry", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v1_registry }, + + // badges can be fetched with both dashboard and badge permissions + { "badge.svg", 0, WEB_CLIENT_ACL_DASHBOARD|WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge }, + + { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms }, + { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_log }, + { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_variables }, + { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_allmetrics }, + + // terminator + { NULL, 0, WEB_CLIENT_ACL_NONE, NULL }, +}; + inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) { - static uint32_t hash_data = 0, hash_chart = 0, hash_charts = 0, hash_registry = 0, hash_badge = 0, hash_alarms = 0, hash_alarm_log = 0, hash_alarm_variables = 0, hash_raw = 0; + static int initialized = 0; + int i; - if(unlikely(hash_data == 0)) { - hash_data = simple_hash("data"); - hash_chart = simple_hash("chart"); - hash_charts = simple_hash("charts"); - hash_registry = simple_hash("registry"); - hash_badge = simple_hash("badge.svg"); - hash_alarms = simple_hash("alarms"); - hash_alarm_log = simple_hash("alarm_log"); - hash_alarm_variables = simple_hash("alarm_variables"); - hash_raw = simple_hash("allmetrics"); + if(unlikely(initialized == 0)) { + initialized = 1; + + for(i = 0; api_commands[i].command ; i++) + api_commands[i].hash = simple_hash(api_commands[i].command); } // get the command @@ -905,39 +930,19 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char * debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok); uint32_t hash = simple_hash(tok); - if(hash == hash_data && !strcmp(tok, "data")) - return web_client_api_request_v1_data(host, w, url); - - else if(hash == hash_chart && !strcmp(tok, "chart")) - return web_client_api_request_v1_chart(host, w, url); - - else if(hash == hash_charts && !strcmp(tok, "charts")) - return web_client_api_request_v1_charts(host, w, url); + for(i = 0; api_commands[i].command ;i++) { + if(unlikely(hash == api_commands[i].hash && !strcmp(tok, api_commands[i].command))) { + if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NOCHECK) && !(w->acl & api_commands[i].acl)) + return web_client_permission_denied(w); - else if(hash == hash_registry && !strcmp(tok, "registry")) - return web_client_api_request_v1_registry(host, w, url); - - else if(hash == hash_badge && !strcmp(tok, "badge.svg")) - return web_client_api_request_v1_badge(host, w, url); - - else if(hash == hash_alarms && !strcmp(tok, "alarms")) - return web_client_api_request_v1_alarms(host, w, url); - - else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log")) - return web_client_api_request_v1_alarm_log(host, w, url); - - else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables")) - return web_client_api_request_v1_alarm_variables(host, w, url); - - else if(hash == hash_raw && !strcmp(tok, "allmetrics")) - return web_client_api_request_v1_allmetrics(host, w, url); - - else { - buffer_flush(w->response.data); - buffer_strcat(w->response.data, "Unsupported v1 API command: "); - buffer_strcat_htmlescape(w->response.data, tok); - return 404; + return api_commands[i].callback(host, w, url); + } } + + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "Unsupported v1 API command: "); + buffer_strcat_htmlescape(w->response.data, tok); + return 404; } else { buffer_flush(w->response.data); diff --git a/src/web_client.c b/src/web_client.c index 8bfd0ddda2..e17bac922f 100644 --- a/src/web_client.c +++ b/src/web_client.c @@ -9,11 +9,14 @@ int respect_web_browser_do_not_track_policy = 0; char *web_x_frame_options = NULL; SIMPLE_PATTERN *web_allow_connections_from = NULL; -SIMPLE_PATTERN *web_allow_registry_from = NULL; -SIMPLE_PATTERN *web_allow_badges_from = NULL; SIMPLE_PATTERN *web_allow_streaming_from = NULL; SIMPLE_PATTERN *web_allow_netdataconf_from = NULL; +// WEB_CLIENT_ACL +SIMPLE_PATTERN *web_allow_dashboard_from = NULL; +SIMPLE_PATTERN *web_allow_registry_from = NULL; +SIMPLE_PATTERN *web_allow_badges_from = NULL; + #ifdef NETDATA_WITH_ZLIB int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRATEGY; #endif /* NETDATA_WITH_ZLIB */ @@ -59,7 +62,7 @@ static inline int web_client_uncrock_socket(struct web_client *w) { inline int web_client_permission_denied(struct web_client *w) { w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); - buffer_strcat(w->response.data, "You do not allowed to access this resource."); + buffer_strcat(w->response.data, "You are not allowed to access this resource."); w->response.code = 403; return 403; } @@ -68,6 +71,19 @@ static void log_connection(struct web_client *w, const char *msg) { log_access("%llu: %d '[%s]:%s' '%s'", w->id, gettid(), w->client_ip, w->client_port, msg); } +static void web_client_update_acl_matches(struct web_client *w) { + w->acl = WEB_CLIENT_ACL_NONE; + + if(!web_allow_dashboard_from || simple_pattern_matches(web_allow_dashboard_from, w->client_ip)) + w->acl |= WEB_CLIENT_ACL_DASHBOARD; + + if(!web_allow_registry_from || simple_pattern_matches(web_allow_registry_from, w->client_ip)) + w->acl |= WEB_CLIENT_ACL_REGISTRY; + + if(!web_allow_badges_from || simple_pattern_matches(web_allow_badges_from, w->client_ip)) + w->acl |= WEB_CLIENT_ACL_BADGE; +} + struct web_client *web_client_create(int listener) { struct web_client *w; @@ -106,6 +122,8 @@ struct web_client *web_client_create(int listener) { error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id); } + web_client_update_acl_matches(w); + w->response.data = buffer_create(INITIAL_WEB_DATA_LENGTH); w->response.header = buffer_create(HTTP_RESPONSE_HEADER_SIZE); w->response.header_output = buffer_create(HTTP_RESPONSE_HEADER_SIZE); @@ -329,6 +347,9 @@ gid_t web_files_gid(void) { int mysendfile(struct web_client *w, char *filename) { debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename); + if(!web_client_can_access_dashboard(w)) + return web_client_permission_denied(w); + // skip leading slashes while (*filename == '/') filename++; @@ -611,6 +632,13 @@ static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char return func(host, w, url); } +static inline int check_host_and_dashboard_acl_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) { + if(!web_client_can_access_dashboard(w)) + return web_client_permission_denied(w); + + return check_host_and_call(host, w, url, func); +} + int web_client_api_request(RRDHOST *host, struct web_client *w, char *url) { // get the api version @@ -1140,26 +1168,26 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch } else if(unlikely(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0)) { // old API "data" debug(D_WEB_CLIENT_ACCESS, "%llu: old API data request...", w->id); - return check_host_and_call(host, w, url, web_client_api_old_data_request_json); + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_data_request_json); } else if(unlikely(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0)) { // old API "datasource" debug(D_WEB_CLIENT_ACCESS, "%llu: old API datasource request...", w->id); - return check_host_and_call(host, w, url, web_client_api_old_data_request_jsonp); + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_data_request_jsonp); } else if(unlikely(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0)) { // old API "graph" debug(D_WEB_CLIENT_ACCESS, "%llu: old API graph request...", w->id); - return check_host_and_call(host, w, url, web_client_api_old_graph_request); + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_graph_request); } else if(unlikely(hash == hash_list && strcmp(tok, "list") == 0)) { // old API "list" debug(D_WEB_CLIENT_ACCESS, "%llu: old API list request...", w->id); - return check_host_and_call(host, w, url, web_client_api_old_list_request); + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_list_request); } else if(unlikely(hash == hash_all_json && strcmp(tok, "all.json") == 0)) { // old API "all.json" debug(D_WEB_CLIENT_ACCESS, "%llu: old API all.json request...", w->id); - return check_host_and_call(host, w, url, web_client_api_old_all_json); + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_all_json); } else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf - if(unlikely(web_allow_netdataconf_from && !simple_pattern_matches(web_allow_netdataconf_from, w->client_ip))) + if(unlikely(!web_client_can_access_netdataconf(w))) return web_client_permission_denied(w); debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id); @@ -1170,6 +1198,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch } #ifdef NETDATA_INTERNAL_CHECKS else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) { + if(unlikely(!web_client_can_access_netdataconf(w))) + return web_client_permission_denied(w); + w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); @@ -1183,6 +1214,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch return 200; } else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) { + if(unlikely(!web_client_can_access_netdataconf(w))) + return web_client_permission_denied(w); + buffer_flush(w->response.data); // get the name of the data to show @@ -1220,6 +1254,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch return 400; } else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) { + if(unlikely(!web_client_can_access_netdataconf(w))) + return web_client_permission_denied(w); + debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id); // replace the zero bytes with spaces @@ -1250,7 +1287,7 @@ void web_client_process_request(struct web_client *w) { case HTTP_VALIDATION_OK: switch(w->mode) { case WEB_CLIENT_MODE_STREAM: - if(unlikely(web_allow_streaming_from && !simple_pattern_matches(web_allow_streaming_from, w->client_ip))) { + if(unlikely(!web_client_can_access_stream(w))) { web_client_permission_denied(w); return; } @@ -1259,6 +1296,11 @@ void web_client_process_request(struct web_client *w) { return; case WEB_CLIENT_MODE_OPTIONS: + if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) { + web_client_permission_denied(w); + return; + } + w->response.data->contenttype = CT_TEXT_PLAIN; buffer_flush(w->response.data); buffer_strcat(w->response.data, "OK"); @@ -1267,6 +1309,11 @@ void web_client_process_request(struct web_client *w) { case WEB_CLIENT_MODE_FILECOPY: case WEB_CLIENT_MODE_NORMAL: + if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) { + web_client_permission_denied(w); + return; + } + w->response.code = web_client_process_url(localhost, w, w->decoded_url); break; } diff --git a/src/web_client.h b/src/web_client.h index 37fb5f6c7e..a07558e1e8 100644 --- a/src/web_client.h +++ b/src/web_client.h @@ -44,7 +44,7 @@ typedef enum web_client_flags { //#define web_client_flag_set(w, flag) __atomic_or_fetch(&((w)->flags), flag, __ATOMIC_SEQ_CST) //#define web_client_flag_clear(w, flag) __atomic_and_fetch(&((w)->flags), ~flag, __ATOMIC_SEQ_CST) //#else -#define web_client_flag_check(w, flag) ((w)->flags & flag) +#define web_client_flag_check(w, flag) ((w)->flags & (flag)) #define web_client_flag_set(w, flag) (w)->flags |= flag #define web_client_flag_clear(w, flag) (w)->flags &= ~flag //#endif @@ -108,11 +108,30 @@ struct response { }; +typedef enum web_client_acl { + WEB_CLIENT_ACL_NONE = 0, + WEB_CLIENT_ACL_NOCHECK = 0, + WEB_CLIENT_ACL_DASHBOARD = 1 << 0, + WEB_CLIENT_ACL_REGISTRY = 1 << 1, + WEB_CLIENT_ACL_BADGE = 1 << 2 +} WEB_CLIENT_ACL; + +#define web_client_can_access_dashboard(w) ((w)->acl & WEB_CLIENT_ACL_DASHBOARD) +#define web_client_can_access_registry(w) ((w)->acl & WEB_CLIENT_ACL_REGISTRY) +#define web_client_can_access_badges(w) ((w)->acl & WEB_CLIENT_ACL_BADGE) + +#define web_client_can_access_stream(w) \ + (!web_allow_streaming_from || simple_pattern_matches(web_allow_streaming_from, (w)->client_ip)) + +#define web_client_can_access_netdataconf(w) \ + (!web_allow_netdataconf_from || simple_pattern_matches(web_allow_netdataconf_from, (w)->client_ip)) + struct web_client { unsigned long long id; WEB_CLIENT_FLAGS flags; // status flags for the client WEB_CLIENT_MODE mode; // the operational mode of the client + WEB_CLIENT_ACL acl; // the access list of the client int tcp_cork; // 1 = we have a cork on the socket @@ -144,6 +163,7 @@ struct web_client { extern struct web_client *web_clients; extern SIMPLE_PATTERN *web_allow_connections_from; +extern SIMPLE_PATTERN *web_allow_dashboard_from; extern SIMPLE_PATTERN *web_allow_registry_from; extern SIMPLE_PATTERN *web_allow_badges_from; extern SIMPLE_PATTERN *web_allow_streaming_from; |