summaryrefslogtreecommitdiffstats
path: root/libnetdata
diff options
context:
space:
mode:
authorCosta Tsaousis <costa@netdata.cloud>2024-01-29 09:18:01 +0200
committerGitHub <noreply@github.com>2024-01-29 09:18:01 +0200
commit84474006d4cf9eb78a47a3bdffbbedb3964f0068 (patch)
tree6abf1999d02a75c20ee18ce10b91b457df01fe77 /libnetdata
parent6fe7cfc0e096ce4dd1c2aa2f5ace8752bffdab44 (diff)
New Permissions System (#16837)
* wip of migrating to bitmap permissions * replace role with bitmapped permissions * formatting permissions using macros * accept view and edit permissions for all dynamic configuration * work on older compilers * parse the header in hex * agreed permissions updates * map permissions to old roles * new permissions management * fix function rename * build libdatachannel when enabled - currently for code maintainance * dyncfg now keeps 2 sets of statuses, to keep track of what happens to dyncfg and what actually happens with the plugin * complete the additions of jobs and solve unittests * fix renumbering of ACL bits * processes function shows the cmdline based on permissions and the presence of the sensitive data permission * now the agent returns 412 when authorization is missing, 403 when authorization exists but permissions are not enough, 451 when access control list prevents the user from accessing the dashboard * enable cmdline on processes with thhe HTTP_ACCESS_VIEW_AGENT_CONFIG permission * by default functions require anonymous-data access * fix compilation on debian * fix left-over renamed define * updated schema for alerts * updated permissions * require a name when loading json payloads, if the name is not provided by dyncfg
Diffstat (limited to 'libnetdata')
-rw-r--r--libnetdata/config/dyncfg.c4
-rw-r--r--libnetdata/config/dyncfg.h6
-rw-r--r--libnetdata/functions_evloop/functions_evloop.c61
-rw-r--r--libnetdata/functions_evloop/functions_evloop.h75
-rw-r--r--libnetdata/http/http_access.c187
-rw-r--r--libnetdata/http/http_access.h144
-rw-r--r--libnetdata/http/http_defs.h9
-rw-r--r--libnetdata/log/log.c4
-rw-r--r--libnetdata/log/log.h1
-rw-r--r--libnetdata/socket/socket.c11
10 files changed, 396 insertions, 106 deletions
diff --git a/libnetdata/config/dyncfg.c b/libnetdata/config/dyncfg.c
index e67d1a4758..0d6d5058db 100644
--- a/libnetdata/config/dyncfg.c
+++ b/libnetdata/config/dyncfg.c
@@ -251,7 +251,7 @@ int dyncfg_default_response(BUFFER *wb, int code, const char *msg) {
int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction, const char *function,
usec_t *stop_monotonic_ut, bool *cancelled,
- BUFFER *payload, const char *source, BUFFER *result) {
+ BUFFER *payload, HTTP_ACCESS access, const char *source, BUFFER *result) {
if(!function || !*function)
return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "command received is empty");
@@ -284,7 +284,7 @@ int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction,
buffer_flush(result);
result->content_type = CT_APPLICATION_JSON;
- int code = df->cb(transaction, id, cmd, add_name, payload, stop_monotonic_ut, cancelled, result, source, df->data);
+ int code = df->cb(transaction, id, cmd, add_name, payload, stop_monotonic_ut, cancelled, result, access, source, df->data);
if(!result->expires)
result->expires = now_realtime_sec();
diff --git a/libnetdata/config/dyncfg.h b/libnetdata/config/dyncfg.h
index 6c4f6448e5..eb31f8c25a 100644
--- a/libnetdata/config/dyncfg.h
+++ b/libnetdata/config/dyncfg.h
@@ -66,7 +66,9 @@ char *dyncfg_escape_id_for_filename(const char *id);
#include "../buffer/buffer.h"
#include "../dictionary/dictionary.h"
-typedef int (*dyncfg_cb_t)(const char *transaction, const char *id, DYNCFG_CMDS cmd, const char *add_name, BUFFER *payload, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *result, const char *source, void *data);
+typedef int (*dyncfg_cb_t)(const char *transaction, const char *id, DYNCFG_CMDS cmd, const char *add_name,
+ BUFFER *payload, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *result,
+ HTTP_ACCESS access, const char *source, void *data);
struct dyncfg_node {
DYNCFG_TYPE type;
@@ -81,6 +83,6 @@ int dyncfg_default_response(BUFFER *wb, int code, const char *msg);
int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction, const char *function,
usec_t *stop_monotonic_ut, bool *cancelled,
- BUFFER *payload, const char *source, BUFFER *result);
+ BUFFER *payload, HTTP_ACCESS access, const char *source, BUFFER *result);
#endif //LIBNETDATA_DYNCFG_H
diff --git a/libnetdata/functions_evloop/functions_evloop.c b/libnetdata/functions_evloop/functions_evloop.c
index 3b490d9736..b21abe6294 100644
--- a/libnetdata/functions_evloop/functions_evloop.c
+++ b/libnetdata/functions_evloop/functions_evloop.c
@@ -2,7 +2,9 @@
#include "functions_evloop.h"
-static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, const char *source, void *data);
+static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut,
+ bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
+ const char *source, void *data);
struct functions_evloop_worker_job {
bool used;
@@ -14,6 +16,7 @@ struct functions_evloop_worker_job {
time_t timeout;
BUFFER *payload;
+ HTTP_ACCESS access;
const char *source;
functions_evloop_worker_execute_t cb;
@@ -90,7 +93,7 @@ static void *rrd_functions_worker_globals_worker_main(void *arg) {
last_acquired = true;
j = dictionary_acquired_item_value(acquired);
- j->cb(j->transaction, j->cmd, &j->stop_monotonic_ut, &j->cancelled, j->payload, j->source, j->cb_data);
+ j->cb(j->transaction, j->cmd, &j->stop_monotonic_ut, &j->cancelled, j->payload, j->access, j->source, j->cb_data);
dictionary_del(wg->worker_queue, j->transaction);
dictionary_acquired_item_release(wg->worker_queue, acquired);
dictionary_garbage_collect(wg->worker_queue);
@@ -101,7 +104,7 @@ static void *rrd_functions_worker_globals_worker_main(void *arg) {
return NULL;
}
-static void worker_add_job(struct functions_evloop_globals *wg, const char *keyword, char *transaction, char *function, char *timeout_s, BUFFER *payload, const char *source) {
+static void worker_add_job(struct functions_evloop_globals *wg, const char *keyword, char *transaction, char *function, char *timeout_s, BUFFER *payload, const char *access, const char *source) {
if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
keyword,
@@ -129,6 +132,7 @@ static void worker_add_job(struct functions_evloop_globals *wg, const char *keyw
.stop_monotonic_ut = now_monotonic_usec() + (timeout * USEC_PER_SEC),
.used = false,
.payload = buffer_dup(payload),
+ .access = http_access_from_hex(access),
.source = source ? strdupz(source) : NULL,
.cb = we->cb,
.cb_data = we->cb_data,
@@ -164,6 +168,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
char *transaction;
char *function;
char *timeout_s;
+ char *access;
char *source;
char *content_type;
} deferred = { 0 };
@@ -190,7 +195,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
if(deferred.enabled) {
char *s = (char *)buffer_tostring(buffer);
- if(strstr(&s[deferred.last_len], PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END "\n") != NULL) {
+ if(strstr(&s[deferred.last_len], PLUGINSD_CALL_FUNCTION_PAYLOAD_END "\n") != NULL) {
if(deferred.last_len > 0)
// remove the trailing newline from the buffer
deferred.last_len--;
@@ -198,12 +203,15 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
s[deferred.last_len] = '\0';
buffer->len = deferred.last_len;
buffer->content_type = content_type_string2id(deferred.content_type);
- worker_add_job(wg, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD, deferred.transaction, deferred.function, deferred.timeout_s, buffer, deferred.source);
+ worker_add_job(wg,
+ PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN, deferred.transaction, deferred.function,
+ deferred.timeout_s, buffer, deferred.access, deferred.source);
buffer_flush(buffer);
freez(deferred.transaction);
freez(deferred.function);
freez(deferred.timeout_s);
+ freez(deferred.access);
freez(deferred.source);
freez(deferred.content_type);
memset(&deferred, 0, sizeof(deferred));
@@ -219,29 +227,32 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
const char *keyword = get_word(words, num_words, 0);
- if(keyword && (strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0)) {
+ if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION) == 0)) {
char *transaction = get_word(words, num_words, 1);
char *timeout_s = get_word(words, num_words, 2);
char *function = get_word(words, num_words, 3);
- char *source = get_word(words, num_words, 4);
- worker_add_job(wg, keyword, transaction, function, timeout_s, NULL, source);
+ char *access = get_word(words, num_words, 4);
+ char *source = get_word(words, num_words, 5);
+ worker_add_job(wg, keyword, transaction, function, timeout_s, NULL, access, source);
}
- else if(keyword && (strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD) == 0)) {
+ else if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN) == 0)) {
char *transaction = get_word(words, num_words, 1);
char *timeout_s = get_word(words, num_words, 2);
char *function = get_word(words, num_words, 3);
- char *source = get_word(words, num_words, 4);
- char *content_type = get_word(words, num_words, 5);
+ char *access = get_word(words, num_words, 4);
+ char *source = get_word(words, num_words, 5);
+ char *content_type = get_word(words, num_words, 6);
deferred.transaction = strdupz(transaction ? transaction : "");
deferred.timeout_s = strdupz(timeout_s ? timeout_s : "");
deferred.function = strdupz(function ? function : "");
+ deferred.access = strdupz(access ? access : "");
deferred.source = strdupz(source ? source : "");
deferred.content_type = strdupz(content_type ? content_type : "");
deferred.last_len = 0;
deferred.enabled = true;
}
- else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_CANCEL) == 0) {
+ else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_CANCEL) == 0) {
char *transaction = get_word(words, num_words, 1);
const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
if(acquired) {
@@ -254,7 +265,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
else
nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "Received CANCEL for transaction '%s', but it not available here", transaction);
}
- else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_PROGRESS) == 0) {
+ else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_PROGRESS) == 0) {
char *transaction = get_word(words, num_words, 1);
const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
if(acquired) {
@@ -338,11 +349,12 @@ void functions_evloop_cancel_threads(struct functions_evloop_globals *wg){
// ----------------------------------------------------------------------------
static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
- BUFFER *payload, const char *source, void *data) {
+ BUFFER *payload, HTTP_ACCESS access, const char *source, void *data) {
struct functions_evloop_globals *wg = data;
CLEAN_BUFFER *result = buffer_create(1024, NULL);
- int code = dyncfg_node_find_and_call(wg->dyncfg.nodes, transaction, function, stop_monotonic_ut, cancelled, payload, source, result);
+ int code = dyncfg_node_find_and_call(wg->dyncfg.nodes, transaction, function, stop_monotonic_ut,
+ cancelled, payload, access, source, result);
netdata_mutex_lock(wg->stdout_mutex);
pluginsd_function_result_begin_to_stdout(transaction, code, content_type_id2string(result->content_type), result->expires);
@@ -352,7 +364,12 @@ static void functions_evloop_config_cb(const char *transaction, char *function,
netdata_mutex_unlock(wg->stdout_mutex);
}
-void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, dyncfg_cb_t cb, void *data) {
+void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
+ DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type,
+ const char *source, DYNCFG_CMDS cmds,
+ HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
+ dyncfg_cb_t cb, void *data) {
+
if(!dyncfg_is_valid_id(id)) {
nd_log(NDLS_COLLECTORS, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
return;
@@ -372,9 +389,15 @@ void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char
netdata_mutex_lock(wg->stdout_mutex);
fprintf(stdout,
- PLUGINSD_KEYWORD_CONFIG " '%s' " PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE " '%s' '%s' '%s' '%s' '%s' '%s'\n",
- id, dyncfg_id2status(status), dyncfg_id2type(type), path,
- dyncfg_id2source_type(source_type), source, buffer_tostring(c)
+ PLUGINSD_KEYWORD_CONFIG " '%s' " PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE " '%s' '%s' '%s' '%s' '%s' '%s' "HTTP_ACCESS_FORMAT" "HTTP_ACCESS_FORMAT"\n",
+ id,
+ dyncfg_id2status(status),
+ dyncfg_id2type(type), path,
+ dyncfg_id2source_type(source_type),
+ source,
+ buffer_tostring(c),
+ (HTTP_ACCESS_FORMAT_CAST)view_access,
+ (HTTP_ACCESS_FORMAT_CAST)edit_access
);
fflush(stdout);
diff --git a/libnetdata/functions_evloop/functions_evloop.h b/libnetdata/functions_evloop/functions_evloop.h
index 80a16e2ee1..9c52972708 100644
--- a/libnetdata/functions_evloop/functions_evloop.h
+++ b/libnetdata/functions_evloop/functions_evloop.h
@@ -6,10 +6,11 @@
#include "../libnetdata.h"
#define MAX_FUNCTION_PARAMETERS 1024
+#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
+// plugins.d 1st version of the external plugins and streaming protocol
#define PLUGINSD_KEYWORD_CHART "CHART"
#define PLUGINSD_KEYWORD_CHART_DEFINITION_END "CHART_DEFINITION_END"
-
#define PLUGINSD_KEYWORD_DIMENSION "DIMENSION"
#define PLUGINSD_KEYWORD_BEGIN "BEGIN"
#define PLUGINSD_KEYWORD_SET "SET"
@@ -21,45 +22,59 @@
#define PLUGINSD_KEYWORD_OVERWRITE "OVERWRITE"
#define PLUGINSD_KEYWORD_CLABEL "CLABEL"
#define PLUGINSD_KEYWORD_CLABEL_COMMIT "CLABEL_COMMIT"
+#define PLUGINSD_KEYWORD_EXIT "EXIT"
-#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION"
-#define PLUGINSD_KEYWORD_FUNCTION_PAYLOAD "FUNCTION_PAYLOAD"
-#define PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END "FUNCTION_PAYLOAD_END"
-#define PLUGINSD_KEYWORD_FUNCTION_CANCEL "FUNCTION_CANCEL"
-#define PLUGINSD_KEYWORD_FUNCTION_PROGRESS "FUNCTION_PROGRESS"
-#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN"
-#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END"
-
-#define PLUGINSD_KEYWORD_CONFIG "CONFIG"
-#define PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE "create"
-#define PLUGINSD_KEYWORD_CONFIG_ACTION_DELETE "delete"
-#define PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS "status"
-
-#define PLUGINSD_FUNCTION_CONFIG "config"
-
-#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
-#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
-#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
-#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
-#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
-#define PLUGINSD_KEYWORD_REPLAY_END "REND"
-
+// high-speed versions of BEGIN, SET, END
#define PLUGINSD_KEYWORD_BEGIN_V2 "BEGIN2"
#define PLUGINSD_KEYWORD_SET_V2 "SET2"
#define PLUGINSD_KEYWORD_END_V2 "END2"
+// super high-speed versions of BEGIN, SET, END have this as first parameter
+// enabled with the streaming capability STREAM_CAP_SLOTS
+#define PLUGINSD_KEYWORD_SLOT "SLOT" // to change the length of this, update pluginsd_extract_chart_slot() too
+
+// virtual hosts (only for external plugins - for streaming virtual hosts are like all other hosts)
#define PLUGINSD_KEYWORD_HOST_DEFINE "HOST_DEFINE"
#define PLUGINSD_KEYWORD_HOST_DEFINE_END "HOST_DEFINE_END"
#define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
#define PLUGINSD_KEYWORD_HOST "HOST"
-#define PLUGINSD_KEYWORD_EXIT "EXIT"
+// replication
+// enabled with STREAM_CAP_REPLICATION
+#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
+#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
+#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
+#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
+#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
+#define PLUGINSD_KEYWORD_REPLAY_END "REND"
-#define PLUGINSD_KEYWORD_SLOT "SLOT" // to change the length of this, update pluginsd_extract_chart_slot() too
+// plugins.d accepts these for functions (from external plugins or streaming children)
+// related to STREAM_CAP_FUNCTIONS, STREAM_CAP_PROGRESS
+#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION" // define a function
+#define PLUGINSD_KEYWORD_FUNCTION_PROGRESS "FUNCTION_PROGRESS" // send updates about function progress
+#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN" // the result of a function transaction
+#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END" // the end of the result of a func. trans.
+
+// plugins.d sends these for functions (to external plugins or streaming children)
+// related to STREAM_CAP_FUNCTIONS, STREAM_CAP_PROGRESS
+#define PLUGINSD_CALL_FUNCTION "FUNCTION" // call a function to a plugin or remote host
+#define PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN "FUNCTION_PAYLOAD" // call a function with a payload
+#define PLUGINSD_CALL_FUNCTION_PAYLOAD_END "FUNCTION_PAYLOAD_END" // function payload ends
+#define PLUGINSD_CALL_FUNCTION_CANCEL "FUNCTION_CANCEL" // cancel a running function transaction
+#define PLUGINSD_CALL_FUNCTION_PROGRESS "FUNCTION_PROGRESS" // let the function know the user is waiting
+
+// dyncfg
+// enabled with STREAM_CAP_DYNCFG
+#define PLUGINSD_KEYWORD_CONFIG "CONFIG"
+#define PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE "create"
+#define PLUGINSD_KEYWORD_CONFIG_ACTION_DELETE "delete"
+#define PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS "status"
+#define PLUGINSD_FUNCTION_CONFIG "config"
-#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
+typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, usec_t *stop_monotonic_ut,
+ bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
+ const char *source, void *data);
-typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, const char *source, void *data);
struct functions_evloop_worker_job;
struct functions_evloop_globals *functions_evloop_init(size_t worker_threads, const char *tag, netdata_mutex_t *stdout_mutex, bool *plugin_should_exit);
void functions_evloop_add_function(struct functions_evloop_globals *wg, const char *function, functions_evloop_worker_execute_t cb, time_t default_timeout, void *data);
@@ -123,7 +138,11 @@ static inline void pluginsd_function_progress_to_stdout(const char *transaction,
fflush(stdout);
}
-void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, dyncfg_cb_t cb, void *data);
+void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
+ DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds,
+ HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
+ dyncfg_cb_t cb, void *data);
+
void functions_evloop_dyncfg_del(struct functions_evloop_globals *wg, const char *id);
void functions_evloop_dyncfg_status(struct functions_evloop_globals *wg, const char *id, DYNCFG_STATUS status);
diff --git a/libnetdata/http/http_access.c b/libnetdata/http/http_access.c
index 32b16a0567..5be63bb199 100644
--- a/libnetdata/http/http_access.c
+++ b/libnetdata/http/http_access.c
@@ -3,41 +3,184 @@
#include "../libnetdata.h"
static struct {
- HTTP_ACCESS access;
+ HTTP_USER_ROLE access;
const char *name;
-} access_levels[] = {
- { .access = HTTP_ACCESS_NONE, .name = "none" },
- { .access = HTTP_ACCESS_MEMBER, .name = "member" },
- { .access = HTTP_ACCESS_ADMIN, .name = "admin" },
- { .access = HTTP_ACCESS_ANY, .name = "any" },
+} user_roles[] = {
+ { .access = HTTP_USER_ROLE_NONE, .name = "none" },
+ { .access = HTTP_USER_ROLE_ADMIN, .name = "admin" },
+ { .access = HTTP_USER_ROLE_MANAGER, .name = "manager" },
+ { .access = HTTP_USER_ROLE_TROUBLESHOOTER, .name = "troubleshooter" },
+ { .access = HTTP_USER_ROLE_OBSERVER, .name = "observer" },
+ { .access = HTTP_USER_ROLE_MEMBER, .name = "member" },
+ { .access = HTTP_USER_ROLE_BILLING, .name = "billing" },
+ { .access = HTTP_USER_ROLE_ANY, .name = "any" },
- { .access = HTTP_ACCESS_MEMBER, .name = "members" },
- { .access = HTTP_ACCESS_ADMIN, .name = "admins" },
- { .access = HTTP_ACCESS_ANY, .name = "all" },
+ { .access = HTTP_USER_ROLE_MEMBER, .name = "members" },
+ { .access = HTTP_USER_ROLE_ADMIN, .name = "admins" },
+ { .access = HTTP_USER_ROLE_ANY, .name = "all" },
// terminator
{ .access = 0, .name = NULL },
};
-HTTP_ACCESS http_access2id(const char *access) {
- if(!access || !*access)
- return HTTP_ACCESS_MEMBER;
+HTTP_USER_ROLE http_user_role2id(const char *role) {
+ if(!role || !*role)
+ return HTTP_USER_ROLE_MEMBER;
- for(size_t i = 0; access_levels[i].name ;i++) {
- if(strcmp(access_levels[i].name, access) == 0)
- return access_levels[i].access;
+ for(size_t i = 0; user_roles[i].name ;i++) {
+ if(strcmp(user_roles[i].name, role) == 0)
+ return user_roles[i].access;
}
- nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP access level '%s' is not valid", access);
- return HTTP_ACCESS_NONE;
+ nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP user role '%s' is not valid", role);
+ return HTTP_USER_ROLE_NONE;
}
-const char *http_id2access(HTTP_ACCESS access) {
- for(size_t i = 0; access_levels[i].name ;i++) {
- if(access == access_levels[i].access)
- return access_levels[i].name;
+const char *http_id2user_role(HTTP_USER_ROLE role) {
+ for(size_t i = 0; user_roles[i].name ;i++) {
+ if(role == user_roles[i].access)
+ return user_roles[i].name;
}
- nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP access level %d is not valid", access);
+ nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP user role %d is not valid", role);
return "none";
}
+
+// --------------------------------------------------------------------------------------------------------------------
+
+static struct {
+ const char *name;
+ uint32_t hash;
+ HTTP_ACCESS value;
+} http_accesses[] = {
+ {"none" , 0 , HTTP_ACCESS_NONE}
+ , {"signed-in" , 0 , HTTP_ACCESS_SIGNED_ID}
+ , {"same-space" , 0 , HTTP_ACCESS_SAME_SPACE}
+ , {"commercial" , 0 , HTTP_ACCESS_COMMERCIAL_SPACE}
+ , {"anonymous-data" , 0 , HTTP_ACCESS_ANONYMOUS_DATA}
+ , {"sensitive-data" , 0 , HTTP_ACCESS_SENSITIVE_DATA}
+ , {"view-config" , 0 , HTTP_ACCESS_VIEW_AGENT_CONFIG}
+ , {"edit-config" , 0 , HTTP_ACCESS_EDIT_AGENT_CONFIG}
+ , {"view-notifications-config" , 0 , HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG}
+ , {"edit-notifications-config" , 0 , HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG}
+ , {"view-alerts-silencing" , 0 , HTTP_ACCESS_VIEW_ALERTS_SILENCING}
+ , {"edit-alerts-silencing" , 0 , HTTP_ACCESS_EDIT_ALERTS_SILENCING}
+
+ , {NULL , 0 , 0}
+};
+
+inline HTTP_ACCESS http_access2id_one(const char *str) {
+ HTTP_ACCESS ret = 0;
+
+ if(!str || !*str) return ret;
+
+ uint32_t hash = simple_hash(str);
+ int i;
+ for(i = 0; http_accesses[i].name ; i++) {
+ if(unlikely(!http_accesses[i].hash))
+ http_accesses[i].hash = simple_hash(http_accesses[i].name);
+
+ if (unlikely(hash == http_accesses[i].hash && !strcmp(str, http_accesses[i].name))) {
+ ret |= http_accesses[i].value;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+inline HTTP_ACCESS http_access2id(char *str) {
+ HTTP_ACCESS ret = 0;
+ char *tok;
+
+ while(str && *str && (tok = strsep_skip_consecutive_separators(&str, ", |"))) {
+ if(!*tok) continue;
+ ret |= http_access2id_one(tok);
+ }
+
+ return ret;
+}
+
+void http_access2buffer_json_array(BUFFER *wb, const char *key, HTTP_ACCESS access) {
+ buffer_json_member_add_array(wb, key);
+
+ HTTP_ACCESS used = 0; // to prevent adding duplicates
+ for(int i = 0; http_accesses[i].name ; i++) {
+ if (unlikely((http_accesses[i].value & access) && !(http_accesses[i].value & used))) {
+ const char *name = http_accesses[i].name;
+ used |= http_accesses[i].value;
+
+ buffer_json_add_array_item_string(wb, name);
+ }
+ }
+
+ buffer_json_array_close(wb);
+}
+
+void http_access2txt(char *buf, size_t size, const char *separator, HTTP_ACCESS access) {
+ char *write = buf;
+ char *end = &buf[size - 1];
+
+ HTTP_ACCESS used = 0; // to prevent adding duplicates
+ int added = 0;
+ for(int i = 0; http_accesses[i].name ; i++) {
+ if (unlikely((http_accesses[i].value & access) && !(http_accesses[i].value & used))) {
+ const char *name = http_accesses[i].name;
+ used |= http_accesses[i].value;
+
+ if(added && write < end) {
+ const char *s = separator;
+ while(*s && write < end)
+ *write++ = *s++;
+ }
+
+ while(*name && write < end)
+ *write++ = *name++;
+
+ added++;
+ }
+ }
+ *write = *end = '\0';
+}
+
+HTTP_ACCESS http_access_from_hex_mapping_old_roles(const char *str) {
+ if(!str || !*str)
+ return HTTP_ACCESS_NONE;
+
+ if(strcmp(str, "any") == 0 || strcmp(str, "all") == 0)
+ return HTTP_ACCESS_MAP_OLD_ANY;
+
+ if(strcmp(str, "member") == 0 || strcmp(str, "members") == 0)
+ return HTTP_ACCESS_MAP_OLD_MEMBER;
+
+ else if(strcmp(str, "admin") == 0 || strcmp(str, "admins") == 0)
+ return HTTP_ACCESS_MAP_OLD_ADMIN;
+
+ return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
+}
+
+HTTP_ACCESS http_access_from_hex(const char *str) {
+ if(!str || !*str)
+ return HTTP_ACCESS_NONE;
+
+ return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
+}
+
+HTTP_ACCESS http_access_from_source(const char *str) {
+ if(!str || !*str)
+ return HTTP_ACCESS_NONE;
+
+ HTTP_ACCESS access = HTTP_ACCESS_NONE;
+
+ const char *permissions = strstr(str, "permissions=");
+ if(permissions)
+ access = (HTTP_ACCESS)strtoull(permissions + 12, NULL, 16) & HTTP_ACCESS_ALL;
+
+ return access;
+}
+
+bool log_cb_http_access_to_hex(BUFFER *wb, void *data) {
+ HTTP_ACCESS access = *((HTTP_ACCESS *)data);
+ buffer_sprintf(wb, HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)access);
+ return true;
+}
diff --git a/libnetdata/http/http_access.h b/libnetdata/http/http_access.h
index 0f055317e2..6031da7444 100644
--- a/libnetdata/http/http_access.h
+++ b/libnetdata/http/http_access.h
@@ -4,38 +4,130 @@
#define NETDATA_HTTP_ACCESS_H
typedef enum __attribute__((packed)) {
- HTTP_ACCESS_NONE = 0,
- HTTP_ACCESS_ADMIN = 1,
- HTTP_ACCESS_MEMBER = 2,
- HTTP_ACCESS_ANY = 3,
+ HTTP_USER_ROLE_NONE = 0,
+ HTTP_USER_ROLE_ADMIN = 1,
+ HTTP_USER_ROLE_MANAGER = 2,
+ HTTP_USER_ROLE_TROUBLESHOOTER = 3,
+ HTTP_USER_ROLE_OBSERVER = 4,
+ HTTP_USER_ROLE_MEMBER = 5,
+ HTTP_USER_ROLE_BILLING = 6,
+ HTTP_USER_ROLE_ANY = 7,
// keep this list so that lower numbers are more strict access levels
-} HTTP_ACCESS;
+} HTTP_USER_ROLE;
+const char *http_id2user_role(HTTP_USER_ROLE role);
+HTTP_USER_ROLE http_user_role2id(const char *role);
-const char *http_id2access(HTTP_ACCESS access);
-HTTP_ACCESS http_access2id(const char *access);
+typedef enum __attribute__((packed)) {
+ HTTP_ACCESS_NONE = 0, // adm man trb obs mem bil
+ HTTP_ACCESS_SIGNED_ID = (1 << 0), // User is authenticated A A A A A A
+ HTTP_ACCESS_SAME_SPACE = (1 << 1), // NC user+agent = same space A A A A A A
+ HTTP_ACCESS_COMMERCIAL_SPACE = (1 << 2), // NC A - - - - -
+ HTTP_ACCESS_ANONYMOUS_DATA = (1 << 3), // NC room:Read A A A SR SR -
+ HTTP_ACCESS_SENSITIVE_DATA = (1 << 4), // NC agent:ViewSensitiveData A A A - - -
+ HTTP_ACCESS_VIEW_AGENT_CONFIG = (1 << 5), // NC agent:ReadDynCfg P P - - - -
+ HTTP_ACCESS_EDIT_AGENT_CONFIG = (1 << 6), // NC agent:EditDynCfg P P - - - -
+ HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG = (1 << 7), // NC agent:ViewNotificationsConfig P - - - - -
+ HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG = (1 << 8), // NC agent:EditNotificationsConfig P - - - - -
+ HTTP_ACCESS_VIEW_ALERTS_SILENCING = (1 << 9), // NC space:GetSystemSilencingRules A A A - A -
+ HTTP_ACCESS_EDIT_ALERTS_SILENCING = (1 << 10), // NC space:CreateSystemSilencingRule P P - - P -
+} HTTP_ACCESS; // ---------------------
+ // A = always
+ // P = commercial plan
+ // SR = same room (Us+Ag)
+
+#define HTTP_ACCESS_FORMAT "0x%" PRIx32
+#define HTTP_ACCESS_FORMAT_CAST uint32_t
+
+#define HTTP_ACCESS_ALL (HTTP_ACCESS)( \
+ HTTP_ACCESS_SIGNED_ID \
+ | HTTP_ACCESS_SAME_SPACE \
+ | HTTP_ACCESS_COMMERCIAL_SPACE \
+ | HTTP_ACCESS_ANONYMOUS_DATA \
+ | HTTP_ACCESS_SENSITIVE_DATA \
+ | HTTP_ACCESS_VIEW_AGENT_CONFIG \
+ | HTTP_ACCESS_EDIT_AGENT_CONFIG \
+ | HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG \
+ | HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG \
+ | HTTP_ACCESS_VIEW_ALERTS_SILENCING \
+ | HTTP_ACCESS_EDIT_ALERTS_SILENCING \
+)
+
+#define HTTP_ACCESS_MAP_OLD_ANY (HTTP_ACCESS)(HTTP_ACCESS_ANONYMOUS_DATA)
+
+#define HTTP_ACCESS_MAP_OLD_MEMBER (HTTP_ACCESS)( \
+ HTTP_ACCESS_SIGNED_ID \
+ | HTTP_ACCESS_SAME_SPACE \
+ | HTTP_ACCESS_ANONYMOUS_DATA | HTTP_ACCESS_SENSITIVE_DATA)
+
+#define HTTP_ACCESS_MAP_OLD_ADMIN (HTTP_ACCESS)( \
+ HTTP_ACCESS_SIGNED_ID \
+ | HTTP_ACCESS_SAME_SPACE \
+ | HTTP_ACCESS_ANONYMOUS_DATA | HTTP_ACCESS_SENSITIVE_DATA | HTTP_ACCESS_VIEW_AGENT_CONFIG \
+ | HTTP_ACCESS_EDIT_AGENT_CONFIG \
+)
+
+HTTP_ACCESS http_access2id_one(const char *str);
+HTTP_ACCESS http_access2id(char *str);
+struct web_buffer;
+void http_access2buffer_json_array(struct web_buffer *wb, const char *key, HTTP_ACCESS access);
+void http_access2txt(char *buf, size_t size, const char *separator, HTTP_ACCESS access);
+HTTP_ACCESS http_access_from_hex(const char *str);
+HTTP_ACCESS http_access_from_hex_mapping_old_roles(const char *str);
+HTTP_ACCESS http_access_from_source(const char *str);
+bool log_cb_http_access_to_hex(struct web_buffer *wb, void *data);
+
+#define HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(access) ((access & HTTP_ACCESS_SIGNED_ID) ? HTTP_RESP_FORBIDDEN : HTTP_RESP_PRECOND_FAIL)
typedef enum __attribute__((packed)) {
HTTP_ACL_NONE = (0),
- HTTP_ACL_NOCHECK = (1 << 0), // Don't check anything - this should work on all channels
- HTTP_ACL_DASHBOARD = (1 << 1),
- HTTP_ACL_REGISTRY = (1 << 2),
- HTTP_ACL_BADGE = (1 << 3),
- HTTP_ACL_MGMT = (1 << 4),
- HTTP_ACL_STREAMING = (1 << 5),
- HTTP_ACL_NETDATACONF = (1 << 6),
- HTTP_ACL_SSL_OPTIONAL = (1 << 7),
- HTTP_ACL_SSL_FORCE = (1 << 8),
- HTTP_ACL_SSL_DEFAULT = (1 << 9),
- HTTP_ACL_ACLK = (1 << 10),
- HTTP_ACL_WEBRTC = (1 << 11),
- HTTP_ACL_BEARER_IF_PROTECTED = (1 << 12), // allow unprotected access if bearer is not enabled in netdata
- HTTP_ACL_BEARER_REQUIRED = (1 << 13), // allow access only if a valid bearer is used
- HTTP_ACL_BEARER_OPTIONAL = (1 << 14), // the call may or may not need a bearer - will be determined later
+
+ HTTP_ACL_NOCHECK = (1 << 0), // Don't check anything - adding this to an endpoint, disables ACL checking
+
+ // transports
+ HTTP_ACL_API = (1 << 1), // from the internal web server (TCP port)
+ HTTP_ACL_API_UDP = (1 << 2), // from the internal web server (UDP port)
+ HTTP_ACL_API_UNIX = (1 << 3), // from the in