diff options
author | Costa Tsaousis <costa@netdata.cloud> | 2024-01-29 09:18:01 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-29 09:18:01 +0200 |
commit | 84474006d4cf9eb78a47a3bdffbbedb3964f0068 (patch) | |
tree | 6abf1999d02a75c20ee18ce10b91b457df01fe77 /libnetdata | |
parent | 6fe7cfc0e096ce4dd1c2aa2f5ace8752bffdab44 (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.c | 4 | ||||
-rw-r--r-- | libnetdata/config/dyncfg.h | 6 | ||||
-rw-r--r-- | libnetdata/functions_evloop/functions_evloop.c | 61 | ||||
-rw-r--r-- | libnetdata/functions_evloop/functions_evloop.h | 75 | ||||
-rw-r--r-- | libnetdata/http/http_access.c | 187 | ||||
-rw-r--r-- | libnetdata/http/http_access.h | 144 | ||||
-rw-r--r-- | libnetdata/http/http_defs.h | 9 | ||||
-rw-r--r-- | libnetdata/log/log.c | 4 | ||||
-rw-r--r-- | libnetdata/log/log.h | 1 | ||||
-rw-r--r-- | libnetdata/socket/socket.c | 11 |
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 |