summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorCosta Tsaousis <costa@tsaousis.gr>2017-10-04 09:19:39 +0300
committerGitHub <noreply@github.com>2017-10-04 09:19:39 +0300
commitd4696776576263ac7bebf529006513992e2610bb (patch)
tree78da671348dec6ad3f969c17cd3afbc1f47e3bb3
parentbc847681af3c4ac6370c4fc677fe3884592c911f (diff)
parentbee0f18b2e303858f96996c3afa61b4c9cb502ef (diff)
Merge pull request #2838 from ktsaou/master
add ACL allow dashboard from;
-rw-r--r--src/main.c7
-rw-r--r--src/web_api_v1.c101
-rw-r--r--src/web_client.c67
-rw-r--r--src/web_client.h22
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;