@cryptotaxi247 / netdata-1 / commits / e43a44134

add ACL allow dashboard from; fixes #2828

Costa Tsaousis (ktsaou) committed Oct 4, 2017 at 00:57 UTC e43a44134eb7097f2cd3aeba7f2868e40b27ef58
4 files changed +125 -61
src/main.c
+4 -3
@@ -84,9 +84,10 @@ void web_server_config_options(void) {
84 if(!*web_x_frame_options) web_x_frame_options = NULL;
85
86 web_allow_connections_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow connections from", "localhost *"), SIMPLE_PATTERN_EXACT);
87 - web_allow_badges_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), SIMPLE_PATTERN_EXACT);
88 - web_allow_registry_from = simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), SIMPLE_PATTERN_EXACT);
89 - web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), SIMPLE_PATTERN_EXACT);
87 + web_allow_dashboard_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow dashboard from", "localhost *"), SIMPLE_PATTERN_EXACT);
88 + web_allow_badges_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow badges from", "*"), SIMPLE_PATTERN_EXACT);
89 + web_allow_registry_from = simple_pattern_create(config_get(CONFIG_SECTION_REGISTRY, "allow from", "*"), SIMPLE_PATTERN_EXACT);
90 + web_allow_streaming_from = simple_pattern_create(config_get(CONFIG_SECTION_WEB, "allow streaming from", "*"), SIMPLE_PATTERN_EXACT);
91 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);
92
93 #ifdef NETDATA_WITH_ZLIB
src/web_api_v1.c
+48 -48
@@ -298,9 +298,6 @@ inline int web_client_api_request_v1_chart(RRDHOST *host, struct web_client *w,
298 }
299
300 int web_client_api_request_v1_badge(RRDHOST *host, struct web_client *w, char *url) {
301 - if(unlikely(web_allow_badges_from && !simple_pattern_matches(web_allow_badges_from, w->client_ip)))
302 - return web_client_permission_denied(w);
303 -
301 int ret = 400;
302 buffer_flush(w->response.data);
303
@@ -825,9 +822,16 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
822 return 400;
823 }
824
828 - // for all calls except HELLO, we have to check the ACL
829 - if(unlikely(action != 'H' && web_allow_registry_from && !simple_pattern_matches(web_allow_registry_from, w->client_ip)))
830 - return web_client_permission_denied(w);
825 + if(unlikely(action == 'H')) {
826 + // HELLO request, dashboard ACL
827 + if(unlikely(!web_client_can_access_dashboard(w)))
828 + return web_client_permission_denied(w);
829 + }
830 + else {
831 + // everything else, registry ACL
832 + if(unlikely(!web_client_can_access_registry(w)))
833 + return web_client_permission_denied(w);
834 + }
835
836 switch(action) {
837 case 'A':
@@ -884,19 +888,35 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
888 }
889 }
890
891 +static struct api_command {
892 + const char *command;
893 + uint32_t hash;
894 + WEB_CLIENT_ACL acl;
895 + int (*callback)(RRDHOST *host, struct web_client *w, char *url);
896 +} api_commands[] = {
897 + { "data", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_data },
898 + { "chart", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_chart },
899 + { "charts", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_charts },
900 + { "registry", 0, WEB_CLIENT_ACL_NONE, web_client_api_request_v1_registry },
901 + { "badge.svg", 0, WEB_CLIENT_ACL_BADGE, web_client_api_request_v1_badge },
902 + { "alarms", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarms },
903 + { "alarm_log", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_log },
904 + { "alarm_variables", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_alarm_variables },
905 + { "allmetrics", 0, WEB_CLIENT_ACL_DASHBOARD, web_client_api_request_v1_allmetrics },
906 +
907 + // terminator
908 + { NULL, 0, WEB_CLIENT_ACL_NONE, NULL },
909 +};
910 +
911 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url) {
888 - 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;
912 + static int initialized = 0;
913 + int i;
914
890 - if(unlikely(hash_data == 0)) {
891 - hash_data = simple_hash("data");
892 - hash_chart = simple_hash("chart");
893 - hash_charts = simple_hash("charts");
894 - hash_registry = simple_hash("registry");
895 - hash_badge = simple_hash("badge.svg");
896 - hash_alarms = simple_hash("alarms");
897 - hash_alarm_log = simple_hash("alarm_log");
898 - hash_alarm_variables = simple_hash("alarm_variables");
899 - hash_raw = simple_hash("allmetrics");
915 + if(unlikely(initialized == 0)) {
916 + initialized = 1;
917 +
918 + for(i = 0; api_commands[i].command ; i++)
919 + api_commands[i].hash = simple_hash(api_commands[i].command);
920 }
921
922 // get the command
@@ -905,39 +925,19 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *
925 debug(D_WEB_CLIENT, "%llu: Searching for API v1 command '%s'.", w->id, tok);
926 uint32_t hash = simple_hash(tok);
927
908 - if(hash == hash_data && !strcmp(tok, "data"))
909 - return web_client_api_request_v1_data(host, w, url);
910 -
911 - else if(hash == hash_chart && !strcmp(tok, "chart"))
912 - return web_client_api_request_v1_chart(host, w, url);
913 -
914 - else if(hash == hash_charts && !strcmp(tok, "charts"))
915 - return web_client_api_request_v1_charts(host, w, url);
916 -
917 - else if(hash == hash_registry && !strcmp(tok, "registry"))
918 - return web_client_api_request_v1_registry(host, w, url);
919 -
920 - else if(hash == hash_badge && !strcmp(tok, "badge.svg"))
921 - return web_client_api_request_v1_badge(host, w, url);
928 + for(i = 0; api_commands[i].command ;i++) {
929 + if(unlikely(hash == api_commands[i].hash && !strcmp(tok, api_commands[i].command))) {
930 + if(unlikely(api_commands[i].acl != WEB_CLIENT_ACL_NONE) && !(w->acl & api_commands[i].acl))
931 + return web_client_permission_denied(w);
932
923 - else if(hash == hash_alarms && !strcmp(tok, "alarms"))
924 - return web_client_api_request_v1_alarms(host, w, url);
925 -
926 - else if(hash == hash_alarm_log && !strcmp(tok, "alarm_log"))
927 - return web_client_api_request_v1_alarm_log(host, w, url);
928 -
929 - else if(hash == hash_alarm_variables && !strcmp(tok, "alarm_variables"))
930 - return web_client_api_request_v1_alarm_variables(host, w, url);
931 -
932 - else if(hash == hash_raw && !strcmp(tok, "allmetrics"))
933 - return web_client_api_request_v1_allmetrics(host, w, url);
934 -
935 - else {
936 - buffer_flush(w->response.data);
937 - buffer_strcat(w->response.data, "Unsupported v1 API command: ");
938 - buffer_strcat_htmlescape(w->response.data, tok);
939 - return 404;
933 + return api_commands[i].callback(host, w, url);
934 + }
935 }
936 +
937 + buffer_flush(w->response.data);
938 + buffer_strcat(w->response.data, "Unsupported v1 API command: ");
939 + buffer_strcat_htmlescape(w->response.data, tok);
940 + return 404;
941 }
942 else {
943 buffer_flush(w->response.data);
src/web_client.c
+53 -9
@@ -9,11 +9,14 @@ int respect_web_browser_do_not_track_policy = 0;
9 char *web_x_frame_options = NULL;
10
11 SIMPLE_PATTERN *web_allow_connections_from = NULL;
12 -SIMPLE_PATTERN *web_allow_registry_from = NULL;
13 -SIMPLE_PATTERN *web_allow_badges_from = NULL;
12 SIMPLE_PATTERN *web_allow_streaming_from = NULL;
13 SIMPLE_PATTERN *web_allow_netdataconf_from = NULL;
14
15 +// WEB_CLIENT_ACL
16 +SIMPLE_PATTERN *web_allow_dashboard_from = NULL;
17 +SIMPLE_PATTERN *web_allow_registry_from = NULL;
18 +SIMPLE_PATTERN *web_allow_badges_from = NULL;
19 +
20 #ifdef NETDATA_WITH_ZLIB
21 int web_enable_gzip = 1, web_gzip_level = 3, web_gzip_strategy = Z_DEFAULT_STRATEGY;
22 #endif /* NETDATA_WITH_ZLIB */
@@ -68,6 +71,19 @@ static void log_connection(struct web_client *w, const char *msg) {
71 log_access("%llu: %d '[%s]:%s' '%s'", w->id, gettid(), w->client_ip, w->client_port, msg);
72 }
73
74 +static void web_client_update_acl_matches(struct web_client *w) {
75 + w->acl = WEB_CLIENT_ACL_NONE;
76 +
77 + if(!web_allow_dashboard_from || simple_pattern_matches(web_allow_dashboard_from, w->client_ip))
78 + w->acl |= WEB_CLIENT_ACL_DASHBOARD;
79 +
80 + if(!web_allow_registry_from || simple_pattern_matches(web_allow_registry_from, w->client_ip))
81 + w->acl |= WEB_CLIENT_ACL_REGISTRY;
82 +
83 + if(!web_allow_badges_from || simple_pattern_matches(web_allow_badges_from, w->client_ip))
84 + w->acl |= WEB_CLIENT_ACL_BADGE;
85 +}
86 +
87 struct web_client *web_client_create(int listener) {
88 struct web_client *w;
89
@@ -106,6 +122,8 @@ struct web_client *web_client_create(int listener) {
122 error("%llu: Cannot set SO_KEEPALIVE on socket.", w->id);
123 }
124
125 + web_client_update_acl_matches(w);
126 +
127 w->response.data = buffer_create(INITIAL_WEB_DATA_LENGTH);
128 w->response.header = buffer_create(HTTP_RESPONSE_HEADER_SIZE);
129 w->response.header_output = buffer_create(HTTP_RESPONSE_HEADER_SIZE);
@@ -611,6 +629,13 @@ static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char
629 return func(host, w, url);
630 }
631
632 +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 *)) {
633 + if(!web_client_can_access_dashboard(w))
634 + return web_client_permission_denied(w);
635 +
636 + return check_host_and_call(host, w, url, func);
637 +}
638 +
639 int web_client_api_request(RRDHOST *host, struct web_client *w, char *url)
640 {
641 // get the api version
@@ -1140,26 +1165,26 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1165 }
1166 else if(unlikely(hash == hash_data && strcmp(tok, WEB_PATH_DATA) == 0)) { // old API "data"
1167 debug(D_WEB_CLIENT_ACCESS, "%llu: old API data request...", w->id);
1143 - return check_host_and_call(host, w, url, web_client_api_old_data_request_json);
1168 + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_data_request_json);
1169 }
1170 else if(unlikely(hash == hash_datasource && strcmp(tok, WEB_PATH_DATASOURCE) == 0)) { // old API "datasource"
1171 debug(D_WEB_CLIENT_ACCESS, "%llu: old API datasource request...", w->id);
1147 - return check_host_and_call(host, w, url, web_client_api_old_data_request_jsonp);
1172 + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_data_request_jsonp);
1173 }
1174 else if(unlikely(hash == hash_graph && strcmp(tok, WEB_PATH_GRAPH) == 0)) { // old API "graph"
1175 debug(D_WEB_CLIENT_ACCESS, "%llu: old API graph request...", w->id);
1151 - return check_host_and_call(host, w, url, web_client_api_old_graph_request);
1176 + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_graph_request);
1177 }
1178 else if(unlikely(hash == hash_list && strcmp(tok, "list") == 0)) { // old API "list"
1179 debug(D_WEB_CLIENT_ACCESS, "%llu: old API list request...", w->id);
1155 - return check_host_and_call(host, w, url, web_client_api_old_list_request);
1180 + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_list_request);
1181 }
1182 else if(unlikely(hash == hash_all_json && strcmp(tok, "all.json") == 0)) { // old API "all.json"
1183 debug(D_WEB_CLIENT_ACCESS, "%llu: old API all.json request...", w->id);
1159 - return check_host_and_call(host, w, url, web_client_api_old_all_json);
1184 + return check_host_and_dashboard_acl_and_call(host, w, url, web_client_api_old_all_json);
1185 }
1186 else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf
1162 - if(unlikely(web_allow_netdataconf_from && !simple_pattern_matches(web_allow_netdataconf_from, w->client_ip)))
1187 + if(unlikely(!web_client_can_access_netdataconf(w)))
1188 return web_client_permission_denied(w);
1189
1190 debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id);
@@ -1170,6 +1195,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1195 }
1196 #ifdef NETDATA_INTERNAL_CHECKS
1197 else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) {
1198 + if(unlikely(!web_client_can_access_netdataconf(w)))
1199 + return web_client_permission_denied(w);
1200 +
1201 w->response.data->contenttype = CT_TEXT_PLAIN;
1202 buffer_flush(w->response.data);
1203
@@ -1183,6 +1211,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1211 return 200;
1212 }
1213 else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) {
1214 + if(unlikely(!web_client_can_access_netdataconf(w)))
1215 + return web_client_permission_denied(w);
1216 +
1217 buffer_flush(w->response.data);
1218
1219 // get the name of the data to show
@@ -1220,6 +1251,9 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1251 return 400;
1252 }
1253 else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) {
1254 + if(unlikely(!web_client_can_access_netdataconf(w)))
1255 + return web_client_permission_denied(w);
1256 +
1257 debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
1258
1259 // replace the zero bytes with spaces
@@ -1250,7 +1284,7 @@ void web_client_process_request(struct web_client *w) {
1284 case HTTP_VALIDATION_OK:
1285 switch(w->mode) {
1286 case WEB_CLIENT_MODE_STREAM:
1253 - if(unlikely(web_allow_streaming_from && !simple_pattern_matches(web_allow_streaming_from, w->client_ip))) {
1287 + if(unlikely(!web_client_can_access_stream(w))) {
1288 web_client_permission_denied(w);
1289 return;
1290 }
@@ -1259,6 +1293,11 @@ void web_client_process_request(struct web_client *w) {
1293 return;
1294
1295 case WEB_CLIENT_MODE_OPTIONS:
1296 + if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) {
1297 + web_client_permission_denied(w);
1298 + return;
1299 + }
1300 +
1301 w->response.data->contenttype = CT_TEXT_PLAIN;
1302 buffer_flush(w->response.data);
1303 buffer_strcat(w->response.data, "OK");
@@ -1267,6 +1306,11 @@ void web_client_process_request(struct web_client *w) {
1306
1307 case WEB_CLIENT_MODE_FILECOPY:
1308 case WEB_CLIENT_MODE_NORMAL:
1309 + if(unlikely(!web_client_can_access_dashboard(w) && !web_client_can_access_registry(w) && !web_client_can_access_badges(w))) {
1310 + web_client_permission_denied(w);
1311 + return;
1312 + }
1313 +
1314 w->response.code = web_client_process_url(localhost, w, w->decoded_url);
1315 break;
1316 }
src/web_client.h
+20 -1
@@ -44,7 +44,7 @@ typedef enum web_client_flags {
44 //#define web_client_flag_set(w, flag) __atomic_or_fetch(&((w)->flags), flag, __ATOMIC_SEQ_CST)
45 //#define web_client_flag_clear(w, flag) __atomic_and_fetch(&((w)->flags), ~flag, __ATOMIC_SEQ_CST)
46 //#else
47 -#define web_client_flag_check(w, flag) ((w)->flags & flag)
47 +#define web_client_flag_check(w, flag) ((w)->flags & (flag))
48 #define web_client_flag_set(w, flag) (w)->flags |= flag
49 #define web_client_flag_clear(w, flag) (w)->flags &= ~flag
50 //#endif
@@ -108,11 +108,29 @@ struct response {
108
109 };
110
111 +typedef enum web_client_acl {
112 + WEB_CLIENT_ACL_NONE = 0,
113 + WEB_CLIENT_ACL_DASHBOARD = 1 << 0,
114 + WEB_CLIENT_ACL_REGISTRY = 1 << 1,
115 + WEB_CLIENT_ACL_BADGE = 1 << 2
116 +} WEB_CLIENT_ACL;
117 +
118 +#define web_client_can_access_dashboard(w) ((w)->acl & WEB_CLIENT_ACL_DASHBOARD)
119 +#define web_client_can_access_registry(w) ((w)->acl & WEB_CLIENT_ACL_REGISTRY)
120 +#define web_client_can_access_badges(w) ((w)->acl & WEB_CLIENT_ACL_BADGE)
121 +
122 +#define web_client_can_access_stream(w) \
123 + (!web_allow_streaming_from || simple_pattern_matches(web_allow_streaming_from, (w)->client_ip))
124 +
125 +#define web_client_can_access_netdataconf(w) \
126 + (!web_allow_netdataconf_from || simple_pattern_matches(web_allow_netdataconf_from, (w)->client_ip))
127 +
128 struct web_client {
129 unsigned long long id;
130
131 WEB_CLIENT_FLAGS flags; // status flags for the client
132 WEB_CLIENT_MODE mode; // the operational mode of the client
133 + WEB_CLIENT_ACL acl; // the access list of the client
134
135 int tcp_cork; // 1 = we have a cork on the socket
136
@@ -144,6 +162,7 @@ struct web_client {
162
163 extern struct web_client *web_clients;
164 extern SIMPLE_PATTERN *web_allow_connections_from;
165 +extern SIMPLE_PATTERN *web_allow_dashboard_from;
166 extern SIMPLE_PATTERN *web_allow_registry_from;
167 extern SIMPLE_PATTERN *web_allow_badges_from;
168 extern SIMPLE_PATTERN *web_allow_streaming_from;