master
c 533 lines 20.4 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "database/rrd.h"
4 #include "registry_internals.h"
5
6 #define REGISTRY_STATUS_OK "ok"
7 #define REGISTRY_STATUS_REDIRECT "redirect"
8 #define REGISTRY_STATUS_FAILED "failed"
9 #define REGISTRY_STATUS_DISABLED "disabled"
10
11 bool registry_is_valid_url(const char *url) {
12 return url && (*url == 'h' || *url == '*');
13 }
14
15 // ----------------------------------------------------------------------------
16 // REGISTRY concurrency locking
17
18 static inline void registry_lock(void) {
19 netdata_mutex_lock(&registry.lock);
20 }
21
22 static inline void registry_unlock(void) {
23 netdata_mutex_unlock(&registry.lock);
24 }
25
26 // ----------------------------------------------------------------------------
27 // COOKIES
28
29 static void registry_set_cookie(struct web_client *w, const char *guid) {
30 char rfc7231_expires[RFC7231_MAX_LENGTH];
31 rfc7231_datetime(rfc7231_expires, sizeof(rfc7231_expires), now_realtime_sec() + registry.persons_expiration);
32
33 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s\r\n", guid, rfc7231_expires);
34 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; SameSite=Strict; Expires=%s\r\n", guid, rfc7231_expires);
35 if(registry.enable_cookies_samesite_secure)
36 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; SameSite=None; Secure\r\n", guid, rfc7231_expires);
37
38 if(registry.registry_domain && *registry.registry_domain) {
39 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; Domain=%s\r\n", guid, rfc7231_expires, registry.registry_domain);
40 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; Domain=%s; SameSite=Strict\r\n", guid, rfc7231_expires, registry.registry_domain);
41 if(registry.enable_cookies_samesite_secure)
42 buffer_sprintf(w->response.header, "Set-Cookie: " NETDATA_REGISTRY_COOKIE_NAME "=%s; Expires=%s; Domain=%s; SameSite=None; Secure\r\n", guid, rfc7231_expires, registry.registry_domain);
43 }
44
45 w->response.has_cookies = true;
46 }
47
48 static inline void registry_set_person_cookie(struct web_client *w, REGISTRY_PERSON *p) {
49 registry_set_cookie(w, p->guid);
50 }
51
52
53 // ----------------------------------------------------------------------------
54 // JSON GENERATION
55
56 static inline void registry_json_header(RRDHOST *host, struct web_client *w, const char *action, const char *status) {
57 buffer_flush(w->response.data);
58 w->response.data->content_type = CT_APPLICATION_JSON;
59 buffer_json_initialize(w->response.data, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_DEFAULT);
60 buffer_json_member_add_string(w->response.data, "action", action);
61 buffer_json_member_add_string(w->response.data, "status", status);
62 buffer_json_member_add_string(w->response.data, "hostname", rrdhost_registry_hostname(host));
63 buffer_json_member_add_string(w->response.data, "machine_guid", host->machine_guid);
64 }
65
66 static inline void registry_json_footer(struct web_client *w) {
67 buffer_json_finalize(w->response.data);
68 }
69
70 static inline int registry_json_disabled(RRDHOST *host, struct web_client *w, const char *action) {
71 registry_json_header(host, w, action, REGISTRY_STATUS_DISABLED);
72
73 buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce);
74
75 registry_json_footer(w);
76 return HTTP_RESP_OK;
77 }
78
79
80 // ----------------------------------------------------------------------------
81 // CALLBACKS FOR WALKING THROUGH REGISTRY OBJECTS
82
83 // structure used be the callbacks below
84 struct registry_json_walk_person_urls_callback {
85 REGISTRY_PERSON *p;
86 REGISTRY_MACHINE *m;
87 struct web_client *w;
88 int count;
89 };
90
91 static STRING *asterisks = NULL;
92
93 // callback for rendering PERSON_URLs
94 static int registry_json_person_url_callback(REGISTRY_PERSON_URL *pu, struct registry_json_walk_person_urls_callback *c) {
95 if(unlikely(!asterisks))
96 asterisks = string_strdupz("***");
97
98 struct web_client *w = c->w;
99
100 if (pu->url == asterisks) return 0;
101
102 buffer_json_add_array_item_array(w->response.data);
103 buffer_json_add_array_item_string(w->response.data, pu->machine->guid);
104 buffer_json_add_array_item_string(w->response.data, string2str(pu->url));
105 buffer_json_add_array_item_uint64(w->response.data, pu->last_t * (uint64_t) 1000);
106 buffer_json_add_array_item_uint64(w->response.data, pu->usages);
107 buffer_json_add_array_item_string(w->response.data, string2str(pu->machine_name));
108 buffer_json_array_close(w->response.data);
109
110 return 1;
111 }
112
113 // callback for rendering MACHINE_URLs
114 static int registry_json_machine_url_callback(REGISTRY_MACHINE_URL *mu, struct registry_json_walk_person_urls_callback *c, STRING *hostname) {
115 if(unlikely(!asterisks))
116 asterisks = string_strdupz("***");
117
118 struct web_client *w = c->w;
119 REGISTRY_MACHINE *m = c->m;
120
121 if (mu->url == asterisks) return 0;
122
123 buffer_json_add_array_item_array(w->response.data);
124 buffer_json_add_array_item_string(w->response.data, m->guid);
125 buffer_json_add_array_item_string(w->response.data, string2str(mu->url));
126 buffer_json_add_array_item_uint64(w->response.data, mu->last_t * (uint64_t) 1000);
127 buffer_json_add_array_item_uint64(w->response.data, mu->usages);
128 buffer_json_add_array_item_string(w->response.data, string2str(hostname));
129 buffer_json_array_close(w->response.data);
130
131 return 1;
132 }
133
134 // ----------------------------------------------------------------------------
135
136 // structure used be the callbacks below
137 struct registry_person_url_callback_verify_machine_exists_data {
138 REGISTRY_MACHINE *m;
139 int count;
140 };
141
142 static inline int registry_person_url_callback_verify_machine_exists(REGISTRY_PERSON_URL *pu, struct registry_person_url_callback_verify_machine_exists_data *d) {
143 REGISTRY_MACHINE *m = d->m;
144
145 if(pu->machine == m)
146 d->count++;
147
148 return 0;
149 }
150
151 // ----------------------------------------------------------------------------
152 // dynamic update of the configuration
153 // The registry does not seem to be designed to support this and I cannot see any concurrency protection
154 // that could make this safe, so try to be as atomic as possible.
155
156 void registry_update_cloud_base_url() {
157 registry.cloud_base_url = cloud_config_url_get();
158 nd_setenv("NETDATA_REGISTRY_CLOUD_BASE_URL", registry.cloud_base_url, 1);
159 }
160
161 // ----------------------------------------------------------------------------
162 // public HELLO request
163
164 int registry_request_hello_json(RRDHOST *host, struct web_client *w, bool do_not_track) {
165 registry_json_header(host, w, "hello", REGISTRY_STATUS_OK);
166
167 if(!UUIDiszero(host->node_id))
168 buffer_json_member_add_uuid(w->response.data, "node_id", host->node_id.uuid);
169
170 buffer_json_member_add_object(w->response.data, "agent");
171 {
172 buffer_json_member_add_string(w->response.data, "machine_guid", localhost->machine_guid);
173
174 if(!UUIDiszero(localhost->node_id))
175 buffer_json_member_add_uuid(w->response.data, "node_id", localhost->node_id.uuid);
176
177 CLAIM_ID claim_id = rrdhost_claim_id_get(host);
178 if (claim_id_is_set(claim_id))
179 buffer_json_member_add_string(w->response.data, "claim_id", claim_id.str);
180
181 buffer_json_member_add_boolean(w->response.data, "bearer_protection", netdata_is_protected_by_bearer);
182 }
183 buffer_json_object_close(w->response.data);
184
185 CLOUD_STATUS status = cloud_status();
186 buffer_json_member_add_string(w->response.data, "cloud_status", CLOUD_STATUS_2str(status));
187 buffer_json_member_add_string(w->response.data, "cloud_base_url", registry.cloud_base_url);
188
189 buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce);
190 buffer_json_member_add_boolean(w->response.data, "anonymous_statistics", do_not_track ? false : netdata_anonymous_statistics_enabled);
191 buffer_json_member_add_boolean(w->response.data, "X-Netdata-Auth", true);
192
193 buffer_json_member_add_array(w->response.data, "nodes");
194 RRDHOST *h;
195 dfe_start_read(rrdhost_root_index, h) {
196 buffer_json_add_array_item_object(w->response.data);
197 buffer_json_member_add_string(w->response.data, "machine_guid", h->machine_guid);
198
199 if(!UUIDiszero(h->node_id))
200 buffer_json_member_add_uuid(w->response.data, "node_id", h->node_id.uuid);
201
202 buffer_json_member_add_string(w->response.data, "hostname", rrdhost_registry_hostname(h));
203 buffer_json_object_close(w->response.data);
204 }
205 dfe_done(h);
206 buffer_json_array_close(w->response.data); // nodes
207
208 registry_json_footer(w);
209 return HTTP_RESP_OK;
210 }
211
212 // ----------------------------------------------------------------------------
213 // public ACCESS request
214
215 // the main method for registering an access
216 int registry_request_access_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *name, time_t when) {
217 if(unlikely(!registry.enabled))
218 return registry_json_disabled(host, w, "access");
219
220 if(!registry_is_valid_url(url)) {
221 buffer_flush(w->response.data);
222 buffer_strcat(w->response.data, "Invalid URL given in the request");
223 return HTTP_RESP_BAD_REQUEST;
224 }
225
226 // ------------------------------------------------------------------------
227 // verify the browser supports cookies or the bearer
228
229 if(registry.verify_cookies_redirects > 0 && !person_guid[0]) {
230 registry_lock();
231 registry_request_access(REGISTRY_VERIFY_COOKIES_GUID, machine_guid, url, name, when);
232 registry_unlock();
233
234 buffer_flush(w->response.data);
235 registry_set_cookie(w, REGISTRY_VERIFY_COOKIES_GUID);
236 w->response.data->content_type = CT_APPLICATION_JSON;
237 registry_json_header(host, w, "access", REGISTRY_STATUS_REDIRECT);
238 buffer_json_member_add_string(w->response.data, "person_guid", REGISTRY_VERIFY_COOKIES_GUID);
239 buffer_json_member_add_string(w->response.data, "registry", registry.registry_to_announce);
240 registry_json_footer(w);
241 return HTTP_RESP_OK;
242 }
243
244 if(unlikely(person_guid[0] && is_dummy_person(person_guid)))
245 // it passed the check - they gave us a different person_guid
246 // empty the dummy one, so that we will generate a new person_guid
247 person_guid[0] = '\0';
248
249 // ------------------------------------------------------------------------
250
251 registry_lock();
252
253 REGISTRY_PERSON *p = registry_request_access(person_guid, machine_guid, url, name, when);
254 if(!p) {
255 registry_json_header(host, w, "access", REGISTRY_STATUS_FAILED);
256 registry_json_footer(w);
257 registry_unlock();
258 return HTTP_RESP_INTERNAL_SERVER_ERROR;
259 }
260
261 // set the cookie
262 registry_set_person_cookie(w, p);
263
264 // generate the response
265 registry_json_header(host, w, "access", REGISTRY_STATUS_OK);
266 buffer_json_member_add_string(w->response.data, "person_guid", p->guid);
267 buffer_json_member_add_array(w->response.data, "urls");
268
269 struct registry_json_walk_person_urls_callback c = { p, NULL, w, 0 };
270 for(REGISTRY_PERSON_URL *pu = p->person_urls; pu ;pu = pu->next)
271 registry_json_person_url_callback(pu, &c);
272 buffer_json_array_close(w->response.data); // urls
273
274 registry_json_footer(w);
275 registry_unlock();
276 return HTTP_RESP_OK;
277 }
278
279 // ----------------------------------------------------------------------------
280 // public DELETE request
281
282 // the main method for deleting a URL from a person
283 int registry_request_delete_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url, char *delete_url, time_t when) {
284 if(!registry.enabled)
285 return registry_json_disabled(host, w, "delete");
286
287 if(!registry_is_valid_url(url)) {
288 buffer_flush(w->response.data);
289 buffer_strcat(w->response.data, "Invalid URL given in the request");
290 return HTTP_RESP_BAD_REQUEST;
291 }
292
293 registry_lock();
294
295 REGISTRY_PERSON *p = registry_request_delete(person_guid, machine_guid, url, delete_url, when);
296 if(!p) {
297 registry_json_header(host, w, "delete", REGISTRY_STATUS_FAILED);
298 registry_json_footer(w);
299 registry_unlock();
300 return HTTP_RESP_BAD_REQUEST;
301 }
302
303 // generate the response
304 registry_json_header(host, w, "delete", REGISTRY_STATUS_OK);
305 registry_json_footer(w);
306 registry_unlock();
307 return HTTP_RESP_OK;
308 }
309
310 // ----------------------------------------------------------------------------
311 // public SEARCH request
312
313 // the main method for searching the URLs of a netdata
314 int registry_request_search_json(RRDHOST *host, struct web_client *w, char *person_guid, char *request_machine) {
315 if(!registry.enabled)
316 return registry_json_disabled(host, w, "search");
317
318 if(!person_guid || !person_guid[0]) {
319 registry_json_header(host, w, "search", REGISTRY_STATUS_FAILED);
320 registry_json_footer(w);
321 return HTTP_RESP_BAD_REQUEST;
322 }
323
324 registry_lock();
325
326 STRING *hostname = NULL;
327 REGISTRY_MACHINE *m = registry_request_machine(person_guid, request_machine, &hostname);
328 if(!m) {
329 registry_json_header(host, w, "search", REGISTRY_STATUS_FAILED);
330 registry_json_footer(w);
331 registry_unlock();
332 string_freez(hostname);
333 return HTTP_RESP_NOT_FOUND;
334 }
335
336 registry_json_header(host, w, "search", REGISTRY_STATUS_OK);
337
338 buffer_json_member_add_array(w->response.data, "urls");
339 struct registry_json_walk_person_urls_callback c = { NULL, m, w, 0 };
340
341 for(REGISTRY_MACHINE_URL *mu = m->machine_urls; mu ; mu = mu->next)
342 registry_json_machine_url_callback(mu, &c, hostname);
343
344 buffer_json_array_close(w->response.data);
345
346 registry_json_footer(w);
347 registry_unlock();
348 string_freez(hostname);
349 return HTTP_RESP_OK;
350 }
351
352 // ----------------------------------------------------------------------------
353 // SWITCH REQUEST
354
355 // the main method for switching user identity
356 int registry_request_switch_json(RRDHOST *host, struct web_client *w, char *person_guid, char *machine_guid, char *url __maybe_unused, char *new_person_guid, time_t when __maybe_unused) {
357 if(!registry.enabled)
358 return registry_json_disabled(host, w, "switch");
359
360 if(!person_guid || !person_guid[0]) {
361 buffer_flush(w->response.data);
362 buffer_strcat(w->response.data, "Who are you? Person GUID is missing");
363 return HTTP_RESP_BAD_REQUEST;
364 }
365
366 if(!registry_is_valid_url(url)) {
367 buffer_flush(w->response.data);
368 buffer_strcat(w->response.data, "Invalid URL given in the request");
369 return HTTP_RESP_BAD_REQUEST;
370 }
371
372 registry_lock();
373
374 REGISTRY_PERSON *op = registry_person_find(person_guid);
375 if(!op) {
376 registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
377 registry_json_footer(w);
378 registry_unlock();
379 return 430;
380 }
381
382 REGISTRY_PERSON *np = registry_person_find(new_person_guid);
383 if(!np) {
384 registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
385 registry_json_footer(w);
386 registry_unlock();
387 return 431;
388 }
389
390 REGISTRY_MACHINE *m = registry_machine_find(machine_guid);
391 if(!m) {
392 registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
393 registry_json_footer(w);
394 registry_unlock();
395 return 432;
396 }
397
398 struct registry_person_url_callback_verify_machine_exists_data data = { m, 0 };
399
400 // verify the old person has access to this machine
401 for(REGISTRY_PERSON_URL *pu = op->person_urls; pu ;pu = pu->next)
402 registry_person_url_callback_verify_machine_exists(pu, &data);
403
404 if(!data.count) {
405 registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
406 registry_json_footer(w);
407 registry_unlock();
408 return 433;
409 }
410
411 // verify the new person has access to this machine
412 data.count = 0;
413 for(REGISTRY_PERSON_URL *pu = np->person_urls; pu ;pu = pu->next)
414 registry_person_url_callback_verify_machine_exists(pu, &data);
415
416 if(!data.count) {
417 registry_json_header(host, w, "switch", REGISTRY_STATUS_FAILED);
418 registry_json_footer(w);
419 registry_unlock();
420 return 434;
421 }
422
423 // set the cookie of the new person
424 // the user just switched identity
425 registry_set_person_cookie(w, np);
426
427 // generate the response
428 registry_json_header(host, w, "switch", REGISTRY_STATUS_OK);
429 buffer_json_member_add_string(w->response.data, "person_guid", np->guid);
430 registry_json_footer(w);
431
432 registry_unlock();
433 return HTTP_RESP_OK;
434 }
435
436 // ----------------------------------------------------------------------------
437 // STATISTICS
438
439 void registry_statistics(void) {
440 if(!registry.enabled) return;
441
442 static RRDSET *sts = NULL, *stc = NULL, *stm = NULL;
443
444 if(unlikely(!sts)) {
445 sts = rrdset_create_localhost(
446 "netdata"
447 , "registry_sessions"
448 , NULL
449 , "registry"
450 , NULL
451 , "Netdata Registry Sessions"
452 , "sessions"
453 , "registry"
454 , "stats"
455 , 131000
456 , localhost->rrd_update_every
457 , RRDSET_TYPE_LINE
458 );
459
460 rrddim_add(sts, "sessions", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
461 }
462
463 rrddim_set(sts, "sessions", (collected_number)registry.usages_count);
464 rrdset_done(sts);
465
466 // ------------------------------------------------------------------------
467
468 if(unlikely(!stc)) {
469 stc = rrdset_create_localhost(
470 "netdata"
471 , "registry_entries"
472 , NULL
473 , "registry"
474 , NULL
475 , "Netdata Registry Entries"
476 , "entries"
477 , "registry"
478 , "stats"
479 , 131100
480 , localhost->rrd_update_every
481 , RRDSET_TYPE_LINE
482 );
483
484 rrddim_add(stc, "persons", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
485 rrddim_add(stc, "machines", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
486 rrddim_add(stc, "persons_urls", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
487 rrddim_add(stc, "machines_urls", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
488 }
489
490 rrddim_set(stc, "persons", (collected_number)registry.persons_count);
491 rrddim_set(stc, "machines", (collected_number)registry.machines_count);
492 rrddim_set(stc, "persons_urls", (collected_number)registry.persons_urls_count);
493 rrddim_set(stc, "machines_urls", (collected_number)registry.machines_urls_count);
494 rrdset_done(stc);
495
496 // ------------------------------------------------------------------------
497
498 if(unlikely(!stm)) {
499 stm = rrdset_create_localhost(
500 "netdata"
501 , "registry_mem"
502 , NULL
503 , "registry"
504 , NULL
505 , "Netdata Registry Memory"
506 , "KiB"
507 , "registry"
508 , "stats"
509 , 131300
510 , localhost->rrd_update_every
511 , RRDSET_TYPE_STACKED
512 );
513
514 rrddim_add(stm, "persons", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
515 rrddim_add(stm, "machines", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
516 rrddim_add(stm, "persons_urls", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
517 rrddim_add(stm, "machines_urls", NULL, 1, 1024, RRD_ALGORITHM_ABSOLUTE);
518 }
519
520 struct aral_statistics *p_aral_stats = aral_get_statistics(registry.persons_aral);
521 rrddim_set(stm, "persons", (collected_number)p_aral_stats->structures.allocated_bytes + (collected_number)p_aral_stats->malloc.allocated_bytes + (collected_number)p_aral_stats->mmap.allocated_bytes);
522
523 struct aral_statistics *m_aral_stats = aral_get_statistics(registry.machines_aral);
524 rrddim_set(stm, "machines", (collected_number)m_aral_stats->structures.allocated_bytes + (collected_number)m_aral_stats->malloc.allocated_bytes + (collected_number)m_aral_stats->mmap.allocated_bytes);
525
526 struct aral_statistics *pu_aral_stats = aral_get_statistics(registry.person_urls_aral);
527 rrddim_set(stm, "persons_urls", (collected_number)pu_aral_stats->structures.allocated_bytes + (collected_number)pu_aral_stats->malloc.allocated_bytes + (collected_number)pu_aral_stats->mmap.allocated_bytes);
528
529 struct aral_statistics *mu_aral_stats = aral_get_statistics(registry.machine_urls_aral);
530 rrddim_set(stm, "machines_urls", (collected_number)mu_aral_stats->structures.allocated_bytes + (collected_number)mu_aral_stats->malloc.allocated_bytes + (collected_number)mu_aral_stats->mmap.allocated_bytes);
531
532 rrdset_done(stm);
533 }