From cd4377fab21e0f500bef7f06543fa848a039c1e0 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Thu, 20 Jul 2023 06:50:01 +0200 Subject: Merging upstream version 1.41.0. Signed-off-by: Daniel Baumann --- web/api/web_api_v2.c | 354 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 331 insertions(+), 23 deletions(-) (limited to 'web/api/web_api_v2.c') diff --git a/web/api/web_api_v2.c b/web/api/web_api_v2.c index 7280c0427..97647f5d6 100644 --- a/web/api/web_api_v2.c +++ b/web/api/web_api_v2.c @@ -3,7 +3,206 @@ #include "web_api_v2.h" #include "../rtc/webrtc.h" -static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_OPTIONS options) { +#define BEARER_TOKEN_EXPIRATION 86400 + +struct bearer_token { + time_t created_s; + time_t expires_s; +}; + +static void bearer_token_cleanup(void) { + static time_t attempts = 0; + + if(++attempts % 1000 != 0) + return; + + time_t now_s = now_monotonic_sec(); + + struct bearer_token *z; + dfe_start_read(netdata_authorized_bearers, z) { + if(z->expires_s < now_s) + dictionary_del(netdata_authorized_bearers, z_dfe.name); + } + dfe_done(z); + + dictionary_garbage_collect(netdata_authorized_bearers); +} + +void bearer_tokens_init(void) { + netdata_authorized_bearers = dictionary_create_advanced( + DICT_OPTION_DONT_OVERWRITE_VALUE | DICT_OPTION_FIXED_SIZE, + NULL, sizeof(struct bearer_token)); +} + +static time_t bearer_get_token(uuid_t *uuid) { + char uuid_str[UUID_STR_LEN]; + + uuid_generate_random(*uuid); + uuid_unparse_lower(*uuid, uuid_str); + + struct bearer_token t = { 0 }, *z; + z = dictionary_set(netdata_authorized_bearers, uuid_str, &t, sizeof(t)); + if(!z->created_s) { + z->created_s = now_monotonic_sec(); + z->expires_s = z->created_s + BEARER_TOKEN_EXPIRATION; + } + + bearer_token_cleanup(); + + return now_realtime_sec() + BEARER_TOKEN_EXPIRATION; +} + +#define HTTP_REQUEST_AUTHORIZATION_BEARER "\r\nAuthorization: Bearer " + +bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t dst_len) { + const char *req = buffer_tostring(w->response.data); + size_t req_len = buffer_strlen(w->response.data); + const char *bearer = strcasestr(req, HTTP_REQUEST_AUTHORIZATION_BEARER); + + if(!bearer) + return false; + + const char *token_start = bearer + sizeof(HTTP_REQUEST_AUTHORIZATION_BEARER) - 1; + + while(isspace(*token_start)) + token_start++; + + const char *token_end = token_start + UUID_STR_LEN - 1 + 2; + if (token_end > req + req_len) + return false; + + strncpyz(dst, token_start, dst_len - 1); + uuid_t uuid; + if (uuid_parse(dst, uuid) != 0) + return false; + + return true; +} + +bool api_check_bearer_token(struct web_client *w) { + if(!netdata_authorized_bearers) + return false; + + char token[UUID_STR_LEN]; + if(!extract_bearer_token_from_request(w, token, sizeof(token))) + return false; + + struct bearer_token *z = dictionary_get(netdata_authorized_bearers, token); + return z && z->expires_s > now_monotonic_sec(); +} + +static bool verify_agent_uuids(const char *machine_guid, const char *node_id, const char *claim_id) { + if(!machine_guid || !node_id || !claim_id) + return false; + + if(strcmp(machine_guid, localhost->machine_guid) != 0) + return false; + + char *agent_claim_id = get_agent_claimid(); + + bool not_verified = (!agent_claim_id || strcmp(claim_id, agent_claim_id) != 0); + freez(agent_claim_id); + + if(not_verified || !localhost->node_id) + return false; + + char buf[UUID_STR_LEN]; + uuid_unparse_lower(*localhost->node_id, buf); + + if(strcmp(node_id, buf) != 0) + return false; + + return true; +} + +int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url) { + char *machine_guid = NULL; + char *claim_id = NULL; + char *node_id = NULL; + bool protection = netdata_is_protected_by_bearer; + + while (url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if (!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if (!name || !*name) continue; + if (!value || !*value) continue; + + if(!strcmp(name, "bearer_protection")) { + if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes")) + protection = true; + else + protection = false; + } + else if(!strcmp(name, "machine_guid")) + machine_guid = value; + else if(!strcmp(name, "claim_id")) + claim_id = value; + else if(!strcmp(name, "node_id")) + node_id = value; + } + + if(!verify_agent_uuids(machine_guid, node_id, claim_id)) { + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs"); + return HTTP_RESP_BAD_REQUEST; + } + + netdata_is_protected_by_bearer = protection; + + BUFFER *wb = w->response.data; + buffer_flush(wb); + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer); + buffer_json_finalize(wb); + + return HTTP_RESP_OK; +} + +int api_v2_bearer_token(RRDHOST *host __maybe_unused, struct web_client *w __maybe_unused, char *url __maybe_unused) { + char *machine_guid = NULL; + char *claim_id = NULL; + char *node_id = NULL; + + while(url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if (!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if (!name || !*name) continue; + if (!value || !*value) continue; + + if(!strcmp(name, "machine_guid")) + machine_guid = value; + else if(!strcmp(name, "claim_id")) + claim_id = value; + else if(!strcmp(name, "node_id")) + node_id = value; + } + + if(!verify_agent_uuids(machine_guid, node_id, claim_id)) { + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "The request is missing or not matching local UUIDs"); + return HTTP_RESP_BAD_REQUEST; + } + + uuid_t uuid; + time_t expires_s = bearer_get_token(&uuid); + + BUFFER *wb = w->response.data; + buffer_flush(wb); + buffer_json_initialize(wb, "\"", "\"", 0, true, false); + buffer_json_member_add_string(wb, "mg", localhost->machine_guid); + buffer_json_member_add_boolean(wb, "bearer_protection", netdata_is_protected_by_bearer); + buffer_json_member_add_uuid(wb, "token", &uuid); + buffer_json_member_add_time_t(wb, "expiration", expires_s); + buffer_json_finalize(wb); + + return HTTP_RESP_OK; +} + +static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unused, struct web_client *w, char *url, CONTEXTS_V2_MODE mode) { struct api_v2_contexts_request req = { 0 }; while(url) { @@ -17,38 +216,133 @@ static int web_client_api_request_v2_contexts_internal(RRDHOST *host __maybe_unu // name and value are now the parameters // they are not null and not empty - if(!strcmp(name, "scope_nodes")) req.scope_nodes = value; - else if((options & (CONTEXTS_V2_NODES | CONTEXTS_V2_CONTEXTS)) && !strcmp(name, "nodes")) req.nodes = value; - else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "scope_contexts")) req.scope_contexts = value; - else if((options & CONTEXTS_V2_CONTEXTS) && !strcmp(name, "contexts")) req.contexts = value; - else if((options & CONTEXTS_V2_SEARCH) && !strcmp(name, "q")) req.q = value; - else if(!strcmp(name, "timeout")) req.timeout_ms = str2l(value); + if(!strcmp(name, "scope_nodes")) + req.scope_nodes = value; + else if(!strcmp(name, "nodes")) + req.nodes = value; + else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "scope_contexts")) + req.scope_contexts = value; + else if((mode & (CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_SEARCH | CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) && !strcmp(name, "contexts")) + req.contexts = value; + else if((mode & CONTEXTS_V2_SEARCH) && !strcmp(name, "q")) + req.q = value; + else if(!strcmp(name, "options")) + req.options = web_client_api_request_v2_context_options(value); + else if(!strcmp(name, "after")) + req.after = str2l(value); + else if(!strcmp(name, "before")) + req.before = str2l(value); + else if(!strcmp(name, "timeout")) + req.timeout_ms = str2l(value); + else if(mode & (CONTEXTS_V2_ALERTS | CONTEXTS_V2_ALERT_TRANSITIONS)) { + if (!strcmp(name, "alert")) + req.alerts.alert = value; + else if (!strcmp(name, "transition")) + req.alerts.transition = value; + else if(mode & CONTEXTS_V2_ALERTS) { + if (!strcmp(name, "status")) + req.alerts.status = web_client_api_request_v2_alert_status(value); + } + else if(mode & CONTEXTS_V2_ALERT_TRANSITIONS) { + if (!strcmp(name, "last")) + req.alerts.last = strtoul(value, NULL, 0); + else if(!strcmp(name, "context")) + req.contexts = value; + else if (!strcmp(name, "anchor_gi")) { + req.alerts.global_id_anchor = str2ull(value, NULL); + } + else { + for(int i = 0; i < ATF_TOTAL_ENTRIES ;i++) { + if(!strcmp(name, alert_transition_facets[i].query_param)) + req.alerts.facets[i] = value; + } + } + } + } } - options |= CONTEXTS_V2_DEBUG; + if ((mode & CONTEXTS_V2_ALERT_TRANSITIONS) && !req.alerts.last) + req.alerts.last = 1; buffer_flush(w->response.data); buffer_no_cacheable(w->response.data); - return rrdcontext_to_json_v2(w->response.data, &req, options); + return rrdcontext_to_json_v2(w->response.data, &req, mode); +} + +static int web_client_api_request_v2_alert_transitions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERT_TRANSITIONS | CONTEXTS_V2_NODES); +} + +static int web_client_api_request_v2_alerts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_ALERTS | CONTEXTS_V2_NODES); +} + +static int web_client_api_request_v2_functions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_FUNCTIONS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS); +} + +static int web_client_api_request_v2_versions(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_VERSIONS); } static int web_client_api_request_v2_q(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES); + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_SEARCH | CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS); } static int web_client_api_request_v2_contexts(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS); + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_CONTEXTS | CONTEXTS_V2_NODES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_VERSIONS); } static int web_client_api_request_v2_nodes(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { - return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_DETAILED); + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODES_INFO); +} + +static int web_client_api_request_v2_info(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO); +} + +static int web_client_api_request_v2_node_instances(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return web_client_api_request_v2_contexts_internal(host, w, url, CONTEXTS_V2_NODES | CONTEXTS_V2_NODE_INSTANCES | CONTEXTS_V2_AGENTS | CONTEXTS_V2_AGENTS_INFO | CONTEXTS_V2_VERSIONS); } static int web_client_api_request_v2_weights(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { - return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, - WEIGHTS_FORMAT_MULTINODE, 2); + return web_client_api_request_weights(host, w, url, WEIGHTS_METHOD_VALUE, WEIGHTS_FORMAT_MULTINODE, 2); } +static int web_client_api_request_v2_claim(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + return api_v2_claim(w, url); +} + +static int web_client_api_request_v2_alert_config(RRDHOST *host __maybe_unused, struct web_client *w, char *url) { + const char *config = NULL; + + while(url) { + char *value = strsep_skip_consecutive_separators(&url, "&"); + if(!value || !*value) continue; + + char *name = strsep_skip_consecutive_separators(&value, "="); + if(!name || !*name) continue; + if(!value || !*value) continue; + + // name and value are now the parameters + // they are not null and not empty + + if(!strcmp(name, "config")) + config = value; + } + + buffer_flush(w->response.data); + + if(!config) { + w->response.data->content_type = CT_TEXT_PLAIN; + buffer_strcat(w->response.data, "A config hash ID is required. Add ?config=UUID query param"); + return HTTP_RESP_BAD_REQUEST; + } + + return contexts_v2_alert_config_to_json(w, config); +} + + #define GROUP_BY_KEY_MAX_LENGTH 30 static struct { char group_by[GROUP_BY_KEY_MAX_LENGTH + 1]; @@ -290,14 +584,14 @@ static int web_client_api_request_v2_data(RRDHOST *host __maybe_unused, struct w if(outFileName && *outFileName) { buffer_sprintf(w->response.header, "Content-Disposition: attachment; filename=\"%s\"\r\n", outFileName); - debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName); + netdata_log_debug(D_WEB_CLIENT, "%llu: generating outfilename header: '%s'", w->id, outFileName); } if(format == DATASOURCE_DATATABLE_JSONP) { if(responseHandler == NULL) responseHandler = "google.visualization.Query.setResponse"; - debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", + netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: GOOGLE JSON/JSONP: version = '%s', reqId = '%s', sig = '%s', out = '%s', responseHandler = '%s', outFileName = '%s'", w->id, google_version, google_reqId, google_sig, google_out, responseHandler, outFileName ); @@ -346,16 +640,30 @@ static int web_client_api_request_v2_webrtc(RRDHOST *host __maybe_unused, struct } static struct web_api_command api_commands_v2[] = { - {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data}, - {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes}, - {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts}, - {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights}, - {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q}, + {"info", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info}, + + {"data", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data}, + {"weights", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights}, + + {"contexts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts}, + {"nodes", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes}, + {"node_instances", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances}, + {"versions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions}, + {"functions", 0, WEB_CLIENT_ACL_ACLK_WEBRTC_DASHBOARD_WITH_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions}, + {"q", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q}, + {"alerts", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts}, + + {"alert_transitions", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions}, + {"alert_config", 0, WEB_CLIENT_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config}, + + {"claim", 0, WEB_CLIENT_ACL_NOCHECK, web_client_api_request_v2_claim}, - {"rtc_offer", 0, WEB_CLIENT_ACL_DASHBOARD | WEB_CLIENT_ACL_ACLK, web_client_api_request_v2_webrtc}, + {"rtc_offer", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc}, + {"bearer_protection", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection}, + {"bearer_get_token", 0, WEB_CLIENT_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token}, // terminator - {NULL, 0, WEB_CLIENT_ACL_NONE, NULL}, + {NULL, 0, WEB_CLIENT_ACL_NONE, NULL}, }; inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) { -- cgit v1.2.3