From 77076d876407abc23ee7162a7fde33866c201be7 Mon Sep 17 00:00:00 2001 From: Costa Tsaousis Date: Tue, 11 Jul 2023 02:28:06 +0300 Subject: bearer improvements (#15342) --- aclk/aclk_capas.c | 2 +- claim/claim.c | 1 + daemon/common.c | 8 +++- database/contexts/api_v2.c | 21 ++++++--- database/contexts/rrdcontext.h | 2 +- registry/registry.c | 13 ++++++ web/api/formatters/json_wrapper.c | 2 +- web/api/queries/weights.c | 4 +- web/api/web_api.c | 2 +- web/api/web_api_v2.c | 93 ++++++++++++++++++++++++++++++++++++++- web/server/web_client.c | 8 ++++ web/server/web_client.h | 1 + 12 files changed, 142 insertions(+), 15 deletions(-) diff --git a/aclk/aclk_capas.c b/aclk/aclk_capas.c index a81116faf6..e8d85a2b9f 100644 --- a/aclk/aclk_capas.c +++ b/aclk/aclk_capas.c @@ -4,7 +4,7 @@ #include "ml/ml.h" -#define HTTP_API_V2_VERSION 5 +#define HTTP_API_V2_VERSION 6 const struct capability *aclk_get_agent_capas() { diff --git a/claim/claim.c b/claim/claim.c index 6a9f75ef45..825f27bd5b 100644 --- a/claim/claim.c +++ b/claim/claim.c @@ -458,6 +458,7 @@ int api_v2_claim(struct web_client *w, char *url) { if(can_be_claimed) buffer_json_member_add_string(wb, "key_filename", netdata_random_session_id_get_filename()); + buffer_json_agents_v2(wb, NULL, now_s, false, false); buffer_json_finalize(wb); return HTTP_RESP_OK; diff --git a/daemon/common.c b/daemon/common.c index 1fa95d0abe..d441c73b67 100644 --- a/daemon/common.c +++ b/daemon/common.c @@ -182,8 +182,14 @@ CLOUD_STATUS buffer_json_cloud_status(BUFFER *wb, time_t now_s) { buffer_json_member_add_time_t(wb, "next_in", next_connect - now_s); } - if (status != CLOUD_STATUS_DISABLED && cloud_base_url()) + if (cloud_base_url()) buffer_json_member_add_string(wb, "url", cloud_base_url()); + + char *claim_id = get_agent_claimid(); + if(claim_id) { + buffer_json_member_add_string(wb, "claim_id", claim_id); + freez(claim_id); + } } buffer_json_object_close(wb); // cloud diff --git a/database/contexts/api_v2.c b/database/contexts/api_v2.c index 9c84a6fe9b..474b085047 100644 --- a/database/contexts/api_v2.c +++ b/database/contexts/api_v2.c @@ -956,17 +956,24 @@ void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings void build_info_to_json_object(BUFFER *b); -void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info) { +void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info, bool array) { if(!now_s) now_s = now_realtime_sec(); - buffer_json_member_add_array(wb, "agents"); - buffer_json_add_array_item_object(wb); + if(array) { + buffer_json_member_add_array(wb, "agents"); + buffer_json_add_array_item_object(wb); + } + else + buffer_json_member_add_object(wb, "agent"); + buffer_json_member_add_string(wb, "mg", localhost->machine_guid); buffer_json_member_add_uuid(wb, "nd", localhost->node_id); buffer_json_member_add_string(wb, "nm", rrdhost_hostname(localhost)); buffer_json_member_add_time_t(wb, "now", now_s); - buffer_json_member_add_uint64(wb, "ai", 0); + + if(array) + buffer_json_member_add_uint64(wb, "ai", 0); if(info) { buffer_json_member_add_object(wb, "application"); @@ -1022,7 +1029,9 @@ void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time buffer_json_query_timings(wb, "timings", timings); buffer_json_object_close(wb); - buffer_json_array_close(wb); + + if(array) + buffer_json_array_close(wb); } void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings) { @@ -2050,7 +2059,7 @@ int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTE version_hashes_api_v2(wb, &ctl.versions); if (mode & CONTEXTS_V2_AGENTS) - buffer_json_agents_array_v2(wb, &ctl.timings, ctl.now, mode & (CONTEXTS_V2_AGENTS_INFO)); + buffer_json_agents_v2(wb, &ctl.timings, ctl.now, mode & (CONTEXTS_V2_AGENTS_INFO), true); } buffer_json_cloud_timings(wb, "timings", &ctl.timings); diff --git a/database/contexts/rrdcontext.h b/database/contexts/rrdcontext.h index 80e5f0afe3..585226df09 100644 --- a/database/contexts/rrdcontext.h +++ b/database/contexts/rrdcontext.h @@ -647,7 +647,7 @@ typedef enum __attribute__ ((__packed__)) { int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTEXTS_V2_MODE mode); RRDCONTEXT_TO_JSON_OPTIONS rrdcontext_to_json_parse_options(char *o); -void buffer_json_agents_array_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info); +void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now_s, bool info, bool array); void buffer_json_node_add_v2(BUFFER *wb, RRDHOST *host, size_t ni, usec_t duration_ut, bool status); void buffer_json_query_timings(BUFFER *wb, const char *key, struct query_timings *timings); void buffer_json_cloud_timings(BUFFER *wb, const char *key, struct query_timings *timings); diff --git a/registry/registry.c b/registry/registry.c index 04edb95c0d..378e5f8afd 100644 --- a/registry/registry.c +++ b/registry/registry.c @@ -163,6 +163,15 @@ void registry_update_cloud_base_url() { int registry_request_hello_json(RRDHOST *host, struct web_client *w) { registry_json_header(host, w, "hello", REGISTRY_STATUS_OK); + if(host->node_id) + buffer_json_member_add_uuid(w->response.data, "node_id", host->node_id); + + char *claim_id = get_agent_claimid(); + if(claim_id) { + buffer_json_member_add_string(w->response.data, "claim_id", claim_id); + freez(claim_id); + } + buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce); buffer_json_member_add_string(w->response.data, "cloud_base_url", registry.cloud_base_url); buffer_json_member_add_boolean(w->response.data, "anonymous_statistics", netdata_anonymous_statistics_enabled); @@ -172,6 +181,10 @@ int registry_request_hello_json(RRDHOST *host, struct web_client *w) { dfe_start_read(rrdhost_root_index, h) { buffer_json_add_array_item_object(w->response.data); buffer_json_member_add_string(w->response.data, "machine_guid", h->machine_guid); + + if(h->node_id) + buffer_json_member_add_uuid(w->response.data, "node_id", h->node_id); + buffer_json_member_add_string(w->response.data, "hostname", rrdhost_registry_hostname(h)); buffer_json_object_close(w->response.data); } diff --git a/web/api/formatters/json_wrapper.c b/web/api/formatters/json_wrapper.c index f9d9dac6f0..6a66cbcca6 100644 --- a/web/api/formatters/json_wrapper.c +++ b/web/api/formatters/json_wrapper.c @@ -1570,7 +1570,7 @@ void rrdr_json_wrapper_end2(RRDR *r, BUFFER *wb) { } buffer_json_object_close(wb); // view - buffer_json_agents_array_v2(wb, &r->internal.qt->timings, 0, false); + buffer_json_agents_v2(wb, &r->internal.qt->timings, 0, false, true); buffer_json_cloud_timings(wb, "timings", &r->internal.qt->timings); buffer_json_finalize(wb); } diff --git a/web/api/queries/weights.c b/web/api/queries/weights.c index e569ce134c..8ffd8951af 100644 --- a/web/api/queries/weights.c +++ b/web/api/queries/weights.c @@ -936,7 +936,7 @@ static size_t registered_results_to_json_multinode_no_group_by( buffer_json_object_close(wb); //dictionaries - buffer_json_agents_array_v2(wb, &qwd->timings, 0, false); + buffer_json_agents_v2(wb, &qwd->timings, 0, false, true); buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); buffer_json_finalize(wb); @@ -1067,7 +1067,7 @@ static size_t registered_results_to_json_multinode_group_by( dfe_done(aw); buffer_json_array_close(wb); // result - buffer_json_agents_array_v2(wb, &qwd->timings, 0, false); + buffer_json_agents_v2(wb, &qwd->timings, 0, false, true); buffer_json_member_add_uint64(wb, "correlated_dimensions", total_dimensions); buffer_json_member_add_uint64(wb, "total_dimensions_count", examined_dimensions); buffer_json_finalize(wb); diff --git a/web/api/web_api.c b/web/api/web_api.c index ad69c0b490..4372bb8cbd 100644 --- a/web/api/web_api.c +++ b/web/api/web_api.c @@ -43,7 +43,7 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat for(int i = 0; api_commands[i].command ; i++) { if(unlikely(hash == api_commands[i].hash && !strcmp(url_path_endpoint, api_commands[i].command))) { if(unlikely(!web_client_check_acl_and_bearer(w, api_commands[i].acl))) - return web_client_permission_denied(w); + return web_client_bearer_required(w); char *query_string = (char *)buffer_tostring(w->url_query_string_decoded); diff --git a/web/api/web_api_v2.c b/web/api/web_api_v2.c index 665f234ec4..c4727f75d9 100644 --- a/web/api/web_api_v2.c +++ b/web/api/web_api_v2.c @@ -8,6 +8,24 @@ struct bearer_token { 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); +} + static void bearer_get_token(uuid_t *uuid) { static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; static bool initialized = false; @@ -34,6 +52,8 @@ static void bearer_get_token(uuid_t *uuid) { z->created_s = now_monotonic_sec(); z->expires_s = z->created_s + 86400; } + + bearer_token_cleanup(); } #define HTTP_REQUEST_AUTHORIZATION_BEARER "\r\nAuthorization: Bearer " @@ -75,7 +95,36 @@ bool api_check_bearer_token(struct web_client *w) { 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(); + if(!agent_claim_id || strcmp(claim_id, agent_claim_id) != 0) + return false; + freez(agent_claim_id); + + if(!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; @@ -86,12 +135,26 @@ int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w if(!strcmp(name, "bearer_protection")) { if(!strcmp(value, "on") || !strcmp(value, "true") || !strcmp(value, "yes")) - netdata_is_protected_by_bearer = true; + protection = true; else - netdata_is_protected_by_bearer = false; + 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); @@ -102,6 +165,32 @@ int api_v2_bearer_protection(RRDHOST *host __maybe_unused, struct web_client *w } 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; bearer_get_token(&uuid); diff --git a/web/server/web_client.c b/web/server/web_client.c index 982bbd0164..ec075ad7ff 100644 --- a/web/server/web_client.c +++ b/web/server/web_client.c @@ -18,6 +18,14 @@ inline int web_client_permission_denied(struct web_client *w) { return HTTP_RESP_FORBIDDEN; } +inline int web_client_bearer_required(struct web_client *w) { + w->response.data->content_type = CT_TEXT_PLAIN; + buffer_flush(w->response.data); + buffer_strcat(w->response.data, "An authorization bearer is required to access the resource."); + w->response.code = HTTP_RESP_UNAUTHORIZED; + return HTTP_RESP_UNAUTHORIZED; +} + static inline int bad_request_multiple_dashboard_versions(struct web_client *w) { w->response.data->content_type = CT_TEXT_PLAIN; buffer_flush(w->response.data); diff --git a/web/server/web_client.h b/web/server/web_client.h index 7a74bffe42..68fcbfa31d 100644 --- a/web/server/web_client.h +++ b/web/server/web_client.h @@ -199,6 +199,7 @@ struct web_client { }; int web_client_permission_denied(struct web_client *w); +int web_client_bearer_required(struct web_client *w); ssize_t web_client_send(struct web_client *w); ssize_t web_client_receive(struct web_client *w); -- cgit v1.2.3