@cryptotaxi247 / netdata-1 / commits / 84474006d

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

Costa Tsaousis committed Jan 29, 2024 at 09:18 UTC 84474006d4cf9eb78a47a3bdffbbedb3964f0068
67 files changed +2036 -1035
CMakeLists.txt
+22
@@ -114,6 +114,27 @@ option(ENABLE_BUNDLED_PROTOBUF "enable bundled protobuf" False)
114
115 option(ENABLE_LOGS_MANAGEMENT_TESTS "enable logs management tests" True)
116
117 +option(ENABLE_WEBRTC "enable webrtc" False)
118 +
119 +if(ENABLE_WEBRTC)
120 + include(FetchContent)
121 +
122 + # ignore debhelper
123 + set(FETCHCONTENT_FULLY_DISCONNECTED Off)
124 +
125 + set(PREFER_SYSTEM_LIB True)
126 + set(NO_MEDIA True)
127 + set(NO_WEBSOCKET True)
128 +
129 + set(HAVE_LIBDATACHANNEL True)
130 +
131 + FetchContent_Declare(libdatachannel
132 + GIT_REPOSITORY https://github.com/paullouisageneau/libdatachannel.git
133 + GIT_TAG v0.20.1
134 + )
135 + FetchContent_MakeAvailable(libdatachannel)
136 +endif()
137 +
138 #
139 # handling of extra compiler flags
140 #
@@ -2010,6 +2031,7 @@ target_link_libraries(netdata PRIVATE
2031 "$<$<BOOL:${ENABLE_EXPORTER_MONGODB}>:${MONGOC_LIBRARIES}>"
2032 "$<$<BOOL:${ENABLE_EXPORTER_PROMETHEUS_REMOTE_WRITE}>:${SNAPPY_LIBRARIES}>"
2033 "$<$<BOOL:${MACOS}>:${IOKIT};${FOUNDATION}>"
2034 + "$<$<BOOL:${ENABLE_WEBRTC}>:LibDataChannel::LibDataChannelStatic>"
2035 )
2036
2037 #
aclk/aclk_query.c
+3 -4
@@ -106,10 +106,9 @@ static int http_api_v2(struct aclk_query_thread *query_thr, aclk_query_t query)
106
107 struct web_client *w = web_client_get_from_cache();
108 web_client_set_conn_cloud(w);
109 - w->acl = HTTP_ACL_ACLK;
110 - w->access = HTTP_ACCESS_MEMBER; // the minimum access level for all requests from netdata cloud
111 - web_client_flags_clear_auth(w);
112 - web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_CLOUD);
109 + w->port_acl = HTTP_ACL_ACLK | HTTP_ACL_ALL_FEATURES;
110 + w->acl = w->port_acl;
111 + web_client_set_permissions(w, HTTP_ACCESS_MAP_OLD_MEMBER, HTTP_USER_ROLE_MEMBER, WEB_CLIENT_FLAG_AUTH_CLOUD);
112
113 w->mode = HTTP_REQUEST_MODE_GET;
114 w->timings.tv_in = query->created_tv;
collectors/apps.plugin/apps_plugin.c
+19 -11
@@ -13,16 +13,18 @@
13 #define APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION "Detailed information on the currently running processes."
14
15 #define APPS_PLUGIN_FUNCTIONS() do { \
16 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" %d \"%s\" \"top\" \"members\" %d\n", \
17 - PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \
18 - RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \
19 - } while(0)
16 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \
17 + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \
18 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID|HTTP_ACCESS_SAME_SPACE|HTTP_ACCESS_SENSITIVE_DATA), \
19 + RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \
20 +} while(0)
21
22 #define APPS_PLUGIN_GLOBAL_FUNCTIONS() do { \
22 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"processes\" %d \"%s\" \"top\" \"members\" %d\n", \
23 - PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \
24 - RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \
25 - } while(0)
23 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"processes\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \
24 + PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT, APPS_PLUGIN_PROCESSES_FUNCTION_DESCRIPTION, \
25 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID|HTTP_ACCESS_SAME_SPACE|HTTP_ACCESS_SENSITIVE_DATA), \
26 + RRDFUNCTIONS_PRIORITY_DEFAULT / 10); \
27 +} while(0)
28
29 // ----------------------------------------------------------------------------
30 // debugging
@@ -4399,9 +4401,15 @@ static void apps_plugin_function_processes_help(const char *transaction) {
4401
4402 static void function_processes(const char *transaction, char *function __maybe_unused,
4403 usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled __maybe_unused,
4402 - BUFFER *payload __maybe_unused, const char *source __maybe_unused, void *data __maybe_unused) {
4404 + BUFFER *payload __maybe_unused, HTTP_ACCESS access,
4405 + const char *source __maybe_unused, void *data __maybe_unused) {
4406 struct pid_stat *p;
4407
4408 + bool show_cmdline = http_access_user_has_enough_access_level_for_endpoint(
4409 + access, HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE |
4410 + HTTP_ACCESS_SENSITIVE_DATA | HTTP_ACCESS_VIEW_AGENT_CONFIG) ||
4411 + enable_function_cmdline;
4412 +
4413 char *words[PLUGINSD_MAX_WORDS] = { NULL };
4414 size_t num_words = quoted_strings_splitter_pluginsd(function, words, PLUGINSD_MAX_WORDS);
4415
@@ -4573,7 +4581,7 @@ static void function_processes(const char *transaction, char *function __maybe_u
4581 buffer_json_add_array_item_string(wb, p->comm);
4582
4583 // cmdline
4576 - if (enable_function_cmdline) {
4584 + if (show_cmdline) {
4585 buffer_json_add_array_item_string(wb, (p->cmdline && *p->cmdline) ? p->cmdline : p->comm);
4586 }
4587
@@ -4684,7 +4692,7 @@ static void function_processes(const char *transaction, char *function __maybe_u
4692 RRDF_FIELD_FILTER_MULTISELECT,
4693 RRDF_FIELD_OPTS_VISIBLE | RRDF_FIELD_OPTS_STICKY, NULL);
4694
4687 - if (enable_function_cmdline) {
4695 + if (show_cmdline) {
4696 buffer_rrdf_table_add_field(wb, field_id++, "CmdLine", "Command Line", RRDF_FIELD_TYPE_STRING,
4697 RRDF_FIELD_VISUAL_VALUE, RRDF_FIELD_TRANSFORM_NONE, 0,
4698 NULL, NAN, RRDF_FIELD_SORT_ASCENDING, NULL, RRDF_FIELD_SUMMARY_COUNT,
collectors/cgroups.plugin/sys_fs_cgroup.c
+4 -2
@@ -1677,11 +1677,13 @@ void *cgroups_main(void *ptr) {
1677
1678 rrd_function_add_inline(localhost, NULL, "containers-vms", 10,
1679 RRDFUNCTIONS_PRIORITY_DEFAULT / 2, RRDFUNCTIONS_CGTOP_HELP,
1680 - "top", HTTP_ACCESS_ANY, cgroup_function_cgroup_top);
1680 + "top", HTTP_ACCESS_ANONYMOUS_DATA,
1681 + cgroup_function_cgroup_top);
1682
1683 rrd_function_add_inline(localhost, NULL, "systemd-services", 10,
1684 RRDFUNCTIONS_PRIORITY_DEFAULT / 3, RRDFUNCTIONS_SYSTEMD_SERVICES_HELP,
1684 - "top", HTTP_ACCESS_ANY, cgroup_function_systemd_top);
1685 + "top", HTTP_ACCESS_ANONYMOUS_DATA,
1686 + cgroup_function_systemd_top);
1687
1688 heartbeat_t hb;
1689 heartbeat_init(&hb);
collectors/diskspace.plugin/plugin_diskspace.c
+2 -1
@@ -852,7 +852,8 @@ void *diskspace_main(void *ptr) {
852
853 rrd_function_add_inline(localhost, NULL, "mount-points", 10,
854 RRDFUNCTIONS_PRIORITY_DEFAULT, RRDFUNCTIONS_DISKSPACE_HELP,
855 - "top", HTTP_ACCESS_ANY, diskspace_function_mount_points);
855 + "top", HTTP_ACCESS_ANONYMOUS_DATA,
856 + diskspace_function_mount_points);
857
858 netdata_thread_cleanup_push(diskspace_main_cleanup, ptr);
859
collectors/ebpf.plugin/ebpf_functions.c
+1
@@ -279,6 +279,7 @@ static void ebpf_function_socket_manipulation(const char *transaction,
279 usec_t *stop_monotonic_ut __maybe_unused,
280 bool *cancelled __maybe_unused,
281 BUFFER *payload __maybe_unused,
282 + HTTP_ACCESS access __maybe_unused,
283 const char *source __maybe_unused,
284 void *data __maybe_unused)
285 {
collectors/ebpf.plugin/ebpf_functions.h
+4 -2
@@ -5,8 +5,10 @@
5
6 // Common
7 static inline void EBPF_PLUGIN_FUNCTIONS(const char *NAME, const char *DESC, int update_every) {
8 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" \"members\" %d\n",
9 - NAME, update_every, DESC, RRDFUNCTIONS_PRIORITY_DEFAULT);
8 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
9 + NAME, update_every, DESC,
10 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
11 + RRDFUNCTIONS_PRIORITY_DEFAULT);
12 }
13
14 // configuration file & description
collectors/freeipmi.plugin/freeipmi_plugin.c
+5 -3
@@ -23,8 +23,9 @@
23 #include "libnetdata/required_dummies.h"
24
25 #define FREEIPMI_GLOBAL_FUNCTION_SENSORS() do { \
26 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"ipmi-sensors\" %d \"%s\" \"top\" \"any\" %d\n", \
27 - 5, "Displays current sensor state and readings", 100); \
26 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"ipmi-sensors\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \
27 + 5, "Displays current sensor state and readings", \
28 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_NONE), 100); \
29 } while(0)
30
31 // component names, based on our patterns
@@ -1472,7 +1473,8 @@ static const char *get_sensor_function_priority(struct sensor *sn) {
1473
1474 static void freeimi_function_sensors(const char *transaction, char *function __maybe_unused,
1475 usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled __maybe_unused,
1475 - BUFFER *payload __maybe_unused, const char *source __maybe_unused, void *data __maybe_unused) {
1476 + BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
1477 + const char *source __maybe_unused, void *data __maybe_unused) {
1478 time_t expires = now_realtime_sec() + update_every;
1479
1480 BUFFER *wb = buffer_create(4096, NULL);
collectors/plugins.d/README.md
+35 -17
@@ -478,7 +478,7 @@ The plugin can register functions to Netdata, like this:
478 - `member` to offer the function to all authenticated members of Netdata.
479 - `admin` to offer the function only to authenticated administrators.
480
481 -A function can be used by users to ask for more information from the collector. Netdata maintains a registry of functions in 2 levels:
481 +Users can use a function to ask for more information from the collector. Netdata maintains a registry of functions in 2 levels:
482
483 - per node
484 - per chart
@@ -489,13 +489,17 @@ Users can get a list of all the registered functions using the `/api/v1/function
489
490 Once a function is called, the plugin will receive at its standard input a command that looks like this:
491
492 -> FUNCTION transaction_id timeout "name and parameters of the function as one quoted parameter" "source of request"
492 +```
493 +FUNCTION transaction_id timeout "name and parameters of the function as one quoted parameter" "user permissions value" "source of request"
494 +```
495
496 When the function to be called is to receive a payload of parameters, the call looks like this:
497
496 -> FUNCTION_PAYLOAD transaction_id timeout "name and parameters of the function as one quoted parameter" "source of request" "content/type"
497 -> body of the payload, formatted according to content/type
498 -> FUNCTION PAYLOAD END
498 +```
499 +FUNCTION_PAYLOAD transaction_id timeout "name and parameters of the function as one quoted parameter" "user permissions value" "source of request" "content/type"
500 +body of the payload, formatted according to content/type
501 +FUNCTION PAYLOAD END
502 +```
503
504 In this case, Netdata will send:
505
@@ -522,7 +526,9 @@ FUNCTION_RESULT_END
526
527 If the plugin prepares a response, it should send (via its standard output, together with the collected data, but not interleaved with them):
528
525 -> FUNCTION_RESULT_BEGIN transaction_id http_response_code content_type expiration
529 +```
530 +FUNCTION_RESULT_BEGIN transaction_id http_response_code content_type expiration
531 +```
532
533 Where:
534
@@ -537,7 +543,9 @@ The type of the context itself depends on the plugin and the UI.
543
544 To terminate the message, Netdata seeks a line with just this:
545
540 -> FUNCTION_RESULT_END
546 +```
547 +FUNCTION_RESULT_END
548 +```
549
550 This defines the end of the message. `FUNCTION_RESULT_END` should appear in a line alone, without any other text, so it is wise to add `\n` before and after it.
551
@@ -557,7 +565,9 @@ When a request takes too long to be processed, Netdata allows the plugin to repo
565
566 The plugin can send `FUNCTION_PROGRESS` like this:
567
560 -> FUNCTION_PROGRESS transaction_id done all
568 +```
569 +FUNCTION_PROGRESS transaction_id done all
570 +```
571
572 Where:
573
@@ -583,7 +593,9 @@ To accomplish this, when Netdata receives a progress request by a user, it gener
593
594 The plugin will receive progress requests like this:
595
586 -> FUNCTION_PROGRESS transaction_id
596 +```
597 +FUNCTION_PROGRESS transaction_id
598 +```
599
600 There is no need to respond to this command. It is only there to let the plugin know that a user is still waiting for the query to finish.
601
@@ -595,7 +607,9 @@ Dynamically configurations made this way are saved to disk by Netdata and are re
607
608 `CONFIG` commands look like this:
609
598 -> CONFIG id action ...
610 +```
611 +CONFIG id action ...
612 +```
613
614 Where:
615
@@ -606,12 +620,13 @@ Where:
620 - `status`, to update the dynamic configuration entity status
621
622 > IMPORTANT:<br/>
609 -> The plugin should blindly create, delete and update the status of its dynamic configuration entities, without any special logic applied to it. Netdata needs to be updated of what is actually happening at the plugin. Keep in mind that creating dynamic configuration entities triggers responses from Netdata, depending on its type and status. Re-creating a job, triggers the same responses every time.
610 -
623 +> The plugin should blindly create, delete and update the status of its dynamic configuration entities, without any special logic applied to it. Netdata needs to be updated of what is actually happening at the plugin. Keep in mind that creating dynamic configuration entities triggers responses from Netdata, depending on its type and status. Re-creating a job, triggers the same responses every time, so make sure you create jobs only when you add jobs.
624
625 When the `action` is `create`, the following additional parameters are expected:
626
614 -> CONFIG id action status type "path" source_type "source" "supported commands"
627 +```
628 +CONFIG id action status type "path" source_type "source" "supported commands" "view permissions" "edit permissions"
629 +```
630
631 Where:
632
@@ -644,23 +659,26 @@ Where:
659 - `remove`, to remove a configuration. Only `jobs` should support this command.
660 - `enable` and `disable`, to receive user requests to enable and disable this entity. Adding only one of `enable` or `disable` to the supported commands, Netdata will add both of them. The plugin should expose these commands on `templates` only when it wants to receive `enable` and `disable` commands for all the `jobs` of this `template`.
661 - `restart`, to restart a job.
662 +- `view permissions` and `edit permissions` are bitmaps of the Netdata permission system to control access to the configuration. If set to zero, Netdata will require a signed in user with view and edit permissions to the Netdata's configuration system.
663
664 The plugin receives commands as if it had exposed a `FUNCTION` named `config`. Netdata formats all these calls like this:
665
650 -> config id command
666 +```
667 +config id command
668 +```
669
670 Where `id` is the unique id of the configurable entity and `command` is one of the supported commands the plugin sent to Netdata.
671
654 -The plugin will receive (for commands: `schema`, `get`, `remove`, `enable` and `disable`):
672 +The plugin will receive (for commands: `schema`, `get`, `remove`, `enable`, `disable` and `restart`):
673
674 ```
657 -FUNCTION transaction_id timeout "config id command"
675 +FUNCTION transaction_id timeout "config id command" "user permissions value" "source string"
676 ```
677
678 or (for commands: `update`, `add` and `test`):
679
680 ```
663 -FUNCTION_PAYLOAD transaction_id timeout "config id command" "content/type"
681 +FUNCTION_PAYLOAD transaction_id timeout "config id command" "user permissions value" "source string" "content/type"
682 body of the payload formatted according to content/type
683 FUNCTION_PAYLOAD_END
684 ```
collectors/plugins.d/pluginsd_dyncfg.c
+12 -6
@@ -14,17 +14,21 @@ PARSER_RC pluginsd_config(char **words, size_t num_words, PARSER *parser) {
14 char *action = get_word(words, num_words, i++);
15
16 if(strcmp(action, PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE) == 0) {
17 - char *status_str = get_word(words, num_words, i++);
18 - char *type_str = get_word(words, num_words, i++);
19 - char *path = get_word(words, num_words, i++);
20 - char *source_type_str = get_word(words, num_words, i++);
21 - char *source = get_word(words, num_words, i++);
22 - char *supported_cmds_str = get_word(words, num_words, i++);
17 + char *status_str = get_word(words, num_words, i++);
18 + char *type_str = get_word(words, num_words, i++);
19 + char *path = get_word(words, num_words, i++);
20 + char *source_type_str = get_word(words, num_words, i++);
21 + char *source = get_word(words, num_words, i++);
22 + char *supported_cmds_str = get_word(words, num_words, i++);
23 + char *view_permissions_str = get_word(words, num_words, i++);
24 + char *edit_permissions_str = get_word(words, num_words, i++);
25
26 DYNCFG_STATUS status = dyncfg_status2id(status_str);
27 DYNCFG_TYPE type = dyncfg_type2id(type_str);
28 DYNCFG_SOURCE_TYPE source_type = dyncfg_source_type2id(source_type_str);
29 DYNCFG_CMDS cmds = dyncfg_cmds2id(supported_cmds_str);
30 + HTTP_ACCESS view_access = http_access_from_hex(view_permissions_str);
31 + HTTP_ACCESS edit_access = http_access_from_hex(edit_permissions_str);
32
33 if(!dyncfg_add_low_level(
34 host,
@@ -38,6 +42,8 @@ PARSER_RC pluginsd_config(char **words, size_t num_words, PARSER *parser) {
42 0,
43 0,
44 false,
45 + view_access,
46 + edit_access,
47 pluginsd_function_execute_cb,
48 parser))
49 return PARSER_RC_ERROR;
collectors/plugins.d/pluginsd_functions.c
+8 -5
@@ -25,10 +25,11 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
25 if(pf->payload && buffer_strlen(pf->payload)) {
26 buffer_sprintf(
27 buffer,
28 - PLUGINSD_KEYWORD_FUNCTION_PAYLOAD " %s %d \"%s\" \"%s\" \"%s\"\n",
28 + PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN " %s %d \"%s\" \""HTTP_ACCESS_FORMAT"\" \"%s\" \"%s\"\n",
29 transaction,
30 pf->timeout_s,
31 string2str(pf->function),
32 + (HTTP_ACCESS_FORMAT_CAST)pf->access,
33 pf->source ? pf->source : "",
34 content_type_id2string(pf->payload->content_type)
35 );
@@ -39,10 +40,11 @@ static void inflight_functions_insert_callback(const DICTIONARY_ITEM *item, void
40 else {
41 buffer_sprintf(
42 buffer,
42 - PLUGINSD_KEYWORD_FUNCTION " %s %d \"%s\" \"%s\"\n",
43 + PLUGINSD_CALL_FUNCTION " %s %d \"%s\" \""HTTP_ACCESS_FORMAT"\" \"%s\"\n",
44 transaction,
45 pf->timeout_s,
46 string2str(pf->function),
47 + (HTTP_ACCESS_FORMAT_CAST)pf->access,
48 pf->source ? pf->source : ""
49 );
50 }
@@ -147,7 +149,7 @@ static void pluginsd_function_cancel(void *data) {
149 internal_error(true, "PLUGINSD: sending function cancellation to plugin for transaction '%s'", transaction);
150
151 char buffer[2048];
150 - snprintfz(buffer, sizeof(buffer), PLUGINSD_KEYWORD_FUNCTION_CANCEL " %s\n", transaction);
152 + snprintfz(buffer, sizeof(buffer), PLUGINSD_CALL_FUNCTION_CANCEL " %s\n", transaction);
153
154 // send the command to the plugin
155 ssize_t ret = send_to_plugin(buffer, t->parser);
@@ -175,7 +177,7 @@ static void pluginsd_function_progress_to_plugin(void *data) {
177 internal_error(true, "PLUGINSD: sending function progress to plugin for transaction '%s'", transaction);
178
179 char buffer[2048];
178 - snprintfz(buffer, sizeof(buffer), PLUGINSD_KEYWORD_FUNCTION_PROGRESS " %s\n", transaction);
180 + snprintfz(buffer, sizeof(buffer), PLUGINSD_CALL_FUNCTION_PROGRESS " %s\n", transaction);
181
182 // send the command to the plugin
183 ssize_t ret = send_to_plugin(buffer, t->parser);
@@ -211,6 +213,7 @@ int pluginsd_function_execute_cb(struct rrd_function_execute *rfe, void *data) {
213 .timeout_s = timeout_s,
214 .function = string_strdupz(rfe->function),
215 .payload = buffer_dup(rfe->payload),
216 + .access = rfe->user_access,
217 .source = rfe->source ? strdupz(rfe->source) : NULL,
218 .parser = parser,
219
@@ -313,7 +316,7 @@ PARSER_RC pluginsd_function(char **words, size_t num_words, PARSER *parser) {
316 }
317
318 rrd_function_add(host, st, name, timeout_s, priority, help, tags,
316 - http_access2id(access_str), false,
319 + http_access_from_hex_mapping_old_roles(access_str), false,
320 pluginsd_function_execute_cb, parser);
321
322 parser->user.data_collections_count++;
collectors/plugins.d/pluginsd_functions.h
+1
@@ -12,6 +12,7 @@ struct inflight_function {
12 int timeout_s;
13 STRING *function;
14 BUFFER *payload;
15 + HTTP_ACCESS access;
16 const char *source;
17
18 BUFFER *result_body_wb;
collectors/proc.plugin/proc_diskstats.c
+2 -1
@@ -1462,7 +1462,8 @@ int do_proc_diskstats(int update_every, usec_t dt) {
1462
1463 rrd_function_add_inline(localhost, NULL, "block-devices", 10,
1464 RRDFUNCTIONS_PRIORITY_DEFAULT, RRDFUNCTIONS_DISKSTATS_HELP,
1465 - "top", HTTP_ACCESS_ANY, diskstats_function_block_devices);
1465 + "top", HTTP_ACCESS_ANONYMOUS_DATA,
1466 + diskstats_function_block_devices);
1467 }
1468
1469 // --------------------------------------------------------------------------
collectors/proc.plugin/proc_net_dev.c
+2 -1
@@ -1756,7 +1756,8 @@ void *netdev_main(void *ptr)
1756
1757 rrd_function_add_inline(localhost, NULL, "network-interfaces", 10,
1758 RRDFUNCTIONS_PRIORITY_DEFAULT, RRDFUNCTIONS_NETDEV_HELP,
1759 - "top", HTTP_ACCESS_ANY, netdev_function_net_interfaces);
1759 + "top", HTTP_ACCESS_ANONYMOUS_DATA,
1760 + netdev_function_net_interfaces);
1761
1762 netdata_thread_cleanup_push(netdev_main_cleanup, ptr) {
1763 usec_t step = localhost->rrd_update_every * USEC_PER_SEC;
collectors/systemd-journal.plugin/systemd-internals.h
+2 -2
@@ -119,7 +119,7 @@ struct journal_directory {
119 extern struct journal_directory journal_directories[MAX_JOURNAL_DIRECTORIES];
120
121 void journal_init_files_and_directories(void);
122 -void function_systemd_journal(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, const char *source, void *data);
122 +void function_systemd_journal(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, HTTP_ACCESS access __maybe_unused, const char *source, void *data);
123 void journal_file_update_header(const char *filename, struct journal_file *jf);
124
125 void netdata_systemd_journal_message_ids_init(void);
@@ -129,7 +129,7 @@ void *journal_watcher_main(void *arg);
129 void journal_watcher_restart(void);
130
131 #ifdef ENABLE_SYSTEMD_DBUS
132 -void function_systemd_units(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, const char *source, void *data);
132 +void function_systemd_units(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *payload, HTTP_ACCESS access __maybe_unused, const char *source, void *data);
133 #endif
134
135 static inline void send_newline_and_flush(void) {
collectors/systemd-journal.plugin/systemd-journal-dyncfg.c
+3
@@ -64,6 +64,7 @@ static int systemd_journal_directories_dyncfg_cb(const char *transaction,
64 usec_t *stop_monotonic_ut __maybe_unused,
65 bool *cancelled __maybe_unused,
66 BUFFER *result,
67 + HTTP_ACCESS access __maybe_unused,
68 const char *source __maybe_unused,
69 void *data __maybe_unused) {
70 CLEAN_BUFFER *action = buffer_create(100, NULL);
@@ -94,6 +95,8 @@ void systemd_journal_dyncfg_init(struct functions_evloop_globals *wg) {
95 DYNCFG_SOURCE_TYPE_INTERNAL,
96 "internal",
97 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE,
98 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
99 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
100 systemd_journal_directories_dyncfg_cb,
101 NULL);
102 }
collectors/systemd-journal.plugin/systemd-journal.c
+2 -1
@@ -1521,7 +1521,8 @@ static void netdata_systemd_journal_function_help(const char *transaction) {
1521 }
1522
1523 void function_systemd_journal(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
1524 - BUFFER *payload __maybe_unused, const char *source __maybe_unused, void *data __maybe_unused) {
1524 + BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
1525 + const char *source __maybe_unused, void *data __maybe_unused) {
1526 fstat_thread_calls = 0;
1527 fstat_thread_cached_responses = 0;
1528
collectors/systemd-journal.plugin/systemd-main.c
+8 -4
@@ -49,7 +49,8 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
49 char buf[] = "systemd-journal after:-8640000 before:0 direction:backward last:200 data_only:false slice:true source:all";
50 // char buf[] = "systemd-journal after:1695332964 before:1695937764 direction:backward last:100 slice:true source:all DHKucpqUoe1:PtVoyIuX.MU";
51 // char buf[] = "systemd-journal after:1694511062 before:1694514662 anchor:1694514122024403";
52 - function_systemd_journal("123", buf, &stop_monotonic_ut, &cancelled, NULL, NULL, NULL);
52 + function_systemd_journal("123", buf, &stop_monotonic_ut, &cancelled,
53 + NULL, HTTP_ACCESS_ALL, NULL, NULL);
54 // function_systemd_units("123", "systemd-units", 600, &cancelled);
55 exit(1);
56 }
@@ -57,7 +58,8 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
58 if(argc == 2 && strcmp(argv[1], "debug-units") == 0) {
59 bool cancelled = false;
60 usec_t stop_monotonic_ut = now_monotonic_usec() + 600 * USEC_PER_SEC;
60 - function_systemd_units("123", "systemd-units", &stop_monotonic_ut, &cancelled, NULL, NULL, NULL);
61 + function_systemd_units("123", "systemd-units", &stop_monotonic_ut, &cancelled,
62 + NULL, HTTP_ACCESS_ALL, NULL, NULL);
63 exit(1);
64 }
65 #endif
@@ -96,13 +98,15 @@ int main(int argc __maybe_unused, char **argv __maybe_unused) {
98
99 netdata_mutex_lock(&stdout_mutex);
100
99 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" \"members\" %d\n",
101 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" "HTTP_ACCESS_FORMAT" %d\n",
102 SYSTEMD_JOURNAL_FUNCTION_NAME, SYSTEMD_JOURNAL_DEFAULT_TIMEOUT, SYSTEMD_JOURNAL_FUNCTION_DESCRIPTION,
103 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
104 RRDFUNCTIONS_PRIORITY_DEFAULT);
105
106 #ifdef ENABLE_SYSTEMD_DBUS
104 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" \"members\" %d\n",
107 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n",
108 SYSTEMD_UNITS_FUNCTION_NAME, SYSTEMD_UNITS_DEFAULT_TIMEOUT, SYSTEMD_UNITS_FUNCTION_DESCRIPTION,
109 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
110 RRDFUNCTIONS_PRIORITY_DEFAULT);
111 #endif
112
collectors/systemd-journal.plugin/systemd-units.c
+2 -1
@@ -1598,7 +1598,8 @@ void systemd_units_assign_priority(UnitInfo *base) {
1598
1599 void function_systemd_units(const char *transaction, char *function,
1600 usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled __maybe_unused,
1601 - BUFFER *payload __maybe_unused, const char *source __maybe_unused, void *data __maybe_unused) {
1601 + BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
1602 + const char *source __maybe_unused, void *data __maybe_unused) {
1603 char *words[SYSTEMD_UNITS_MAX_PARAMS] = { NULL };
1604 size_t num_words = quoted_strings_splitter_pluginsd(function, words, SYSTEMD_UNITS_MAX_PARAMS);
1605 for(int i = 1; i < SYSTEMD_UNITS_MAX_PARAMS ;i++) {
config.cmake.h.in
+1
@@ -33,6 +33,7 @@
33 #cmakedefine HAVE_PROTOBUF
34 #cmakedefine BUNDLED_PROTOBUF
35 #cmakedefine HAVE_MONGOC
36 +#cmakedefine HAVE_LIBDATACHANNEL
37
38 // checked symbols
39
daemon/config/dyncfg-echo.c
+53 -27
@@ -12,18 +12,42 @@
12
13 struct dyncfg_echo {
14 const DICTIONARY_ITEM *item;
15 - DYNCFG *df;
15 + DYNCFG *df; // for additions this is the job, not the template
16 BUFFER *wb;
17 - const char *cmd;
17 + DYNCFG_CMDS cmd;
18 + const char *cmd_str;
19 };
20
21 void dyncfg_echo_cb(BUFFER *wb __maybe_unused, int code __maybe_unused, void *result_cb_data) {
22 struct dyncfg_echo *e = result_cb_data;
23 + DYNCFG *df = e->df;
24 +
25 + if(DYNCFG_RESP_SUCCESS(code)) {
26 + // successful response
27 +
28 + if(e->cmd == DYNCFG_CMD_ADD) {
29 + df->dyncfg.status = dyncfg_status_from_successful_response(code);
30 + dyncfg_update_status_on_successful_add_or_update(df, code);
31 + }
32 + else if(e->cmd == DYNCFG_CMD_UPDATE) {
33 + df->dyncfg.status = dyncfg_status_from_successful_response(code);
34 + dyncfg_update_status_on_successful_add_or_update(df, code);
35 + }
36 + else if(e->cmd == DYNCFG_CMD_DISABLE)
37 + df->dyncfg.status = df->current.status = DYNCFG_STATUS_DISABLED;
38 + else if(e->cmd == DYNCFG_CMD_ENABLE)
39 + df->dyncfg.status = df->current.status = dyncfg_status_from_successful_response(code);
40 + }
41 + else {
42 + // failed response
43
23 - if(!DYNCFG_RESP_SUCCESS(code))
44 nd_log(NDLS_DAEMON, NDLP_ERR,
45 "DYNCFG: received response code %d on request to id '%s', cmd: %s",
26 - code, dictionary_acquired_item_name(e->item), e->cmd);
46 + code, dictionary_acquired_item_name(e->item), e->cmd_str);
47 +
48 + if(e->cmd == DYNCFG_CMD_UPDATE || e->cmd == DYNCFG_CMD_ADD)
49 + e->df->dyncfg.plugin_rejected = true;
50 + }
51
52 buffer_free(e->wb);
53 dictionary_acquired_item_release(dyncfg_globals.nodes, e->item);
@@ -31,8 +55,8 @@ void dyncfg_echo_cb(BUFFER *wb __maybe_unused, int code __maybe_unused, void *re
55 e->wb = NULL;
56 e->df = NULL;
57 e->item = NULL;
34 - freez((void *)e->cmd);
35 - e->cmd = NULL;
58 + freez((void *)e->cmd_str);
59 + e->cmd_str = NULL;
60 freez(e);
61 }
62
@@ -60,30 +84,32 @@ void dyncfg_echo(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id __maybe
84 e->item = dictionary_acquired_item_dup(dyncfg_globals.nodes, item);
85 e->wb = buffer_create(0, NULL);
86 e->df = df;
63 - e->cmd = strdupz(cmd_str);
87 + e->cmd = cmd;
88 + e->cmd_str = strdupz(cmd_str);
89
65 - char buf[string_strlen(df->function) + strlen(cmd_str) + 20];
66 - snprintfz(buf, sizeof(buf), "%s %s", string2str(df->function), cmd_str);
90 + char buf[string_strlen(df->function) + strlen(e->cmd_str) + 20];
91 + snprintfz(buf, sizeof(buf), "%s %s", string2str(df->function), e->cmd_str);
92
93 rrd_function_run(
69 - host, e->wb, 10, HTTP_ACCESS_ADMIN, buf, false, NULL,
94 + host, e->wb, 10,
95 + HTTP_ACCESS_ALL, buf, false, NULL,
96 dyncfg_echo_cb, e,
97 NULL, NULL,
98 NULL, NULL,
73 - NULL, string2str(df->source));
99 + NULL, string2str(df->dyncfg.source));
100 }
101
102 // ----------------------------------------------------------------------------
103
78 -static void dyncfg_echo_payload(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id, const char *cmd) {
104 +void dyncfg_echo_update(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id) {
105 RRDHOST *host = dyncfg_rrdhost(df);
106 if(!host) {
107 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot find host of configuration id '%s'", id);
108 return;
109 }
110
85 - if(!df->payload) {
86 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: requested to send a '%s' to '%s', but there is no payload", cmd, id);
111 + if(!df->dyncfg.payload) {
112 + nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: requested to send an update to '%s', but there is no payload", id);
113 return;
114 }
115
@@ -91,21 +117,19 @@ static void dyncfg_echo_payload(const DICTIONARY_ITEM *item, DYNCFG *df, const c
117 e->item = dictionary_acquired_item_dup(dyncfg_globals.nodes, item);
118 e->wb = buffer_create(0, NULL);
119 e->df = df;
94 - e->cmd = strdupz(cmd);
120 + e->cmd = DYNCFG_CMD_UPDATE;
121 + e->cmd_str = strdupz("update");
122
96 - char buf[string_strlen(df->function) + strlen(cmd) + 20];
97 - snprintfz(buf, sizeof(buf), "%s %s", string2str(df->function), cmd);
123 + char buf[string_strlen(df->function) + strlen(e->cmd_str) + 20];
124 + snprintfz(buf, sizeof(buf), "%s %s", string2str(df->function), e->cmd_str);
125
126 rrd_function_run(
100 - host, e->wb, 10, HTTP_ACCESS_ADMIN, buf, false, NULL,
127 + host, e->wb, 10,
128 + HTTP_ACCESS_ALL, buf, false, NULL,
129 dyncfg_echo_cb, e,
130 NULL, NULL,
131 NULL, NULL,
104 - df->payload, string2str(df->source));
105 -}
106 -
107 -void dyncfg_echo_update(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id) {
108 - dyncfg_echo_payload(item, df, id, "update");
132 + df->dyncfg.payload, string2str(df->dyncfg.source));
133 }
134
135 // ----------------------------------------------------------------------------
@@ -117,7 +141,7 @@ static void dyncfg_echo_payload_add(const DICTIONARY_ITEM *item_template __maybe
141 return;
142 }
143
120 - if(!df_job->payload) {
144 + if(!df_job->dyncfg.payload) {
145 nd_log(NDLS_DAEMON, NDLP_ERR,
146 "DYNCFG: requested to send a '%s' to '%s', but there is no payload",
147 cmd, id_template);
@@ -128,17 +152,19 @@ static void dyncfg_echo_payload_add(const DICTIONARY_ITEM *item_template __maybe
152 e->item = dictionary_acquired_item_dup(dyncfg_globals.nodes, item_job);
153 e->wb = buffer_create(0, NULL);
154 e->df = df_job;
131 - e->cmd = strdupz(cmd);
155 + e->cmd = DYNCFG_CMD_ADD;
156 + e->cmd_str = strdupz(cmd);
157
158 char buf[string_strlen(df_template->function) + strlen(cmd) + 20];
159 snprintfz(buf, sizeof(buf), "%s %s", string2str(df_template->function), cmd);
160
161 rrd_function_run(
137 - host, e->wb, 10, HTTP_ACCESS_ADMIN, buf, false, NULL,
162 + host, e->wb, 10,
163 + HTTP_ACCESS_ALL, buf, false, NULL,
164 dyncfg_echo_cb, e,
165 NULL, NULL,
166 NULL, NULL,
141 - df_job->payload, string2str(df_job->source));
167 + df_job->dyncfg.payload, string2str(df_job->dyncfg.source));
168 }
169
170 void dyncfg_echo_add(const DICTIONARY_ITEM *item_template, const DICTIONARY_ITEM *item_job, DYNCFG *df_template, DYNCFG *df_job, const char *template_id, const char *job_name) {
daemon/config/dyncfg-files.c
+45 -26
@@ -21,7 +21,9 @@ void dyncfg_file_save(const char *id, DYNCFG *df) {
21 return;
22 }
23
24 - df->modified_ut = now_realtime_usec();
24 + df->dyncfg.modified_ut = now_realtime_usec();
25 + if(!df->dyncfg.created_ut)
26 + df->dyncfg.created_ut = df->dyncfg.modified_ut;
27
28 fprintf(fp, "version=%zu\n", DYNCFG_VERSION);
29 fprintf(fp, "id=%s\n", id);
@@ -36,24 +38,24 @@ void dyncfg_file_save(const char *id, DYNCFG *df) {
38 fprintf(fp, "path=%s\n", string2str(df->path));
39 fprintf(fp, "type=%s\n", dyncfg_id2type(df->type));
40
39 - fprintf(fp, "source_type=%s\n", dyncfg_id2source_type(df->source_type));
40 - fprintf(fp, "source=%s\n", string2str(df->source));
41 + fprintf(fp, "source_type=%s\n", dyncfg_id2source_type(df->dyncfg.source_type));
42 + fprintf(fp, "source=%s\n", string2str(df->dyncfg.source));
43
42 - fprintf(fp, "created=%"PRIu64"\n", df->created_ut);
43 - fprintf(fp, "modified=%"PRIu64"\n", df->modified_ut);
44 + fprintf(fp, "created=%"PRIu64"\n", df->dyncfg.created_ut);
45 + fprintf(fp, "modified=%"PRIu64"\n", df->dyncfg.modified_ut);
46 fprintf(fp, "sync=%s\n", df->sync ? "true" : "false");
45 - fprintf(fp, "user_disabled=%s\n", df->user_disabled ? "true" : "false");
46 - fprintf(fp, "saves=%"PRIu32"\n", ++df->saves);
47 + fprintf(fp, "user_disabled=%s\n", df->dyncfg.user_disabled ? "true" : "false");
48 + fprintf(fp, "saves=%"PRIu32"\n", ++df->dyncfg.saves);
49
50 fprintf(fp, "cmds=");
51 dyncfg_cmds2fp(df->cmds, fp);
52 fprintf(fp, "\n");
53
52 - if(df->payload && buffer_strlen(df->payload) > 0) {
53 - fprintf(fp, "content_type=%s\n", content_type_id2string(df->payload->content_type));
54 - fprintf(fp, "content_length=%zu\n", buffer_strlen(df->payload));
54 + if(df->dyncfg.payload && buffer_strlen(df->dyncfg.payload) > 0) {
55 + fprintf(fp, "content_type=%s\n", content_type_id2string(df->dyncfg.payload->content_type));
56 + fprintf(fp, "content_length=%zu\n", buffer_strlen(df->dyncfg.payload));
57 fprintf(fp, "---\n");
56 - fwrite(buffer_tostring(df->payload), 1, buffer_strlen(df->payload), fp);
58 + fwrite(buffer_tostring(df->dyncfg.payload), 1, buffer_strlen(df->dyncfg.payload), fp);
59 }
60
61 fclose(fp);
@@ -66,9 +68,7 @@ void dyncfg_file_load(const char *filename) {
68 return;
69 }
70
69 - DYNCFG tmp = {
70 - .status = DYNCFG_STATUS_ORPHAN,
71 - };
71 + DYNCFG tmp = { 0 };
72
73 char line[PLUGINSD_LINE_MAX];
74 CLEAN_CHAR_P *id = NULL;
@@ -115,19 +115,19 @@ void dyncfg_file_load(const char *filename) {
115 } else if (strcmp(key, "type") == 0) {
116 tmp.type = dyncfg_type2id(value);
117 } else if (strcmp(key, "source_type") == 0) {
118 - tmp.source_type = dyncfg_source_type2id(value);
118 + tmp.dyncfg.source_type = dyncfg_source_type2id(value);
119 } else if (strcmp(key, "source") == 0) {
120 - tmp.source = string_strdupz(value);
120 + tmp.dyncfg.source = string_strdupz(value);
121 } else if (strcmp(key, "created") == 0) {
122 - tmp.created_ut = strtoull(value, NULL, 10);
122 + tmp.dyncfg.created_ut = strtoull(value, NULL, 10);
123 } else if (strcmp(key, "modified") == 0) {
124 - tmp.modified_ut = strtoull(value, NULL, 10);
124 + tmp.dyncfg.modified_ut = strtoull(value, NULL, 10);
125 } else if (strcmp(key, "sync") == 0) {
126 tmp.sync = (strcmp(value, "true") == 0);
127 } else if (strcmp(key, "user_disabled") == 0) {
128 - tmp.user_disabled = (strcmp(value, "true") == 0);
128 + tmp.dyncfg.user_disabled = (strcmp(value, "true") == 0);
129 } else if (strcmp(key, "saves") == 0) {
130 - tmp.saves = strtoull(value, NULL, 10);
130 + tmp.dyncfg.saves = strtoull(value, NULL, 10);
131 } else if (strcmp(key, "content_type") == 0) {
132 content_type = content_type_string2id(value);
133 } else if (strcmp(key, "content_length") == 0) {
@@ -137,12 +137,26 @@ void dyncfg_file_load(const char *filename) {
137 }
138 }
139
140 - if(read_payload && content_length) {
141 - tmp.payload = buffer_create(content_length, NULL);
142 - tmp.payload->content_type = content_type;
143 -
144 - buffer_need_bytes(tmp.payload, content_length);
145 - tmp.payload->len = fread(tmp.payload->buffer, 1, content_length, fp);
140 + if (read_payload) {
141 + // Determine the actual size of the remaining file content
142 + long saved_position = ftell(fp); // Save current position
143 + fseek(fp, 0, SEEK_END);
144 + long total_size = ftell(fp); // Total size of the file
145 + size_t actual_size = total_size - saved_position; // Calculate remaining content size
146 + fseek(fp, saved_position, SEEK_SET); // Reset file pointer to the beginning of the payload
147 +
148 + // Use actual_size instead of content_length to handle the whole remaining file
149 + tmp.dyncfg.payload = buffer_create(actual_size, NULL);
150 + tmp.dyncfg.payload->content_type = content_type;
151 +
152 + buffer_need_bytes(tmp.dyncfg.payload, actual_size);
153 + tmp.dyncfg.payload->len = fread(tmp.dyncfg.payload->buffer, 1, actual_size, fp);
154 +
155 + if (content_length != tmp.dyncfg.payload->len) {
156 + nd_log(NDLS_DAEMON, NDLP_WARNING,
157 + "DYNCFG: content_length %zu does not match actual payload size %zu for file '%s'",
158 + content_length, actual_size, filename);
159 + }
160 }
161
162 fclose(fp);
@@ -156,6 +170,11 @@ void dyncfg_file_load(const char *filename) {
170 return;
171 }
172
173 + tmp.dyncfg.status = DYNCFG_STATUS_ORPHAN;
174 + tmp.dyncfg.restart_required = false;
175 +
176 + dyncfg_set_current_from_dyncfg(&tmp);
177 +
178 dictionary_set(dyncfg_globals.nodes, id, &tmp, sizeof(tmp));
179 }
180
daemon/config/dyncfg-inline.c
+7 -3
@@ -14,7 +14,8 @@ static int dyncfg_inline_callback(struct rrd_function_execute *rfe, void *data _
14 if(cancelled)
15 code = HTTP_RESP_CLIENT_CLOSED_REQUEST;
16 else
17 - code = dyncfg_node_find_and_call(dyncfg_nodes, tr, rfe->function, rfe->stop_monotonic_ut, &cancelled, rfe->payload, rfe->source, rfe->result.wb);
17 + code = dyncfg_node_find_and_call(dyncfg_nodes, tr, rfe->function, rfe->stop_monotonic_ut, &cancelled,
18 + rfe->payload, rfe->user_access, rfe->source, rfe->result.wb);
19
20 if(code == HTTP_RESP_CLIENT_CLOSED_REQUEST || (rfe->is_cancelled.cb && rfe->is_cancelled.cb(rfe->is_cancelled.data))) {
21 buffer_flush(rfe->result.wb);
@@ -27,7 +28,10 @@ static int dyncfg_inline_callback(struct rrd_function_execute *rfe, void *data _
28 return code;
29 }
30
30 -bool dyncfg_add(RRDHOST *host, 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) {
31 +bool dyncfg_add(RRDHOST *host, const char *id, const char *path,
32 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source,
33 + DYNCFG_CMDS cmds, HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
34 + dyncfg_cb_t cb, void *data) {
35
36 struct dyncfg_node tmp = {
37 .cmds = cmds,
@@ -38,7 +42,7 @@ bool dyncfg_add(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS s
42 dictionary_set(dyncfg_nodes, id, &tmp, sizeof(tmp));
43
44 if(!dyncfg_add_low_level(host, id, path, status, type, source_type, source, cmds,
41 - 0, 0, true,
45 + 0, 0, true, view_access, edit_access,
46 dyncfg_inline_callback, NULL)) {
47 dictionary_del(dyncfg_nodes, id);
48 return false;
daemon/config/dyncfg-intercept.c
+209 -222
@@ -18,33 +18,59 @@ struct dyncfg_call {
18 bool from_dyncfg_echo;
19 };
20
21 -DYNCFG_STATUS dyncfg_status_from_successful_response(int code) {
22 - DYNCFG_STATUS status = DYNCFG_STATUS_ACCEPTED;
23 -
24 - switch(code) {
25 - default:
26 - case DYNCFG_RESP_ACCEPTED:
27 - case DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED:
28 - status = DYNCFG_STATUS_ACCEPTED;
29 - break;
21 +static void dyncfg_function_intercept_job_successfully_added(DYNCFG *df_template, int code, struct dyncfg_call *dc) {
22 + char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
23 + snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
24 +
25 + RRDHOST *host = dyncfg_rrdhost(df_template);
26 + if(!host) {
27 + nd_log(NDLS_DAEMON, NDLP_ERR,
28 + "DYNCFG: cannot add job '%s' because host is missing", id);
29 + }
30 + else {
31 + const DICTIONARY_ITEM *item = dyncfg_add_internal(
32 + host,
33 + id,
34 + string2str(df_template->path),
35 + dyncfg_status_from_successful_response(code),
36 + DYNCFG_TYPE_JOB,
37 + DYNCFG_SOURCE_TYPE_DYNCFG,
38 + dc->source,
39 + (df_template->cmds & ~DYNCFG_CMD_ADD) | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST | DYNCFG_CMD_ENABLE |
40 + DYNCFG_CMD_DISABLE | DYNCFG_CMD_REMOVE,
41 + 0,
42 + 0,
43 + df_template->sync,
44 + df_template->view_access,
45 + df_template->edit_access,
46 + df_template->execute_cb,
47 + df_template->execute_cb_data,
48 + false);
49 +
50 + // adding does not create df->dyncfg
51 + // we have to do it here
52
31 - case DYNCFG_RESP_ACCEPTED_DISABLED:
32 - status = DYNCFG_STATUS_DISABLED;
33 - break;
53 + DYNCFG *df = dictionary_acquired_item_value(item);
54 + SWAP(df->dyncfg.payload, dc->payload);
55 + dyncfg_set_dyncfg_source_from_txt(df, dc->source);
56 + df->dyncfg.user_disabled = false;
57 + df->dyncfg.source_type = DYNCFG_SOURCE_TYPE_DYNCFG;
58 + df->dyncfg.status = dyncfg_status_from_successful_response(code);
59
35 - case DYNCFG_RESP_RUNNING:
36 - status = DYNCFG_STATUS_RUNNING;
37 - break;
60 + dyncfg_file_save(id, df); // updates also the df->dyncfg timestamps
61 + dyncfg_update_status_on_successful_add_or_update(df, code);
62
63 + dictionary_acquired_item_release(dyncfg_globals.nodes, item);
64 }
40 -
41 - return status;
65 }
66
44 -static void dyncfg_function_intercept_keep_source(DYNCFG *df, const char *source) {
45 - STRING *old = df->source;
46 - df->source = string_strdupz(source);
47 - string_freez(old);
67 +static void dyncfg_function_intercept_job_successfully_updated(DYNCFG *df, int code, struct dyncfg_call *dc) {
68 + df->dyncfg.status = dyncfg_status_from_successful_response(code);
69 + df->dyncfg.source_type = DYNCFG_SOURCE_TYPE_DYNCFG;
70 + SWAP(df->dyncfg.payload, dc->payload);
71 + dyncfg_set_dyncfg_source_from_txt(df, dc->source);
72 +
73 + dyncfg_update_status_on_successful_add_or_update(df, code);
74 }
75
76 void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_data) {
@@ -55,7 +81,7 @@ void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_d
81 const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item_advanced(dyncfg_globals.nodes, dc->id, -1);
82 if(item) {
83 DYNCFG *df = dictionary_acquired_item_value(item);
58 - bool old_user_disabled = df->user_disabled;
84 + bool old_user_disabled = df->dyncfg.user_disabled;
85 bool save_required = false;
86
87 if (!called_from_dyncfg_echo) {
@@ -63,61 +89,24 @@ void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_d
89
90 if (DYNCFG_RESP_SUCCESS(code)) {
91 if (dc->cmd == DYNCFG_CMD_ADD) {
66 - char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
67 - snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
68 -
69 - RRDHOST *host = dyncfg_rrdhost(df);
70 - if(!host) {
71 - nd_log(NDLS_DAEMON, NDLP_ERR,
72 - "DYNCFG: cannot add job '%s' because host is missing", id);
73 - }
74 - else {
75 - const DICTIONARY_ITEM *new_item = dyncfg_add_internal(
76 - host,
77 - id,
78 - string2str(df->path),
79 - dyncfg_status_from_successful_response(code),
80 - DYNCFG_TYPE_JOB,
81 - DYNCFG_SOURCE_TYPE_DYNCFG,
82 - dc->source,
83 - (df->cmds & ~DYNCFG_CMD_ADD) | DYNCFG_CMD_GET | DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST |
84 - DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_REMOVE,
85 - 0,
86 - 0,
87 - df->sync,
88 - df->execute_cb,
89 - df->execute_cb_data,
90 - false);
91 -
92 - DYNCFG *new_df = dictionary_acquired_item_value(new_item);
93 - SWAP(new_df->payload, dc->payload);
94 - if (code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
95 - new_df->restart_required = true;
96 -
97 - dyncfg_file_save(id, new_df);
98 - dictionary_acquired_item_release(dyncfg_globals.nodes, new_item);
99 - }
92 + dyncfg_function_intercept_job_successfully_added(df, code, dc);
93 } else if (dc->cmd == DYNCFG_CMD_UPDATE) {
101 - df->source_type = DYNCFG_SOURCE_TYPE_DYNCFG;
102 - dyncfg_function_intercept_keep_source(df, dc->source);
103 -
104 - df->status = dyncfg_status_from_successful_response(code);
105 - SWAP(df->payload, dc->payload);
106 -
94 + dyncfg_function_intercept_job_successfully_updated(df, code, dc);
95 save_required = true;
108 - } else if (dc->cmd == DYNCFG_CMD_ENABLE) {
109 - df->user_disabled = false;
110 - dyncfg_function_intercept_keep_source(df, dc->source);
111 - } else if (dc->cmd == DYNCFG_CMD_DISABLE) {
112 - df->user_disabled = true;
113 - dyncfg_function_intercept_keep_source(df, dc->source);
114 - } else if (dc->cmd == DYNCFG_CMD_REMOVE) {
96 + }
97 + else if (dc->cmd == DYNCFG_CMD_ENABLE) {
98 + df->dyncfg.user_disabled = false;
99 + }
100 + else if (dc->cmd == DYNCFG_CMD_DISABLE) {
101 + df->dyncfg.user_disabled = true;
102 + }
103 + else if (dc->cmd == DYNCFG_CMD_REMOVE) {
104 dyncfg_file_delete(dc->id);
105 dictionary_del(dyncfg_globals.nodes, dc->id);
106 }
107
119 - if(dc->cmd != DYNCFG_CMD_ADD && code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
120 - df->restart_required = true;
108 + if (save_required || old_user_disabled != df->dyncfg.user_disabled)
109 + dyncfg_file_save(dc->id, df);
110 }
111 else
112 nd_log(NDLS_DAEMON, NDLP_ERR,
@@ -125,47 +114,10 @@ void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_d
114 }
115 else {
116 // the command was sent by dyncfg
128 -
129 - if(DYNCFG_RESP_SUCCESS(code)) {
130 - if(dc->cmd == DYNCFG_CMD_ADD) {
131 - char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
132 - snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
133 -
134 - const DICTIONARY_ITEM *new_item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
135 - if(new_item) {
136 - DYNCFG *new_df = dictionary_acquired_item_value(new_item);
137 - new_df->status = dyncfg_status_from_successful_response(code);
138 -
139 - if(code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
140 - new_df->restart_required = true;
141 -
142 - dictionary_acquired_item_release(dyncfg_globals.nodes, new_item);
143 - }
144 - }
145 - else if(dc->cmd == DYNCFG_CMD_UPDATE) {
146 - df->status = dyncfg_status_from_successful_response(code);
147 - df->plugin_rejected = false;
148 - }
149 - else if(dc->cmd == DYNCFG_CMD_DISABLE)
150 - df->status = DYNCFG_STATUS_DISABLED;
151 - else if(dc->cmd == DYNCFG_CMD_ENABLE)
152 - df->status = dyncfg_status_from_successful_response(code);
153 -
154 - if(dc->cmd != DYNCFG_CMD_ADD && code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
155 - df->restart_required = true;
156 - }
157 - else {
158 - nd_log(NDLS_DAEMON, NDLP_ERR,
159 - "DYNCFG: plugin returned code %d to dyncfg initiated call: %s", code, dc->function);
160 -
161 - if(dc->cmd & (DYNCFG_CMD_UPDATE | DYNCFG_CMD_ADD))
162 - df->plugin_rejected = true;
163 - }
117 + // these are handled by the echo callback, we don't need to do anything here
118 + ;
119 }
120
166 - if (save_required || old_user_disabled != df->user_disabled)
167 - dyncfg_file_save(dc->id, df);
168 -
121 dictionary_acquired_item_release(dyncfg_globals.nodes, item);
122 }
123
@@ -201,7 +153,7 @@ static void dyncfg_apply_action_on_all_template_jobs(struct rrd_function_execute
153 DYNCFG_CMDS cmd_to_send_to_plugin = c;
154
155 if(c == DYNCFG_CMD_ENABLE)
204 - cmd_to_send_to_plugin = df->user_disabled ? DYNCFG_CMD_DISABLE : DYNCFG_CMD_ENABLE;
156 + cmd_to_send_to_plugin = df->dyncfg.user_disabled ? DYNCFG_CMD_DISABLE : DYNCFG_CMD_ENABLE;
157 else if(c == DYNCFG_CMD_DISABLE)
158 cmd_to_send_to_plugin = DYNCFG_CMD_DISABLE;
159
@@ -219,159 +171,194 @@ static void dyncfg_apply_action_on_all_template_jobs(struct rrd_function_execute
171 // ----------------------------------------------------------------------------
172 // the callback for all config functions
173
174 +static int dyncfg_intercept_early_error(struct rrd_function_execute *rfe, int rc, const char *msg) {
175 + rc = dyncfg_default_response(rfe->result.wb, rc, msg);
176 +
177 + if(rfe->result.cb)
178 + rfe->result.cb(rfe->result.wb, rc, rfe->result.data);
179 +
180 + return rc;
181 +}
182 +
183 int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __maybe_unused) {
184
185 // IMPORTANT: this function MUST call the result_cb even on failures
186
187 bool called_from_dyncfg_echo = rrd_function_has_this_original_result_callback(rfe->transaction, dyncfg_echo_cb);
188 + bool has_payload = rfe->payload && buffer_strlen(rfe->payload) ? true : false;
189 + bool make_the_call_to_plugin = true;
190
228 - DYNCFG_CMDS c = DYNCFG_CMD_NONE;
191 + int rc = HTTP_RESP_INTERNAL_SERVER_ERROR;
192 + DYNCFG_CMDS cmd;
193 const DICTIONARY_ITEM *item = NULL;
194 const char *add_name = NULL;
231 - size_t add_name_len = 0;
232 - if(strncmp(rfe->function, PLUGINSD_FUNCTION_CONFIG " ", sizeof(PLUGINSD_FUNCTION_CONFIG)) == 0) {
233 - const char *id = &rfe->function[sizeof(PLUGINSD_FUNCTION_CONFIG)];
234 - while(isspace(*id)) id++;
235 - const char *space = id;
236 - while(*space && !isspace(*space)) space++;
237 - size_t id_len = space - id;
238 -
239 - const char *cmd = space;
240 - while(isspace(*cmd)) cmd++;
241 - space = cmd;
242 - while(*space && !isspace(*space)) space++;
243 - size_t cmd_len = space - cmd;
244 -
245 - char cmd_copy[cmd_len + 1];
246 - strncpyz(cmd_copy, cmd, cmd_len);
247 - c = dyncfg_cmds2id(cmd_copy);
248 -
249 - if(c == DYNCFG_CMD_ADD) {
250 - add_name = space;
251 - while(isspace(*add_name)) add_name++;
252 - space = add_name;
253 - while(*space && !isspace(*space)) space++;
254 - add_name_len = space - add_name;
255 - }
195
257 - item = dictionary_get_and_acquire_item_advanced(dyncfg_globals.nodes, id, (ssize_t)id_len);
258 - }
196 + char buf[strlen(rfe->function) + 1];
197 + memcpy(buf, rfe->function, sizeof(buf));
198
260 - int rc = HTTP_RESP_INTERNAL_SERVER_ERROR;
199 + char *words[20];
200 + size_t num_words = quoted_strings_splitter_pluginsd(buf, words, 20);
201
262 - if(!item) {
263 - rc = HTTP_RESP_NOT_FOUND;
264 - dyncfg_default_response(rfe->result.wb, rc, "dyncfg functions intercept: id is not found");
202 + size_t i = 0;
203 + char *config = get_word(words, num_words, i++);
204 + char *id = get_word(words, num_words, i++);
205 + char *cmd_str = get_word(words, num_words, i++);
206
266 - if(rfe->result.cb)
267 - rfe->result.cb(rfe->result.wb, rc, rfe->result.data);
207 + if(!config || !*config || strcmp(config, PLUGINSD_FUNCTION_CONFIG) != 0)
208 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
209 + "dyncfg functions intercept: this is not a dyncfg request");
210
269 - return HTTP_RESP_NOT_FOUND;
270 - }
211 + cmd = dyncfg_cmds2id(cmd_str);
212 + if(cmd == DYNCFG_CMD_NONE)
213 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
214 + "dyncfg functions intercept: invalid command received");
215
272 - DYNCFG *df = dictionary_acquired_item_value(item);
273 - const char *id = dictionary_acquired_item_name(item);
274 - bool has_payload = rfe->payload && buffer_strlen(rfe->payload) ? true : false;
275 - bool make_the_call_to_plugin = true;
216 + if(cmd == DYNCFG_CMD_ADD) {
217 + add_name = get_word(words, num_words, i++);
218
277 - if((c & (DYNCFG_CMD_GET | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_REMOVE | DYNCFG_CMD_RESTART)) && has_payload)
278 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: command has a payload, but it is not going to be used: %s", rfe->function);
219 + if(!add_name || !*add_name)
220 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
221 + "dyncfg functions intercept: this action requires a name");
222
280 - if(c == DYNCFG_CMD_NONE) {
281 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: this command is unknown: %s", rfe->function);
223 + if(!called_from_dyncfg_echo) {
224 + char nid[strlen(id) + strlen(add_name) + 2];
225 + snprintfz(nid, sizeof(nid), "%s:%s", id, add_name);
226
283 - rc = HTTP_RESP_BAD_REQUEST;
284 - dyncfg_default_response(rfe->result.wb, rc,
285 - "dyncfg functions intercept: unknown command");
286 - make_the_call_to_plugin = false;
227 + if (dictionary_get(dyncfg_globals.nodes, nid))
228 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
229 + "dyncfg functions intercept: a configuration with this name already exists");
230 + }
231 }
288 - else if(!(df->cmds & c)) {
289 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: this command is not supported by the configuration node: %s", rfe->function);
232
291 - rc = HTTP_RESP_BAD_REQUEST;
292 - dyncfg_default_response(rfe->result.wb, rc,
293 - "dyncfg functions intercept: this command is not supported by this configuration node");
294 - make_the_call_to_plugin = false;
295 - }
296 - else if((c & (DYNCFG_CMD_ADD | DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST)) && !has_payload) {
297 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: command requires a payload, but no payload given: %s", rfe->function);
233 + if((cmd == DYNCFG_CMD_ADD || cmd == DYNCFG_CMD_UPDATE || cmd == DYNCFG_CMD_TEST) && !has_payload)
234 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
235 + "dyncfg functions intercept: this action requires a payload");
236
299 - rc = HTTP_RESP_BAD_REQUEST;
300 - dyncfg_default_response(rfe->result.wb, rc,
301 - "dyncfg functions intercept: payload is required");
302 - make_the_call_to_plugin = false;
303 - }
304 - else if(c == DYNCFG_CMD_SCHEMA) {
305 - bool loaded = false;
306 - if(df->type == DYNCFG_TYPE_JOB) {
307 - if(df->template)
308 - loaded = dyncfg_get_schema(string2str(df->template), rfe->result.wb);
309 - }
310 - else
311 - loaded = dyncfg_get_schema(id, rfe->result.wb);
237 + if((cmd != DYNCFG_CMD_ADD && cmd != DYNCFG_CMD_UPDATE && cmd != DYNCFG_CMD_TEST) && has_payload)
238 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_BAD_REQUEST,
239 + "dyncfg functions intercept: this action does not require a payload");
240
313 - if(loaded) {
314 - rfe->result.wb->content_type = CT_APPLICATION_JSON;
315 - rfe->result.wb->expires = now_realtime_sec();
316 - rc = HTTP_RESP_OK;
241 + item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
242 + if(!item)
243 + return dyncfg_intercept_early_error(rfe, HTTP_RESP_NOT_FOUND,
244 + "dyncfg functions intercept: id is not found");
245 +
246 + DYNCFG *df = dictionary_acquired_item_value(item);
247 +
248 + // 1. check the permissions of the request
249 +
250 + switch(cmd) {
251 + case DYNCFG_CMD_GET:
252 + case DYNCFG_CMD_SCHEMA:
253 + if(!http_access_user_has_enough_access_level_for_endpoint(rfe->user_access, df->view_access)) {
254 + make_the_call_to_plugin = false;
255 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_FORBIDDEN,
256 + "dyncfg: you don't have enough view permissions to execute this command");
257 + }
258 + break;
259 +
260 + case DYNCFG_CMD_ENABLE:
261 + case DYNCFG_CMD_DISABLE:
262 + case DYNCFG_CMD_ADD:
263 + case DYNCFG_CMD_TEST:
264 + case DYNCFG_CMD_UPDATE:
265 + case DYNCFG_CMD_REMOVE:
266 + case DYNCFG_CMD_RESTART:
267 + if(!http_access_user_has_enough_access_level_for_endpoint(rfe->user_access, df->edit_access)) {
268 + make_the_call_to_plugin = false;
269 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_FORBIDDEN,
270 + "dyncfg: you don't have enough edit permissions to execute this command");
271 + }
272 + break;
273 +
274 + default: {
275 make_the_call_to_plugin = false;
276 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_INTERNAL_SERVER_ERROR,
277 + "dyncfg: permissions for this command are not set");
278 }
279 + break;
280 }
320 - else if(c & (DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_RESTART) && df->type == DYNCFG_TYPE_TEMPLATE) {
321 - if(!called_from_dyncfg_echo) {
322 - bool old_user_disabled = df->user_disabled;
323 - if (c == DYNCFG_CMD_ENABLE)
324 - df->user_disabled = false;
325 - else if (c == DYNCFG_CMD_DISABLE)
326 - df->user_disabled = true;
327 -
328 - if (df->user_disabled != old_user_disabled)
329 - dyncfg_file_save(id, df);
330 - }
281
332 - dyncfg_apply_action_on_all_template_jobs(rfe, id, c);
282 + // 2. validate the request parameters
283 +
284 + if(make_the_call_to_plugin) {
285 + if (!(df->cmds & cmd)) {
286 + nd_log(NDLS_DAEMON, NDLP_ERR,
287 + "DYNCFG: this command is not supported by the configuration node: %s", rfe->function);
288
334 - rc = HTTP_RESP_OK;
335 - dyncfg_default_response(rfe->result.wb, rc, "applied");
336 - make_the_call_to_plugin = false;
337 - }
338 - else if(c == DYNCFG_CMD_ADD) {
339 - if (df->type != DYNCFG_TYPE_TEMPLATE) {
340 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: add command can only be applied on templates, not %s: %s",
341 - dyncfg_id2type(df->type), rfe->function);
342 -
343 - rc = HTTP_RESP_BAD_REQUEST;
344 - dyncfg_default_response(rfe->result.wb, rc,
345 - "dyncfg functions intercept: add command is only allowed in templates");
289 make_the_call_to_plugin = false;
290 + rc = dyncfg_default_response(
291 + rfe->result.wb, HTTP_RESP_BAD_REQUEST,
292 + "dyncfg functions intercept: this command is not supported by this configuration node");
293 }
348 - else if (!add_name || !*add_name || !add_name_len) {
349 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: add command does not specify a name: %s", rfe->function);
294 + else if (cmd == DYNCFG_CMD_ADD) {
295 + if (df->type != DYNCFG_TYPE_TEMPLATE) {
296 + make_the_call_to_plugin = false;
297 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST,
298 + "dyncfg functions intercept: add command is only allowed in templates");
299
351 - rc = HTTP_RESP_BAD_REQUEST;
352 - dyncfg_default_response(rfe->result.wb, rc,
353 - "dyncfg functions intercept: command add requires a name, which is missing");
300 + nd_log(NDLS_DAEMON, NDLP_ERR,
301 + "DYNCFG: add command can only be applied on templates, not %s: %s",
302 + dyncfg_id2type(df->type), rfe->function);
303 + }
304 + }
305 + else if (
306 + cmd == DYNCFG_CMD_ENABLE && df->type == DYNCFG_TYPE_JOB &&
307 + dyncfg_is_user_disabled(string2str(df->template))) {
308 + nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot enable a job of a disabled template: %s", rfe->function);
309
310 make_the_call_to_plugin = false;
311 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_BAD_REQUEST,
312 + "dyncfg functions intercept: this job belongs to disabled template");
313 }
314 }
358 - else if(c == DYNCFG_CMD_ENABLE && df->type == DYNCFG_TYPE_JOB && dyncfg_is_user_disabled(string2str(df->template))) {
359 - nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: cannot enable a job of a disabled template: %s", rfe->function);
315
361 - rc = HTTP_RESP_BAD_REQUEST;
362 - dyncfg_default_response(rfe->result.wb, rc,
363 - "dyncfg functions intercept: this job belongs to disabled template");
316 + // 3. check if it is one of the commands we should execute
317
365 - make_the_call_to_plugin = false;
318 + if(make_the_call_to_plugin) {
319 + if (cmd & (DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE | DYNCFG_CMD_RESTART) && df->type == DYNCFG_TYPE_TEMPLATE) {
320 + if (!called_from_dyncfg_echo) {
321 + bool old_user_disabled = df->dyncfg.user_disabled;
322 + if (cmd == DYNCFG_CMD_ENABLE)
323 + df->dyncfg.user_disabled = false;
324 + else if (cmd == DYNCFG_CMD_DISABLE)
325 + df->dyncfg.user_disabled = true;
326 +
327 + if (df->dyncfg.user_disabled != old_user_disabled)
328 + dyncfg_file_save(id, df);
329 + }
330 +
331 + dyncfg_apply_action_on_all_template_jobs(rfe, id, cmd);
332 +
333 + rc = dyncfg_default_response(rfe->result.wb, HTTP_RESP_OK, "applied");
334 + make_the_call_to_plugin = false;
335 + }
336 + else if (cmd == DYNCFG_CMD_SCHEMA) {
337 + bool loaded = false;
338 + if (df->type == DYNCFG_TYPE_JOB) {
339 + if (df->template)
340 + loaded = dyncfg_get_schema(string2str(df->template), rfe->result.wb);
341 + } else
342 + loaded = dyncfg_get_schema(id, rfe->result.wb);
343 +
344 + if (loaded) {
345 + rfe->result.wb->content_type = CT_APPLICATION_JSON;
346 + rfe->result.wb->expires = now_realtime_sec();
347 + rc = HTTP_RESP_OK;
348 + make_the_call_to_plugin = false;
349 + }
350 + }
351 }
352
353 + // 4. execute the command
354 +
355 if(make_the_call_to_plugin) {
356 struct dyncfg_call *dc = callocz(1, sizeof(*dc));
357 dc->function = strdupz(rfe->function);
358 dc->id = strdupz(id);
359 dc->source = rfe->source ? strdupz(rfe->source) : NULL;
373 - dc->add_name = (c == DYNCFG_CMD_ADD) ? strndupz(add_name, add_name_len) : NULL;
374 - dc->cmd = c;
360 + dc->add_name = (add_name) ? strdupz(add_name) : NULL;
361 + dc->cmd = cmd;
362 dc->result_cb = rfe->result.cb;
363 dc->result_cb_data = rfe->result.data;
364 dc->payload = buffer_dup(rfe->payload);
daemon/config/dyncfg-internals.h
+92 -16
@@ -14,26 +14,36 @@ typedef struct dyncfg {
14 STRING *function;
15 STRING *template;
16 STRING *path;
17 - DYNCFG_STATUS status;
18 - DYNCFG_TYPE type;
17 DYNCFG_CMDS cmds;
20 - DYNCFG_SOURCE_TYPE source_type;
21 - STRING *source;
22 - usec_t created_ut;
23 - usec_t modified_ut;
24 - uint32_t saves;
25 - bool sync;
26 - bool user_disabled;
27 - bool plugin_rejected;
28 - bool restart_required;
18 + DYNCFG_TYPE type;
19
30 - BUFFER *payload;
20 + HTTP_ACCESS view_access;
21 + HTTP_ACCESS edit_access;
22
23 + struct {
24 + DYNCFG_STATUS status;
25 + DYNCFG_SOURCE_TYPE source_type;
26 + STRING *source;
27 + usec_t created_ut;
28 + usec_t modified_ut;
29 + } current;
30 +
31 + struct {
32 + uint32_t saves;
33 + bool restart_required;
34 + bool plugin_rejected;
35 + bool user_disabled;
36 + DYNCFG_STATUS status;
37 + DYNCFG_SOURCE_TYPE source_type;
38 + STRING *source;
39 + BUFFER *payload;
40 + usec_t created_ut;
41 + usec_t modified_ut;
42 + } dyncfg;
43 +
44 + bool sync;
45 rrd_function_execute_cb_t execute_cb;
46 void *execute_cb_data;
34 -
35 - // constructor data
36 - bool overwrite_cb;
47 } DYNCFG;
48
49 struct dyncfg_globals {
@@ -55,7 +65,14 @@ void dyncfg_echo(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id, DYNCFG
65 void dyncfg_echo_update(const DICTIONARY_ITEM *item, DYNCFG *df, const char *id);
66 void dyncfg_echo_add(const DICTIONARY_ITEM *item_template, const DICTIONARY_ITEM *item_job, DYNCFG *df_template, DYNCFG *df_job, const char *template_id, const char *job_name);
67
58 -const DICTIONARY_ITEM *dyncfg_add_internal(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, usec_t created_ut, usec_t modified_ut, bool sync, rrd_function_execute_cb_t execute_cb, void *execute_cb_data, bool overwrite_cb);
68 +const DICTIONARY_ITEM *dyncfg_add_internal(RRDHOST *host, const char *id, const char *path,
69 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type,
70 + const char *source, DYNCFG_CMDS cmds,
71 + usec_t created_ut, usec_t modified_ut,
72 + bool sync, HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
73 + rrd_function_execute_cb_t execute_cb, void *execute_cb_data,
74 + bool overwrite_cb);
75 +
76 int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data);
77 void dyncfg_cleanup(DYNCFG *v);
78
@@ -64,4 +81,63 @@ bool dyncfg_is_user_disabled(const char *id);
81 RRDHOST *dyncfg_rrdhost_by_uuid(UUID *uuid);
82 RRDHOST *dyncfg_rrdhost(DYNCFG *df);
83
84 +static inline void dyncfg_copy_dyncfg_source_to_current(DYNCFG *df) {
85 + STRING *old = df->current.source;
86 + df->current.source = string_dup(df->dyncfg.source);
87 + string_freez(old);
88 +}
89 +
90 +static inline void dyncfg_set_dyncfg_source_from_txt(DYNCFG *df, const char *source) {
91 + STRING *old = df->dyncfg.source;
92 + df->dyncfg.source = string_strdupz(source);
93 + string_freez(old);
94 +}
95 +
96 +static inline void dyncfg_set_current_from_dyncfg(DYNCFG *df) {
97 + df->current.status = df->dyncfg.status;
98 + df->current.source_type = df->dyncfg.source_type;
99 +
100 + dyncfg_copy_dyncfg_source_to_current(df);
101 +
102 + if(df->dyncfg.created_ut < df->current.created_ut)
103 + df->current.created_ut = df->dyncfg.created_ut;
104 +
105 + if(df->dyncfg.modified_ut > df->current.modified_ut)
106 + df->current.modified_ut = df->dyncfg.modified_ut;
107 +}
108 +
109 +static inline void dyncfg_update_status_on_successful_add_or_update(DYNCFG *df, int code) {
110 + df->dyncfg.plugin_rejected = false;
111 +
112 + if (code == DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED)
113 + df->dyncfg.restart_required = true;
114 + else
115 + df->dyncfg.restart_required = false;
116 +
117 + dyncfg_set_current_from_dyncfg(df);
118 +}
119 +
120 +static inline DYNCFG_STATUS dyncfg_status_from_successful_response(int code) {
121 + DYNCFG_STATUS status = DYNCFG_STATUS_ACCEPTED;
122 +
123 + switch(code) {
124 + default:
125 + case DYNCFG_RESP_ACCEPTED:
126 + case DYNCFG_RESP_ACCEPTED_RESTART_REQUIRED:
127 + status = DYNCFG_STATUS_ACCEPTED;
128 + break;
129 +
130 + case DYNCFG_RESP_ACCEPTED_DISABLED:
131 + status = DYNCFG_STATUS_DISABLED;
132 + break;
133 +
134 + case DYNCFG_RESP_RUNNING:
135 + status = DYNCFG_STATUS_RUNNING;
136 + break;
137 +
138 + }
139 +
140 + return status;
141 +}
142 +
143 #endif //NETDATA_DYNCFG_INTERNALS_H
daemon/config/dyncfg-tree.c
+32 -22
@@ -25,27 +25,38 @@ static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
25 if(df->type == DYNCFG_TYPE_JOB)
26 buffer_json_member_add_string(wb, "template", string2str(df->template));
27
28 - buffer_json_member_add_string(wb, "status", dyncfg_id2status(df->status));
28 + buffer_json_member_add_string(wb, "status", dyncfg_id2status(df->current.status));
29 dyncfg_cmds2json_array(df->cmds, "cmds", wb);
30 - buffer_json_member_add_string(wb, "source_type", dyncfg_id2source_type(df->source_type));
31 - buffer_json_member_add_string(wb, "source", string2str(df->source));
30 + buffer_json_member_add_object(wb, "access");
31 + {
32 + http_access2buffer_json_array(wb, "view", df->view_access);
33 + http_access2buffer_json_array(wb, "edit", df->edit_access);
34 + }
35 + buffer_json_object_close(wb);
36 + buffer_json_member_add_string(wb, "source_type", dyncfg_id2source_type(df->current.source_type));
37 + buffer_json_member_add_string(wb, "source", string2str(df->current.source));
38 buffer_json_member_add_boolean(wb, "sync", df->sync);
33 - buffer_json_member_add_boolean(wb, "user_disabled", df->user_disabled);
34 - buffer_json_member_add_boolean(wb, "restart_required", df->restart_required);
35 - buffer_json_member_add_boolean(wb, "plugin_rejected", df->restart_required);
39 + buffer_json_member_add_boolean(wb, "user_disabled", df->dyncfg.user_disabled);
40 + buffer_json_member_add_boolean(wb, "restart_required", df->dyncfg.restart_required);
41 + buffer_json_member_add_boolean(wb, "plugin_rejected", df->dyncfg.plugin_rejected);
42 buffer_json_member_add_object(wb, "payload");
43 {
38 - if (df->payload && buffer_strlen(df->payload)) {
44 + if (df->dyncfg.payload && buffer_strlen(df->dyncfg.payload)) {
45 buffer_json_member_add_boolean(wb, "available", true);
40 - buffer_json_member_add_string(wb, "content_type", content_type_id2string(df->payload->content_type));
41 - buffer_json_member_add_uint64(wb, "content_length", df->payload->len);
46 + buffer_json_member_add_string(wb, "status", dyncfg_id2status(df->dyncfg.status));
47 + buffer_json_member_add_string(wb, "source_type", dyncfg_id2source_type(df->dyncfg.source_type));
48 + buffer_json_member_add_string(wb, "source", string2str(df->dyncfg.source));
49 + buffer_json_member_add_uint64(wb, "created_ut", df->dyncfg.created_ut);
50 + buffer_json_member_add_uint64(wb, "modified_ut", df->dyncfg.modified_ut);
51 + buffer_json_member_add_string(wb, "content_type", content_type_id2string(df->dyncfg.payload->content_type));
52 + buffer_json_member_add_uint64(wb, "content_length", df->dyncfg.payload->len);
53 } else
54 buffer_json_member_add_boolean(wb, "available", false);
55 }
56 buffer_json_object_close(wb); // payload
46 - buffer_json_member_add_uint64(wb, "saves", df->saves);
47 - buffer_json_member_add_uint64(wb, "created_ut", df->created_ut);
48 - buffer_json_member_add_uint64(wb, "modified_ut", df->modified_ut);
57 + buffer_json_member_add_uint64(wb, "saves", df->dyncfg.saves);
58 + buffer_json_member_add_uint64(wb, "created_ut", df->current.created_ut);
59 + buffer_json_member_add_uint64(wb, "modified_ut", df->current.modified_ut);
60 }
61 buffer_json_object_close(wb);
62 }
@@ -72,7 +83,7 @@ static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, co
83 continue;
84
85 if(!rrd_function_available(host, string2str(df->function)))
75 - df->status = DYNCFG_STATUS_ORPHAN;
86 + df->current.status = DYNCFG_STATUS_ORPHAN;
87
88 if((id && strcmp(id, df_dfe.name) != 0) || (template && df->template != template))
89 continue;
@@ -105,17 +116,17 @@ static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, co
116
117 dyncfg_to_json(df, dictionary_acquired_item_name(items[i]), wb);
118
108 - if(df->status != DYNCFG_STATUS_ORPHAN) {
109 - if (df->restart_required)
110 - restart_required++;
119 + if (df->dyncfg.plugin_rejected)
120 + plugin_rejected++;
121
112 - if (df->plugin_rejected)
113 - plugin_rejected++;
122 + if(df->current.status != DYNCFG_STATUS_ORPHAN) {
123 + if (df->dyncfg.restart_required)
124 + restart_required++;
125
115 - if (df->status == DYNCFG_STATUS_FAILED)
126 + if (df->current.status == DYNCFG_STATUS_FAILED)
127 status_failed++;
128
118 - if (df->status == DYNCFG_STATUS_INCOMPLETE)
129 + if (df->current.status == DYNCFG_STATUS_INCOMPLETE)
130 status_incomplete++;
131 }
132 }
@@ -208,7 +219,6 @@ cleanup:
219
220 void dyncfg_host_init(RRDHOST *host) {
221 rrd_function_add(host, NULL, PLUGINSD_FUNCTION_CONFIG, 120,
211 - 1000, "Dynamic configuration", "config",
212 - HTTP_ACCESS_ADMIN,
222 + 1000, "Dynamic configuration", "config", HTTP_ACCESS_ANONYMOUS_DATA,
223 true, dyncfg_config_execute_cb, host);
224 }
daemon/config/dyncfg-unittest.c
+20 -17
@@ -125,6 +125,7 @@ bool dyncfg_unittest_parse_payload(BUFFER *payload, TEST *t, DYNCFG_CMDS cmd, co
125 dyncfg_add_low_level(localhost, t2->id, "/unittests",
126 DYNCFG_STATUS_RUNNING, t2->type, t2->source_type, t2->source,
127 t2->cmds, 0, 0, t2->sync,
128 + HTTP_ACCESS_NONE, HTTP_ACCESS_NONE,
129 dyncfg_unittest_execute_cb, t2);
130 }
131 else {
@@ -281,7 +282,7 @@ cleanup:
282 return rc;
283 }
284
284 -static bool dyncfg_unittest_check(TEST *t, const char *cmd, bool received) {
285 +static bool dyncfg_unittest_check(TEST *t, DYNCFG_CMDS c, const char *cmd, bool received) {
286 size_t errors = 0;
287
288 fprintf(stderr, "CHECK '%s' after cmd '%s'...", t->id, cmd);
@@ -346,31 +347,31 @@ static bool dyncfg_unittest_check(TEST *t, const char *cmd, bool received) {
347 fprintf(stderr, "\n");
348 errors++;
349 }
349 - else if(df->type == DYNCFG_TYPE_JOB && df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !df->saves) {
350 + else if(df->type == DYNCFG_TYPE_JOB && df->current.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !df->dyncfg.saves) {
351 fprintf(stderr, "\n - DYNCFG job has no saves!");
352 errors++;
353 }
353 - else if(df->type == DYNCFG_TYPE_JOB && df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && (!df->payload || !buffer_strlen(df->payload))) {
354 + else if(df->type == DYNCFG_TYPE_JOB && df->current.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && (!df->dyncfg.payload || !buffer_strlen(df->dyncfg.payload))) {
355 fprintf(stderr, "\n - DYNCFG job has no payload!");
356 errors++;
357 }
357 - else if(df->user_disabled && !df->saves) {
358 + else if(df->dyncfg.user_disabled && !df->dyncfg.saves) {
359 fprintf(stderr, "\n - DYNCFG disabled config has no saves!");
360 errors++;
361 }
361 - else if(t->source && string_strcmp(df->source, t->source) != 0) {
362 + else if((c & (DYNCFG_CMD_ADD | DYNCFG_CMD_UPDATE)) && t->source && string_strcmp(df->current.source, t->source) != 0) {
363 fprintf(stderr, "\n - source does not match!");
364 errors++;
365 }
365 - else if(df->source && !t->source) {
366 + else if((c & (DYNCFG_CMD_ADD | DYNCFG_CMD_UPDATE)) && df->current.source && !t->source) {
367 fprintf(stderr, "\n - there is a source but it shouldn't be any!");
368 errors++;
369 }
369 - else if(t->needs_save && df->saves <= t->last_saves) {
370 + else if(t->needs_save && df->dyncfg.saves <= t->last_saves) {
371 fprintf(stderr, "\n - should be saved, but it is not saved!");
372 errors++;
373 }
373 - else if(!t->needs_save && df->saves > t->last_saves) {
374 + else if(!t->needs_save && df->dyncfg.saves > t->last_saves) {
375 fprintf(stderr, "\n - should be not be saved, but it saved!");
376 errors++;
377 }
@@ -398,7 +399,7 @@ static void dyncfg_unittest_reset(void) {
399 dyncfg_unittest_register_error(NULL, NULL);
400 }
401 else
401 - t->last_saves = df->saves;
402 + t->last_saves = df->dyncfg.saves;
403 }
404 dfe_done(t);
405 }
@@ -408,7 +409,7 @@ void should_be_saved(TEST *t, DYNCFG_CMDS c) {
409
410 if(t->type == DYNCFG_TYPE_TEMPLATE) {
411 df = dictionary_get(dyncfg_globals.nodes, t->id);
411 - t->current.enabled = !df->user_disabled;
412 + t->current.enabled = !df->dyncfg.user_disabled;
413 }
414
415 t->needs_save =
@@ -466,7 +467,7 @@ static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload,
467
468 should_be_saved(t, c);
469
469 - int rc = rrd_function_run(localhost, wb, 10, HTTP_ACCESS_ADMIN, cmd,
470 + int rc = rrd_function_run(localhost, wb, 10, HTTP_ACCESS_ALL, cmd,
471 true, NULL,
472 NULL, NULL,
473 NULL, NULL,
@@ -477,7 +478,7 @@ static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload,
478 dyncfg_unittest_register_error(NULL, NULL);
479 }
480
480 - dyncfg_unittest_check(t, cmd, true);
481 + dyncfg_unittest_check(t, c, cmd, true);
482
483 if(rc == HTTP_RESP_OK && t->type == DYNCFG_TYPE_TEMPLATE) {
484 if(c == DYNCFG_CMD_ADD) {
@@ -490,7 +491,7 @@ static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload,
491 id, cmd);
492 dyncfg_unittest_register_error(NULL, NULL);
493 }
493 - dyncfg_unittest_check(tt, cmd, true);
494 + dyncfg_unittest_check(tt, c, cmd, true);
495 }
496 else {
497 STRING *template = string_strdupz(t->id);
@@ -508,7 +509,7 @@ static int dyncfg_unittest_run(const char *cmd, BUFFER *wb, const char *payload,
509 tt->expected.enabled = false;
510 if(c == DYNCFG_CMD_ENABLE)
511 tt->expected.enabled = true;
511 - dyncfg_unittest_check(tt, cmd, true);
512 + dyncfg_unittest_check(tt, c, cmd, true);
513 }
514 }
515 }
@@ -549,12 +550,14 @@ static TEST *dyncfg_unittest_add(TEST t) {
550 TEST *ret = dictionary_set(dyncfg_unittest_data.nodes, t.id, &t, sizeof(t));
551
552 if(!dyncfg_add_low_level(localhost, t.id, "/unittests", DYNCFG_STATUS_RUNNING, t.type,
552 - t.source_type, t.source,
553 - t.cmds, 0, 0, t.sync, dyncfg_unittest_execute_cb, ret)) {
553 + t.source_type, t.source,
554 + t.cmds, 0, 0, t.sync,
555 + HTTP_ACCESS_NONE, HTTP_ACCESS_NONE,
556 + dyncfg_unittest_execute_cb, ret)) {
557 dyncfg_unittest_register_error(t.id, "addition of job failed");
558 }
559
557 - dyncfg_unittest_check(ret, "plugin create", t.type != DYNCFG_TYPE_TEMPLATE);
560 + dyncfg_unittest_check(ret, DYNCFG_CMD_NONE, "plugin create", t.type != DYNCFG_TYPE_TEMPLATE);
561
562 return ret;
563 }
daemon/config/dyncfg.c
+78 -51
@@ -21,14 +21,17 @@ RRDHOST *dyncfg_rrdhost(DYNCFG *df) {
21 }
22
23 void dyncfg_cleanup(DYNCFG *v) {
24 - buffer_free(v->payload);
25 - v->payload = NULL;
24 + string_freez(v->dyncfg.source);
25 + v->dyncfg.source = NULL;
26 +
27 + buffer_free(v->dyncfg.payload);
28 + v->dyncfg.payload = NULL;
29
30 string_freez(v->path);
31 v->path = NULL;
32
30 - string_freez(v->source);
31 - v->source = NULL;
33 + string_freez(v->current.source);
34 + v->current.source = NULL;
35
36 string_freez(v->function);
37 v->function = NULL;
@@ -40,11 +43,11 @@ void dyncfg_cleanup(DYNCFG *v) {
43 static void dyncfg_normalize(DYNCFG *df) {
44 usec_t now_ut = now_realtime_usec();
45
43 - if(!df->created_ut)
44 - df->created_ut = now_ut;
46 + if(!df->current.created_ut)
47 + df->current.created_ut = now_ut;
48
46 - if(!df->modified_ut)
47 - df->modified_ut = now_ut;
49 + if(!df->current.modified_ut)
50 + df->current.modified_ut = now_ut;
51 }
52
53 static void dyncfg_delete_cb(const DICTIONARY_ITEM *item __maybe_unused, void *value, void *data __maybe_unused) {
@@ -76,7 +79,10 @@ static void dyncfg_react_cb(const DICTIONARY_ITEM *item __maybe_unused, void *va
79 ;
80 }
81
79 -static bool dyncfg_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data __maybe_unused) {
82 +static bool dyncfg_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void *old_value, void *new_value, void *data) {
83 + bool *overwrite_cb_ptr = data;
84 + bool overwrite_cb = (overwrite_cb_ptr && *overwrite_cb_ptr);
85 +
86 DYNCFG *v = old_value;
87 DYNCFG *nv = new_value;
88
@@ -94,8 +100,8 @@ static bool dyncfg_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void
100 changes++;
101 }
102
97 - if(v->status != nv->status) {
98 - SWAP(v->status, nv->status);
103 + if(v->cmds != nv->cmds) {
104 + SWAP(v->cmds, nv->cmds);
105 changes++;
106 }
107
@@ -104,42 +110,43 @@ static bool dyncfg_conflict_cb(const DICTIONARY_ITEM *item __maybe_unused, void
110 changes++;
111 }
112
107 - if(v->source_type != nv->source_type) {
108 - SWAP(v->source_type, nv->source_type);
113 + if(v->view_access != nv->view_access) {
114 + SWAP(v->view_access, nv->view_access);
115 changes++;
116 }
117
112 - if(v->cmds != nv->cmds) {
113 - SWAP(v->cmds, nv->cmds);
118 + if(v->edit_access != nv->edit_access) {
119 + SWAP(v->edit_access, nv->edit_access);
120 changes++;
121 }
122
117 - if(v->source != nv->source) {
118 - SWAP(v->source, nv->source);
123 + if(v->current.status != nv->current.status) {
124 + SWAP(v->current.status, nv->current.status);
125 changes++;
126 }
127
122 - if(nv->created_ut < v->created_ut) {
123 - SWAP(v->created_ut, nv->created_ut);
128 + if (v->current.source_type != nv->current.source_type) {
129 + SWAP(v->current.source_type, nv->current.source_type);
130 changes++;
131 }
132
127 - if(nv->modified_ut > v->modified_ut) {
128 - SWAP(v->modified_ut, nv->modified_ut);
133 + if (v->current.source != nv->current.source) {
134 + SWAP(v->current.source, nv->current.source);
135 changes++;
136 }
137
132 - if(v->sync != nv->sync) {
133 - SWAP(v->sync, nv->sync);
138 + if(nv->current.created_ut < v->current.created_ut) {
139 + SWAP(v->current.created_ut, nv->current.created_ut);
140 changes++;
141 }
142
137 - if(nv->payload) {
138 - SWAP(v->payload, nv->payload);
143 + if(nv->current.modified_ut > v->current.modified_ut) {
144 + SWAP(v->current.modified_ut, nv->current.modified_ut);
145 changes++;
146 }
147
142 - if(!v->execute_cb || (nv->overwrite_cb && nv->execute_cb && (v->execute_cb != nv->execute_cb || v->execute_cb_data != nv->execute_cb_data))) {
148 + if(!v->execute_cb || (overwrite_cb && nv->execute_cb && (v->execute_cb != nv->execute_cb || v->execute_cb_data != nv->execute_cb_data))) {
149 + v->sync = nv->sync,
150 v->execute_cb = nv->execute_cb;
151 v->execute_cb_data = nv->execute_cb_data;
152 changes++;
@@ -177,27 +184,34 @@ void dyncfg_init_low_level(bool load_saved) {
184
185 // ----------------------------------------------------------------------------
186
180 -const DICTIONARY_ITEM *dyncfg_add_internal(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, usec_t created_ut, usec_t modified_ut, bool sync, rrd_function_execute_cb_t execute_cb, void *execute_cb_data, bool overwrite_cb) {
187 +const DICTIONARY_ITEM *dyncfg_add_internal(RRDHOST *host, const char *id, const char *path,
188 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type,
189 + const char *source, DYNCFG_CMDS cmds,
190 + usec_t created_ut, usec_t modified_ut,
191 + bool sync, HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
192 + rrd_function_execute_cb_t execute_cb, void *execute_cb_data,
193 + bool overwrite_cb) {
194 DYNCFG tmp = {
195 .host_uuid = uuid2UUID(host->host_uuid),
196 .path = string_strdupz(path),
184 - .status = status,
185 - .type = type,
197 .cmds = cmds,
187 - .source_type = source_type,
188 - .source = string_strdupz(source),
189 - .created_ut = created_ut,
190 - .modified_ut = modified_ut,
198 + .type = type,
199 + .view_access = view_access,
200 + .edit_access = edit_access,
201 + .current = {
202 + .status = status,
203 + .source_type = source_type,
204 + .source = string_strdupz(source),
205 + .created_ut = created_ut,
206 + .modified_ut = modified_ut,
207 + },
208 .sync = sync,
192 - .user_disabled = false,
193 - .restart_required = false,
194 - .payload = NULL,
209 + .dyncfg = { 0 },
210 .execute_cb = execute_cb,
211 .execute_cb_data = execute_cb_data,
197 - .overwrite_cb = overwrite_cb,
212 };
213
200 - return dictionary_set_and_acquire_item_advanced(dyncfg_globals.nodes, id, -1, &tmp, sizeof(tmp), NULL);
214 + return dictionary_set_and_acquire_item_advanced(dyncfg_globals.nodes, id, -1, &tmp, sizeof(tmp), &overwrite_cb);
215 }
216
217 static void dyncfg_send_updates(const char *id) {
@@ -210,7 +224,7 @@ static void dyncfg_send_updates(const char *id) {
224 DYNCFG *df = dictionary_acquired_item_value(item);
225
226 if(df->type == DYNCFG_TYPE_SINGLE || df->type == DYNCFG_TYPE_JOB) {
213 - if (df->cmds & DYNCFG_CMD_UPDATE && df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && df->payload && buffer_strlen(df->payload))
227 + if (df->cmds & DYNCFG_CMD_UPDATE && df->dyncfg.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && df->dyncfg.payload && buffer_strlen(df->dyncfg.payload))
228 dyncfg_echo_update(item, df, id);
229 }
230 else if(df->type == DYNCFG_TYPE_TEMPLATE && (df->cmds & DYNCFG_CMD_ADD)) {
@@ -221,7 +235,7 @@ static void dyncfg_send_updates(const char *id) {
235 dfe_start_reentrant(dyncfg_globals.nodes, df_job) {
236 const char *id_template = df_job_dfe.name;
237 if(df_job->type == DYNCFG_TYPE_JOB && // it is a job
224 - df_job->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && // it is dynamically configured
238 + df_job->current.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && // it is dynamically configured
239 df_job->template == template && // it has the same template name
240 strncmp(id_template, id, len) == 0 && // the template name matches (redundant)
241 id_template[len] == ':' && // immediately after the template there is ':'
@@ -243,7 +257,7 @@ bool dyncfg_is_user_disabled(const char *id) {
257 return false;
258
259 DYNCFG *df = dictionary_acquired_item_value(item);
246 - bool ret = df->user_disabled;
260 + bool ret = df->dyncfg.user_disabled;
261 dictionary_acquired_item_release(dyncfg_globals.nodes, item);
262 return ret;
263 }
@@ -267,7 +281,18 @@ bool dyncfg_job_has_registered_template(const char *id) {
281 return ret;
282 }
283
270 -bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, usec_t created_ut, usec_t modified_ut, bool sync, rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
284 +bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path,
285 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source,
286 + DYNCFG_CMDS cmds, usec_t created_ut, usec_t modified_ut, bool sync,
287 + HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
288 + rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
289 +
290 + if(view_access == HTTP_ACCESS_NONE)
291 + view_access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG;
292 +
293 + if(edit_access == HTTP_ACCESS_NONE)
294 + edit_access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG;
295 +
296 if(!dyncfg_is_valid_id(id)) {
297 nd_log(NDLS_DAEMON, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
298 return false;
@@ -318,7 +343,9 @@ bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path, DYNCF
343 nd_log(NDLS_DAEMON, NDLP_NOTICE, "%s", buffer_tostring(t));
344 }
345
321 - const DICTIONARY_ITEM *item = dyncfg_add_internal(host, id, path, status, type, source_type, source, cmds, created_ut, modified_ut, sync, execute_cb, execute_cb_data, true);
346 + const DICTIONARY_ITEM *item = dyncfg_add_internal(host, id, path, status, type, source_type, source, cmds,
347 + created_ut, modified_ut, sync, view_access, edit_access,
348 + execute_cb, execute_cb_data, true);
349 DYNCFG *df = dictionary_acquired_item_value(item);
350
351 // if(df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !df->saves)
@@ -333,14 +360,14 @@ bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path, DYNCF
360 1000,
361 "Dynamic configuration",
362 "config",
336 - HTTP_ACCESS_ADMIN,
363 + (view_access & edit_access),
364 sync,
365 dyncfg_function_intercept_cb,
366 NULL);
367
341 - if(df->type != DYNCFG_TYPE_TEMPLATE) {
368 + if(df->type != DYNCFG_TYPE_TEMPLATE && (df->cmds & (DYNCFG_CMD_ENABLE|DYNCFG_CMD_DISABLE))) {
369 DYNCFG_CMDS status_to_send_to_plugin =
343 - (df->user_disabled || df->status == DYNCFG_STATUS_DISABLED) ? DYNCFG_CMD_DISABLE : DYNCFG_CMD_ENABLE;
370 + (df->dyncfg.user_disabled || df->current.status == DYNCFG_STATUS_DISABLED) ? DYNCFG_CMD_DISABLE : DYNCFG_CMD_ENABLE;
371
372 if (status_to_send_to_plugin == DYNCFG_CMD_ENABLE && dyncfg_is_user_disabled(string2str(df->template)))
373 status_to_send_to_plugin = DYNCFG_CMD_DISABLE;
@@ -348,7 +375,7 @@ bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path, DYNCF
375 dyncfg_echo(item, df, id, status_to_send_to_plugin);
376 }
377
351 - if(!(df->source_type == DYNCFG_SOURCE_TYPE_DYNCFG && df->type == DYNCFG_TYPE_JOB))
378 + if(!(df->current.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && df->type == DYNCFG_TYPE_JOB))
379 dyncfg_send_updates(id);
380
381 dictionary_acquired_item_release(dyncfg_globals.nodes, item);
@@ -368,7 +395,7 @@ void dyncfg_del_low_level(RRDHOST *host, const char *id) {
395 rrd_function_del(host, NULL, string2str(df->function));
396
397 bool garbage_collect = false;
371 - if(df->saves == 0) {
398 + if(df->dyncfg.saves == 0) {
399 dictionary_del(dyncfg_globals.nodes, id);
400 garbage_collect = true;
401 }
@@ -394,7 +421,7 @@ void dyncfg_status_low_level(RRDHOST *host __maybe_unused, const char *id, DYNCF
421 const DICTIONARY_ITEM *item = dictionary_get_and_acquire_item(dyncfg_globals.nodes, id);
422 if(item) {
423 DYNCFG *df = dictionary_acquired_item_value(item);
397 - df->status = status;
424 + df->current.status = status;
425 dictionary_acquired_item_release(dyncfg_globals.nodes, item);
426 }
427 }
@@ -407,11 +434,11 @@ void dyncfg_add_streaming(BUFFER *wb) {
434 // this way the parent does not need to receive removals of config functions;
435
436 buffer_sprintf(wb
410 - , PLUGINSD_KEYWORD_FUNCTION " GLOBAL " PLUGINSD_FUNCTION_CONFIG " %d \"%s\" \"%s\" \"%s\" %d\n"
437 + , PLUGINSD_KEYWORD_FUNCTION " GLOBAL " PLUGINSD_FUNCTION_CONFIG " %d \"%s\" \"%s\" "HTTP_ACCESS_FORMAT" %d\n"
438 , 120
439 , "Dynamic configuration"
440 , "config"
414 - , http_id2access(HTTP_ACCESS_ADMIN)
441 + , (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA)
442 , 1000
443 );
444 }
daemon/config/dyncfg.h
+4 -1
@@ -15,6 +15,7 @@ void dyncfg_host_init(RRDHOST *host);
15 bool dyncfg_add_low_level(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type,
16 DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds,
17 usec_t created_ut, usec_t modified_ut, bool sync,
18 + HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
19 rrd_function_execute_cb_t execute_cb, void *execute_cb_data);
20 void dyncfg_del_low_level(RRDHOST *host, const char *id);
21 void dyncfg_status_low_level(RRDHOST *host, const char *id, DYNCFG_STATUS status);
@@ -22,7 +23,9 @@ void dyncfg_init_low_level(bool load_saved);
23
24 // high-level API for internal modules
25 bool dyncfg_add(RRDHOST *host, const char *id, const char *path, DYNCFG_STATUS status, DYNCFG_TYPE type,
25 - DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds, dyncfg_cb_t cb, void *data);
26 + DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds,
27 + HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
28 + dyncfg_cb_t cb, void *data);
29 void dyncfg_del(RRDHOST *host, const char *id);
30 void dyncfg_status(RRDHOST *host, const char *id, DYNCFG_STATUS status);
31
database/contexts/api_v2.c
+2 -2
@@ -1016,7 +1016,7 @@ static ssize_t rrdcontext_to_json_v2_add_host(void *data, RRDHOST *host, bool qu
1016 .node_ids = &ctl->nodes.ni,
1017 .help = NULL,
1018 .tags = NULL,
1019 - .access = HTTP_ACCESS_MEMBER,
1019 + .access = HTTP_ACCESS_ALL,
1020 .priority = RRDFUNCTIONS_PRIORITY_DEFAULT,
1021 };
1022 host_functions_to_dict(host, ctl->functions.dict, &t, sizeof(t), &t.help, &t.tags, &t.access, &t.priority);
@@ -2249,7 +2249,7 @@ int rrdcontext_to_json_v2(BUFFER *wb, struct api_v2_contexts_request *req, CONTE
2249 }
2250 buffer_json_array_close(wb);
2251 buffer_json_member_add_string(wb, "tags", string2str(t->tags));
2252 - buffer_json_member_add_string(wb, "access", http_id2access(t->access));
2252 + http_access2buffer_json_array(wb, "access", t->access);
2253 buffer_json_member_add_uint64(wb, "priority", t->priority);
2254 }
2255 buffer_json_object_close(wb);
database/rrdfunctions-exporters.c
+9 -9
@@ -14,14 +14,13 @@ void rrd_chart_functions_expose_rrdpush(RRDSET *st, BUFFER *wb) {
14 if(t->options & RRD_FUNCTION_DYNCFG) continue;
15
16 buffer_sprintf(wb
17 - , PLUGINSD_KEYWORD_FUNCTION " \"%s\" %d \"%s\" \"%s\" \"%s\" %d\n"
17 + , PLUGINSD_KEYWORD_FUNCTION " \"%s\" %d \"%s\" \"%s\" "HTTP_ACCESS_FORMAT" %d\n"
18 , t_dfe.name
19 , t->timeout
20 , string2str(t->help)
21 , string2str(t->tags)
22 - , http_id2access(t->access)
23 - ,
24 - t->priority
22 + , (HTTP_ACCESS_FORMAT_CAST)t->access
23 + , t->priority
24 );
25 }
26 dfe_done(t);
@@ -42,12 +41,12 @@ void rrd_global_functions_expose_rrdpush(RRDHOST *host, BUFFER *wb, bool dyncfg)
41 }
42
43 buffer_sprintf(wb
45 - , PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"%s\" \"%s\" %d\n"
44 + , PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"%s\" "HTTP_ACCESS_FORMAT" %d\n"
45 , tmp_dfe.name
46 , tmp->timeout
47 , string2str(tmp->help)
48 , string2str(tmp->tags)
50 - , http_id2access(tmp->access)
49 + , (HTTP_ACCESS_FORMAT_CAST)tmp->access
50 , tmp->priority
51 );
52 }
@@ -78,7 +77,7 @@ static void functions2json(DICTIONARY *functions, BUFFER *wb) {
77
78 buffer_json_member_add_string_or_empty(wb, "options", options);
79 buffer_json_member_add_string_or_empty(wb, "tags", string2str(t->tags));
81 - buffer_json_member_add_string(wb, "access", http_id2access(t->access));
80 + http_access2buffer_json_array(wb, "access", t->access);
81 buffer_json_member_add_uint64(wb, "priority", t->priority);
82 }
83 buffer_json_object_close(wb);
@@ -115,7 +114,7 @@ void host_functions2json(RRDHOST *host, BUFFER *wb) {
114 }
115 buffer_json_array_close(wb);
116 buffer_json_member_add_string(wb, "tags", string2str(t->tags));
118 - buffer_json_member_add_string(wb, "access", http_id2access(t->access));
117 + http_access2buffer_json_array(wb, "access", t->access);
118 buffer_json_member_add_uint64(wb, "priority", t->priority);
119 }
120 buffer_json_object_close(wb);
@@ -138,7 +137,8 @@ void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst,
137 dfe_done(t);
138 }
139
141 -void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t value_size, STRING **help, STRING **tags, HTTP_ACCESS *access, int *priority) {
140 +void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t value_size,
141 + STRING **help, STRING **tags, HTTP_ACCESS *access, int *priority) {
142 if(!host || !host->functions || !dictionary_entries(host->functions) || !dst) return;
143
144 struct rrd_host_function *t;
database/rrdfunctions-exporters.h
+2 -1
@@ -10,7 +10,8 @@ void rrd_global_functions_expose_rrdpush(RRDHOST *host, BUFFER *wb, bool dyncfg)
10
11 void chart_functions2json(RRDSET *st, BUFFER *wb);
12 void chart_functions_to_dict(DICTIONARY *rrdset_functions_view, DICTIONARY *dst, void *value, size_t value_size);
13 -void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t value_size, STRING **help, STRING **tags, HTTP_ACCESS *access, int *priority);
13 +void host_functions_to_dict(RRDHOST *host, DICTIONARY *dst, void *value, size_t value_size, STRING **help, STRING **tags,
14 + HTTP_ACCESS *access, int *priority);
15 void host_functions2json(RRDHOST *host, BUFFER *wb);
16
17 #endif //NETDATA_RRDFUNCTIONS_EXPORTERS_H
database/rrdfunctions-inflight.c
+47 -9
@@ -20,6 +20,8 @@ struct rrd_function_inflight {
20 bool cancelled;
21 usec_t stop_monotonic_ut;
22
23 + HTTP_ACCESS user_access;
24 +
25 BUFFER *payload;
26
27 const DICTIONARY_ITEM *host_function_acquired;
@@ -198,6 +200,7 @@ static inline int rrd_call_function_async_and_dont_wait(struct rrd_function_infl
200 .transaction = &r->transaction_uuid,
201 .function = r->sanitized_cmd,
202 .payload = r->payload,
203 + .user_access = r->user_access,
204 .source = r->source,
205 .stop_monotonic_ut = &r->stop_monotonic_ut,
206 .result = {
@@ -248,6 +251,7 @@ static int rrd_call_function_async_and_wait(struct rrd_function_inflight *r) {
251 .transaction = &r->transaction_uuid,
252 .function = r->sanitized_cmd,
253 .payload = r->payload,
254 + .user_access = r->user_access,
255 .source = r->source,
256 .stop_monotonic_ut = &r->stop_monotonic_ut,
257 .result = {
@@ -388,7 +392,8 @@ static inline int rrd_call_function_async(struct rrd_function_inflight *r, bool
392
393 // ----------------------------------------------------------------------------
394
391 -int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s, HTTP_ACCESS access, const char *cmd,
395 +int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s,
396 + HTTP_ACCESS user_access, const char *cmd,
397 bool wait, const char *transaction,
398 rrd_function_result_callback_t result_cb, void *result_cb_data,
399 rrd_function_progress_cb_t progress_cb, void *progress_cb_data,
@@ -432,21 +437,52 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s, HTTP_ACCES
437
438 struct rrd_host_function *rdcf = dictionary_acquired_item_value(host_function_acquired);
439
435 - if(!web_client_has_enough_access_level(access, rdcf->access)) {
440 + if(!http_access_user_has_enough_access_level_for_endpoint(user_access, rdcf->access)) {
441
442 if(!aclk_connected)
438 - rrd_call_function_error(result_wb, "This Netdata must be connected to Netdata Cloud to access this function.", HTTP_RESP_PRECOND_FAIL);
439 - else if(access >= HTTP_ACCESS_ANY)
440 - rrd_call_function_error(result_wb, "You need to login to the Netdata Cloud space this agent is claimed to, to access this function.", HTTP_RESP_PRECOND_FAIL);
441 - else /* if(access < HTTP_ACCESS_ANY && rdcf->access < access) */
442 - rrd_call_function_error(result_wb, "To access this function you need to be an admin in this Netdata Cloud space.", HTTP_RESP_PRECOND_FAIL);
443 + code = rrd_call_function_error(result_wb,
444 + "This Netdata must be connected to Netdata Cloud for Single-Sign-On (SSO) "
445 + "access this feature. Claim this Netdata to Netdata Cloud to enable access.",
446 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
447 +
448 + else if((rdcf->access & HTTP_ACCESS_SIGNED_ID) && !(user_access & HTTP_ACCESS_SIGNED_ID))
449 + code = rrd_call_function_error(result_wb,
450 + "You need to be authenticated via Netdata Cloud Single-Sign-On (SSO) "
451 + "to access this feature. Sign-in on this dashboard, "
452 + "or access your Netdata via https://app.netdata.cloud.",
453 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
454 +
455 + else if((rdcf->access & HTTP_ACCESS_SAME_SPACE) && !(user_access & HTTP_ACCESS_SAME_SPACE))
456 + code = rrd_call_function_error(result_wb,
457 + "You need to login to the Netdata Cloud space this agent is claimed to, "
458 + "to access this feature.",
459 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
460 +
461 + else if((rdcf->access & HTTP_ACCESS_COMMERCIAL_SPACE) && !(user_access & HTTP_ACCESS_COMMERCIAL_SPACE))
462 + code = rrd_call_function_error(result_wb,
463 + "This feature is only available for commercial users and supporters "
464 + "of Netdata. To use it, please upgrade your space. "
465 + "Thank you for supporting Netdata.",
466 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
467 +
468 + else {
469 + HTTP_ACCESS missing_access = (~user_access) & rdcf->access;
470 + char perms_str[1024];
471 + http_access2txt(perms_str, sizeof(perms_str), ", ", missing_access);
472 +
473 + char msg[2048];
474 + snprintfz(msg, sizeof(msg), "This feature requires additional permissions: %s.", perms_str);
475 +
476 + code = rrd_call_function_error(result_wb, msg,
477 + HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(user_access));
478 + }
479
480 dictionary_acquired_item_release(host->functions, host_function_acquired);
481
482 if(result_cb)
447 - result_cb(result_wb, HTTP_RESP_PRECOND_FAIL, result_cb_data);
483 + result_cb(result_wb, code, result_cb_data);
484
449 - return HTTP_RESP_PRECOND_FAIL;
485 + return code;
486 }
487
488 if(timeout_s <= 0)
@@ -475,6 +511,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s, HTTP_ACCES
511 .sanitized_cmd = strdupz(sanitized_cmd),
512 .sanitized_cmd_length = sanitized_cmd_length,
513 .transaction = strdupz(transaction),
514 + .user_access = user_access,
515 .source = strdupz(sanitized_source),
516 .payload = buffer_dup(payload),
517 .timeout = timeout_s,
@@ -524,6 +561,7 @@ int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s, HTTP_ACCES
561 .transaction = &r->transaction_uuid,
562 .function = r->sanitized_cmd,
563 .payload = r->payload,
564 + .user_access = r->user_access,
565 .source = r->source,
566 .stop_monotonic_ut = &r->stop_monotonic_ut,
567 .result = {
database/rrdfunctions-inline.c
+5 -3
@@ -30,13 +30,15 @@ static int rrd_function_run_inline(struct rrd_function_execute *rfe, void *data)
30 return code;
31 }
32
33 -void rrd_function_add_inline(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority, const char *help, const char *tags,
34 - HTTP_ACCESS access, rrd_function_execute_inline_cb_t execute_cb) {
33 +void rrd_function_add_inline(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority,
34 + const char *help, const char *tags,
35 + HTTP_ACCESS access, rrd_function_execute_inline_cb_t execute_cb) {
36
37 rrd_collector_started(); // this creates a collector that runs for as long as netdata runs
38
39 struct rrd_function_inline *fi = callocz(1, sizeof(struct rrd_function_inline));
40 fi->cb = execute_cb;
41
41 - rrd_function_add(host, st, name, timeout, priority, help, tags, access, true, rrd_function_run_inline, fi);
42 + rrd_function_add(host, st, name, timeout, priority, help, tags, access, true,
43 + rrd_function_run_inline, fi);
44 }
database/rrdfunctions.c
+2 -1
@@ -240,7 +240,8 @@ static inline bool is_function_dyncfg(const char *name) {
240 }
241
242 void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout, int priority,
243 - const char *help, const char *tags, HTTP_ACCESS access, bool sync,
243 + const char *help, const char *tags,
244 + HTTP_ACCESS access, bool sync,
245 rrd_function_execute_cb_t execute_cb, void *execute_cb_data) {
246
247 // RRDSET *st may be NULL in this function
database/rrdfunctions.h
+4 -1
@@ -24,6 +24,8 @@ struct rrd_function_execute {
24 BUFFER *payload;
25 const char *source;
26
27 + HTTP_ACCESS user_access;
28 +
29 usec_t *stop_monotonic_ut;
30
31 struct {
@@ -71,7 +73,8 @@ void rrd_function_add(RRDHOST *host, RRDSET *st, const char *name, int timeout,
73 void rrd_function_del(RRDHOST *host, RRDSET *st, const char *name);
74
75 // call a function, to be run from anywhere
74 -int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s, HTTP_ACCESS access, const char *cmd,
76 +int rrd_function_run(RRDHOST *host, BUFFER *result_wb, int timeout_s,
77 + HTTP_ACCESS user_access, const char *cmd,
78 bool wait, const char *transaction,
79 rrd_function_result_callback_t result_cb, void *result_cb_data,
80 rrd_function_progress_cb_t progress_cb, void *progress_cb_data,
database/rrdhost.c
+4 -2
@@ -1119,11 +1119,13 @@ int rrd_init(char *hostname, struct rrdhost_system_info *system_info, bool unitt
1119 // for the other nodes, the origin server should register it
1120 rrd_function_add_inline(localhost, NULL, "streaming", 10,
1121 RRDFUNCTIONS_PRIORITY_DEFAULT + 1, RRDFUNCTIONS_STREAMING_HELP, "top",
1122 - HTTP_ACCESS_MEMBER, rrdhost_function_streaming);
1122 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
1123 + rrdhost_function_streaming);
1124
1125 rrd_function_add_inline(localhost, NULL, "netdata-api-calls", 10,
1126 RRDFUNCTIONS_PRIORITY_DEFAULT + 2, RRDFUNCTIONS_PROGRESS_HELP, "top",
1126 - HTTP_ACCESS_MEMBER, rrdhost_function_progress);
1127 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA,
1128 + rrdhost_function_progress);
1129
1130 if (likely(system_info)) {
1131 migrate_localhost(&localhost->host_uuid);
health/health_dyncfg.c
+14 -5
@@ -96,12 +96,17 @@ static bool parse_config(json_object *jobj, const char *path, struct rrd_alert_c
96 return true;
97 }
98
99 -static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTOTYPE *base, BUFFER *error) {
100 - JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "name", base->config.name, error, false);
101 -
99 +static bool parse_prototype(json_object *jobj, const char *path, RRD_ALERT_PROTOTYPE *base, BUFFER *error, const char *name) {
100 int64_t version;
101 JSONC_PARSE_INT_OR_ERROR_AND_RETURN(jobj, path, "format_version", version, error);
102
103 + if(version != 1) {
104 + buffer_sprintf(error, "unsupported document version");
105 + return false;
106 + }
107 +
108 + JSONC_PARSE_TXT2STRING_OR_ERROR_AND_RETURN(jobj, path, "name", base->config.name, error, !name && !*name);
109 +
110 json_object *rules;
111 if (json_object_object_get_ex(jobj, "rules", &rules)) {
112 size_t rules_len = json_object_array_length(rules);
@@ -162,7 +167,7 @@ static RRD_ALERT_PROTOTYPE *health_prototype_payload_parse(const char *payload,
167 }
168 json_tokener_free(tokener);
169
165 - if(!parse_prototype(jobj, "", base, error))
170 + if(!parse_prototype(jobj, "", base, error, name))
171 goto cleanup;
172
173 if(!base->config.name && name)
@@ -498,7 +503,7 @@ static int dyncfg_health_prototype_job_action(BUFFER *result, DYNCFG_CMDS cmd, B
503
504 int dyncfg_health_cb(const char *transaction __maybe_unused, const char *id, DYNCFG_CMDS cmd, const char *add_name,
505 BUFFER *payload, usec_t *stop_monotonic_ut __maybe_unused, bool *cancelled __maybe_unused,
501 - BUFFER *result, const char *source, void *data __maybe_unused) {
506 + BUFFER *result, HTTP_ACCESS access __maybe_unused, const char *source, void *data __maybe_unused) {
507
508 char buf[strlen(id) + 1];
509 memcpy(buf, id, sizeof(buf));
@@ -563,6 +568,8 @@ static void health_dyncfg_register_prototype(RRD_ALERT_PROTOTYPE *ap) {
568 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_GET | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE |
569 DYNCFG_CMD_UPDATE | DYNCFG_CMD_TEST |
570 (ap->config.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && !ap->_internal.is_on_disk ? DYNCFG_CMD_REMOVE : 0),
571 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
572 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
573 dyncfg_health_cb, NULL);
574
575 #ifdef NETDATA_TEST_HEALTH_PROTOTYPES_JSON_AND_PARSING
@@ -593,6 +600,8 @@ void health_dyncfg_register_all_prototypes(void) {
600 DYNCFG_STATUS_ACCEPTED, DYNCFG_TYPE_TEMPLATE,
601 DYNCFG_SOURCE_TYPE_INTERNAL, "internal",
602 DYNCFG_CMD_SCHEMA | DYNCFG_CMD_ADD | DYNCFG_CMD_ENABLE | DYNCFG_CMD_DISABLE,
603 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG,
604 + HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_EDIT_AGENT_CONFIG,
605 dyncfg_health_cb, NULL);
606
607 dfe_start_read(health_globals.prototypes.dict, ap) {
health/health_internals.h
+1 -1
@@ -109,7 +109,7 @@ bool rrdcalc_add_from_prototype(RRDHOST *host, RRDSET *st, RRD_ALERT_PROTOTYPE *
109
110 int dyncfg_health_cb(const char *transaction, const char *id, DYNCFG_CMDS cmd, const char *add_name,
111 BUFFER *payload, usec_t *stop_monotonic_ut, bool *cancelled,
112 - BUFFER *result, const char *source, void *data);
112 + BUFFER *result, HTTP_ACCESS access, const char *source, void *data);
113
114 void health_dyncfg_unregister_all_prototypes(void);
115 void health_dyncfg_register_all_prototypes(void);
health/schema.d/health:alert:prototype.json
+261 -215
@@ -2,9 +2,45 @@
2 "jsonSchema": {
3 "$schema": "http://json-schema.org/draft-07/schema#",
4 "definitions": {
5 + "matchPlugin": {
6 + "type": "string",
7 + "default": "*",
8 + "title": "Plugins",
9 + "description": "A simple pattern to match the data collection plugins that are collecting the data this rule is to be applied to. The values it takes are shown as _collect_plugin at the labels filter of the charts on the dashboard."
10 + },
11 + "matchModule": {
12 + "type": "string",
13 + "default": "*",
14 + "title": "Modules",
15 + "description": "A simple pattern to match the data collection plugin modules that are collecting the data this rule is to be applied to. The values it takes are shown as _collect_module at the labels filter of the charts on the dashboard."
16 + },
17 + "matchInstanceLabels": {
18 + "type": "string",
19 + "default": "*",
20 + "title": "Only for instances with these labels",
21 + "description": "A simple pattern to match the instance labels of the instances this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard."
22 + },
23 + "matchHostLabels": {
24 + "type": "string",
25 + "default": "*",
26 + "title": "Only for nodes with these host labels",
27 + "description": "A simple pattern to match the node labels of the nodes this rule is to be applied to. A space separated list of label=value pairs is accepted. Asterisks can be placed anywhere, including the label key. The label keys and their values are available at the labels filter of the charts on the dashboard."
28 + },
29 + "matchHostnames": {
30 + "type": "string",
31 + "default": "*",
32 + "title": "Only for these hostnames",
33 + "description": "A simple pattern to match the hostnames of the nodes this rule is to be applied to."
34 + },
35 + "matchOs": {
36 + "type": "string",
37 + "default": "*",
38 + "title": "Operating Systems",
39 + "description": "A simple pattern to match the operating system name of the nodes this rule is to be applied to. The operating system names are available at the global nodes filter, with label key _os_name."
40 + },
41 "matchInstance": {
42 "type": "object",
7 - "title": "Matching rule for a specific instance",
43 + "title": "Apply this rule to a single instance",
44 "description": "This rule will be applied to a specific instance on all nodes",
45 "properties": {
46 "on": {
@@ -13,42 +49,12 @@
49 "title": "The instance this rule should be applied to",
50 "description": "You can find the instance names on all charts at the instances drop down menu. Do not include the host name in this field."
51 },
16 - "plugin": {
17 - "type": "string",
18 - "default": "*",
19 - "title": "Match data collection plugins",
20 - "description": "A simple pattern to match the data collection plugins that are collecting the data this rule is to be applied to."
21 - },
22 - "module": {
23 - "type": "string",
24 - "default": "*",
25 - "title": "Match data collection plugin modules",
26 - "description": "A simple pattern to match the data collection plugin modules that are collecting the data this rule is to be applied to."
27 - },
28 - "instance_labels": {
29 - "type": "string",
30 - "default": "*",
31 - "title": "Match instance labels",
32 - "description": "A simple pattern to match the instance labels of the instances this rule is to be applied to."
33 - },
34 - "host_labels": {
35 - "type": "string",
36 - "default": "*",
37 - "title": "Match node labels",
38 - "description": "A simple pattern to match the node labels of the nodes this rule is to be applied to."
39 - },
40 - "os": {
41 - "type": "string",
42 - "default": "*",
43 - "title": "Match operating system",
44 - "description": "A simple pattern to match the operating system name of the nodes this rule is to be applied to."
45 - },
46 - "host": {
47 - "type": "string",
48 - "default": "*",
49 - "title": "Match node hostnames",
50 - "description": "A simple pattern to match the hostnames of the nodes this rule is to be applied to."
51 - }
52 + "instance_labels": { "$ref": "#/definitions/matchInstanceLabels" },
53 + "host_labels": { "$ref": "#/definitions/matchHostLabels" },
54 + "host": { "$ref": "#/definitions/matchHostnames" },
55 + "os": { "$ref": "#/definitions/matchOs" },
56 + "plugin": { "$ref": "#/definitions/matchPlugin" },
57 + "module": { "$ref": "#/definitions/matchModule" }
58 },
59 "required": [
60 "on",
@@ -62,57 +68,27 @@
68 },
69 "matchTemplate": {
70 "type": "object",
65 - "title": "Matching rule for applying the alert to multiple instances",
71 + "title": "Apply this rule to all instances of a context",
72 "description": "This rule will applied to all instances on all nodes.",
73 "properties": {
74 "on": {
75 "type": "string",
76 "default": "",
77 "title": "The context of the instances this rule should be applied to",
72 - "description": "You can find the context at the title bar of all charts in the Metrics dashboard, between the chart title and the units (like system.cpu, or disk.io, etc)."
73 - },
74 - "plugin": {
75 - "type": "string",
76 - "default": "*",
77 - "title": "Match data collection plugins",
78 - "description": "A simple pattern to match the data collection plugins that are collecting the data this rule is to be applied to."
79 - },
80 - "module": {
81 - "type": "string",
82 - "default": "*",
83 - "title": "Match data collection plugin modules",
84 - "description": "A simple pattern to match the data collection plugin modules that are collecting the data this rule is to be applied to."
85 - },
86 - "instance_labels": {
87 - "type": "string",
88 - "default": "*",
89 - "title": "Match instance labels",
90 - "description": "A simple pattern to match the instance labels of the instances this rule is to be applied to."
78 + "description": "The context is the code-name of each chart on the dashboard, that appears at the chart title bar, between the chart title and its unit of measurement, like: system.cpu, disk.io, etc."
79 },
80 + "instance_labels": { "$ref": "#/definitions/matchInstanceLabels" },
81 + "host_labels": { "$ref": "#/definitions/matchHostLabels" },
82 + "host": { "$ref": "#/definitions/matchHostnames" },
83 "instances": {
84 "type": "string",
85 "default": "*",
95 - "title": "Match instance names",
86 + "title": "On on these instances",
87 "description": "A simple pattern to match the instance names of the instances this rule is to be applied to."
88 },
98 - "host_labels": {
99 - "type": "string",
100 - "default": "*",
101 - "title": "Match node labels",
102 - "description": "A simple pattern to match the node labels of the nodes this rule is to be applied to."
103 - },
104 - "os": {
105 - "type": "string",
106 - "default": "*",
107 - "title": "Match operating system",
108 - "description": "A simple pattern to match the operating system name of the nodes this rule is to be applied to."
109 - },
110 - "host": {
111 - "type": "string",
112 - "default": "*",
113 - "title": "Match node hostnames",
114 - "description": "A simple pattern to match the hostnames of the nodes this rule is to be applied to."
115 - }
89 + "os": { "$ref": "#/definitions/matchOs" },
90 + "plugin": { "$ref": "#/definitions/matchPlugin" },
91 + "module": { "$ref": "#/definitions/matchModule" }
92 },
93 "required": [
94 "on",
@@ -147,7 +123,7 @@
123 },
124 "component": {
125 "type": "string",
150 - "title": "Alert Component (sub-type)",
126 + "title": "Alert Component",
127 "description": "Component is a sub-type of Alert Type. Examples: 'CPU', 'Memory', 'Network', 'Disk', 'Hardware', 'nginx', 'redis', 'postgresql', etc."
128 },
129 "classification": {
@@ -157,101 +133,71 @@
133 },
134 "value": {
135 "type": "object",
160 - "title": "Alert Value",
136 + "title": "Alert Value Calculation",
137 "description": "Each alert has a value. This section defines how this value is calculated.",
138 "properties": {
139 "database_lookup": {
140 "type": "object",
141 + "title": "Database Query to Get Value",
142 + "description": "The database query to be executed to calculate the value of the alert. When set, the query is executed before any other calculations. The result of the query will be available as $this in further calculations.",
143 "properties": {
144 "after": {
145 "type": "integer",
146 "default": 0,
169 - "title": "Time-Series Oldest Time",
147 + "title": "From",
148 "description": "The oldest timestamp of the time-series data to be included in the query. Negative values define a duration in seconds in the past (so, -60 means a minute ago)."
149 },
150 "before": {
151 "type": "integer",
152 "default": 0,
175 - "title": "Time-Series Newest Time",
153 + "title": "To",
154 "description": "The newest timestamp of the time-series data to be included in the query. Negative value define a duration in seconds in the past (so, -60 means a minute ago). Zero means now."
155 },
156 + "dimensions": {
157 + "type": "string",
158 + "title": "Dimensions",
159 + "description": "A simple pattern to match the dimensions that should be included in the query",
160 + "default": "*"
161 + },
162 "grouping": {
163 "type": "string",
180 - "enum": [
181 - "average",
182 - "median",
183 - "min",
184 - "max",
185 - "sum",
186 - "incremental_sum",
187 - "stddev",
188 - "cv",
189 - "trimmed-mean1",
190 - "trimmed-mean2",
191 - "trimmed-mean3",
192 - "trimmed-mean",
193 - "trimmed-mean10",
194 - "trimmed-mean15",
195 - "trimmed-mean20",
196 - "trimmed-mean25",
197 - "trimmed-median1",
198 - "trimmed-median2",
199 - "trimmed-median3",
200 - "trimmed-median",
201 - "trimmed-median10",
202 - "trimmed-median15",
203 - "trimmed-median20",
204 - "trimmed-median25",
205 - "percentile99",
206 - "percentile98",
207 - "percentile97",
208 - "percentile",
209 - "percentile90",
210 - "percentile80",
211 - "percentile75",
212 - "percentile50",
213 - "percentile25",
214 - "ses",
215 - "des",
216 - "countif"
217 - ],
218 - "enumNames": [
219 - "The mean (average) value",
220 - "The median value",
221 - "The minimum value",
222 - "The maximum value",
223 - "The sum of all the values",
224 - "The delta of the latest and oldest values",
225 - "The standard deviation of the values",
226 - "The standard deviation expresses as a % of the mean value",
227 - "The mean after trimming 1% of the extreme values",
228 - "The mean after trimming 2% of the extreme values",
229 - "The mean after trimming 3% of the extreme values",
230 - "The mean after trimming 5% of the extreme values",
231 - "The mean after trimming 10% of the extreme values",
232 - "The mean after trimming 15% of the extreme values",
233 - "The mean after trimming 20% of the extreme values",
234 - "The mean after trimming 25% of the extreme values",
235 - "The median after trimming 1% of the extreme values",
236 - "The median after trimming 2% of the extreme values",
237 - "The median after trimming 3% of the extreme values",
238 - "The median after trimming 5% of the extreme values",
239 - "The median after trimming 10% of the extreme values",
240 - "The median after trimming 15% of the extreme values",
241 - "The median after trimming 20% of the extreme values",
242 - "The median after trimming 25% of the extreme values",
243 - "The 99th percentile of the values",
244 - "The 98th percentile of the values",
245 - "The 97th percentile of the values",
246 - "The 95th percentile of the values",
247 - "The 90th percentile of the values",
248 - "The 80th percentile of the values",
249 - "The 75th percentile of the values",
250 - "The 50th percentile of the values",
251 - "The 25th percentile of the values",
252 - "Single Exponential Smoothing",
253 - "Double Exponential Smoothing",
254 - "Count If zero"
164 + "oneOf": [
165 + { "const": "average", "title": "The mean (average) value" },
166 + { "const": "median", "title": "The median value" },
167 + { "const": "min", "title": "The minimum value" },
168 + { "const": "max", "title": "The maximum value" },
169 + { "const": "sum", "title": "The sum of all the values" },
170 + { "const": "incremental_sum", "title": "The delta of the latest and oldest values" },
171 + { "const": "stddev", "title": "The standard deviation of the values" },
172 + { "const": "cv", "title": "The standard deviation expresses as a % of the mean value" },
173 + { "const": "trimmed-mean1", "title": "The mean after trimming 1% of the extreme values" },
174 + { "const": "trimmed-mean2", "title": "The mean after trimming 2% of the extreme values" },
175 + { "const": "trimmed-mean3", "title": "The mean after trimming 3% of the extreme values" },
176 + { "const": "trimmed-mean", "title": "The mean after trimming 5% of the extreme values" },
177 + { "const": "trimmed-mean10", "title": "The mean after trimming 10% of the extreme values" },
178 + { "const": "trimmed-mean15", "title": "The mean after trimming 15% of the extreme values" },
179 + { "const": "trimmed-mean20", "title": "The mean after trimming 20% of the extreme values" },
180 + { "const": "trimmed-mean25", "title": "The mean after trimming 25% of the extreme values" },
181 + { "const": "trimmed-median1", "title": "The median after trimming 1% of the extreme values" },
182 + { "const": "trimmed-median2", "title": "The median after trimming 2% of the extreme values" },
183 + { "const": "trimmed-median3", "title": "The median after trimming 3% of the extreme values" },
184 + { "const": "trimmed-median", "title": "The median after trimming 5% of the extreme values" },
185 + { "const": "trimmed-median10", "title": "The median after trimming 10% of the extreme values" },
186 + { "const": "trimmed-median15", "title": "The median after trimming 15% of the extreme values" },
187 + { "const": "trimmed-median20", "title": "The median after trimming 20% of the extreme values" },
188 + { "const": "trimmed-median25", "title": "The median after trimming 25% of the extreme values" },
189 + { "const": "percentile99", "title": "The 99th percentile of the values" },
190 + { "const": "percentile98", "title": "The 98th percentile of the values" },
191 + { "const": "percentile97", "title": "The 97th percentile of the values" },
192 + { "const": "percentile", "title": "The 95th percentile of the values" },
193 + { "const": "percentile90", "title": "The 90th percentile of the values" },
194 + { "const": "percentile80", "title": "The 80th percentile of the values" },
195 + { "const": "percentile75", "title": "The 75th percentile of the values" },
196 + { "const": "percentile50", "title": "The 50th percentile of the values" },
197 + { "const": "percentile25", "title": "The 25th percentile of the values" },
198 + { "const": "ses", "title": "Single Exponential Smoothing" },
199 + { "const": "des", "title": "Double Exponential Smoothing" },
200 + { "const": "countif", "title": "Count If zero" }
201 ],
202 "default": "average",
203 "title": "Time Aggregation Function",
@@ -263,58 +209,53 @@
209 "description": "Options affecting the way the value is calculated",
210 "uniqueItems": true,
211 "items": {
266 - "enum": [
267 - "unaligned",
268 - "abs",
269 - "min2max",
270 - "null2zero",
271 - "percentage",
272 - "anomaly-bit",
273 - "match_ids",
274 - "match_names"
275 - ],
276 - "enumNames": [
277 - "Do not shift the time-frame for visual presentation",
278 - "Make all values positive before using them",
279 - "Use the delta of the minimum to the maximum value",
280 - "Treat gaps in the time-series as a zero value",
281 - "Calculate the percentage of the selected dimensions over the sum of all dimensions",
282 - "Query the anomaly rate of the samples collected",
283 - "Match only dimension IDs, not Names",
284 - "Match only dimension Names, not IDs"
212 + "oneOf": [
213 + { "const": "unaligned", "title": "Do not shift the time-frame for visual presentation" },
214 + { "const": "abs", "title": "Make all values positive before using them" },
215 + { "const": "min2max", "title": "Use the delta of the minimum to the maximum value" },
216 + { "const": "null2zero", "title": "Treat gaps in the time-series as a zero value" },
217 + { "const": "percentage", "title": "Calculate the percentage of the selected dimensions over the sum of all dimensions" },
218 + { "const": "anomaly-bit", "title": "Query the anomaly rate of the samples collected" },
219 + { "const": "match_ids", "title": "Match only dimension IDs, not Names" },
220 + { "const": "match_names", "title": "Match only dimension Names, not IDs" }
221 ]
222 },
223 "default": [ "unaligned" ]
288 - },
289 - "dimensions": {
290 - "type": "string",
291 - "title": "Dimensions Selection Pattern",
292 - "description": "A simple pattern to match the dimensions that should be included in the query",
293 - "default": "*"
224 }
225 }
226 },
227 "calculation": {
228 "type": "string",
299 - "title": "Calculation Expression",
229 + "title": "Calculation to Transform the Value",
230 "description": "The database value is available as '$this'. This expression can utilize variables to transform the value of the alert."
231 },
232 "units": {
233 "type": "string",
304 - "title": "Alert Unit of Measurement",
234 + "title": "Unit",
235 "description": "The unit of measurement the alert value is expressed with. If unset, the units of the instance the alert is attached to will be used."
236 }
237 }
238 },
239 "conditions": {
240 "type": "object",
241 + "title": "Warning and Critical Conditions",
242 "properties": {
243 + "warning_condition": {
244 + "type": "string",
245 + "title": "Warning Expression",
246 + "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in warning level."
247 + },
248 + "critical_condition": {
249 + "type": "string",
250 + "title": "Critical Expression",
251 + "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in critical level."
252 + },
253 "green": {
254 "type": [
255 "integer",
256 "null"
257 ],
317 - "title": "Healthy threshold ($green)",
258 + "title": "Healthy threshold",
259 "description": "A threshold that indicates a healthy status. This threshold can be used as '$green' in the alert conditions."
260 },
261 "red": {
@@ -322,24 +263,14 @@
263 "integer",
264 "null"
265 ],
325 - "title": "Critical threshold ($red)",
266 + "title": "Critical threshold",
267 "description": "A threshold that indicates a critical status. This threshold can be used as '$red' in the alert conditions."
327 - },
328 - "warning_condition": {
329 - "type": "string",
330 - "title": "Warning Expression",
331 - "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in warning level."
332 - },
333 - "critical_condition": {
334 - "type": "string",
335 - "title": "Critical Expression",
336 - "description": "The alert value is available as '$this'. If this expression evaluates to a non-zero value, the alert is considered to be in critical level."
268 }
269 }
270 },
271 "action": {
272 "type": "object",
342 - "title": "Alert Action",
273 + "title": "Alert Action (notification or automation)",
274 "description": "The action the alert should take when it transitions states",
275 "properties": {
276 "execute": {
@@ -349,7 +280,7 @@
280 },
281 "recipient": {
282 "type": "string",
352 - "title": "Notification Recipients",
283 + "title": "Recipient(s)",
284 "description": "A space separated list of the recipients of the alert notifications. The special recipient 'silent' prevents this alert from taking any action (i.e. sending notifications)."
285 },
286 "options": {
@@ -358,11 +289,8 @@
289 "description": "Options related to the actions this alert will take.",
290 "uniqueItems": true,
291 "items": {
361 - "enum": [
362 - "no-clear-notification"
363 - ],
364 - "enumNames": [
365 - "Do not perform any action when the alert is cleared"
292 + "oneOf": [
293 + { "const": "no-clear-notification", "title": "Do not perform any action when the alert is cleared"}
294 ]
295 },
296 "default": []
@@ -382,21 +310,21 @@
310 "title": "Delay when going Down",
311 "description": "Delay the action (notification) that many seconds, when the alert is recovering."
312 },
385 - "max": {
386 - "type": "integer",
387 - "title": "Max Acceptable Delay",
388 - "description": "The maximum acceptable delay in seconds, for taking the action (notification)."
389 - },
313 "multiplier": {
314 "type": "number",
392 - "title": "Back-Off on Transitions",
315 + "title": "Back-Off",
316 "description": "Multiply the delay by this number, every time the alert transitions to a new state, while the action (notification) is being delayed."
317 + },
318 + "max": {
319 + "type": "integer",
320 + "title": "Max",
321 + "description": "The maximum acceptable delay in seconds, for taking the action (notification)."
322 }
323 }
324 },
325 "repeat": {
326 "type": "object",
399 - "title": "Action Auto-Repeat",
327 + "title": "Auto-Repeat Action",
328 "description": "Repeat the action while the alert is raised.",
329 "properties": {
330 "enabled": {
@@ -442,13 +370,9 @@
370 },
371 "type": {
372 "type": "string",
445 - "enum": [
446 - "instance",
447 - "template"
448 - ],
449 - "enumNames": [
450 - "Apply this rule to a specific instance (deprecated)",
451 - "Apply this rule to all instances matching the rules"
373 + "oneOf": [
374 + { "const": "instance" , "title": "Apply this rule to a specific instance (deprecated)" },
375 + { "const": "template" , "title": "Apply this rule to all instances" }
376 ],
377 "default": "template",
378 "title": "Type of rule",
@@ -492,7 +416,126 @@
416 "enabled": {
417 "ui:widget": "checkbox"
418 },
419 + "match": {
420 + "ui:classNames": "dyncfg-grid dyncfg-grid-col-6",
421 + "on": {
422 + "ui:classNames": "dyncfg-grid-col-span-1-6"
423 + },
424 + "instance_labels": {
425 + "ui:classNames": "dyncfg-grid-col-span-1-2"
426 + },
427 + "host_labels": {
428 + "ui:classNames": "dyncfg-grid-col-span-3-2"
429 + },
430 + "host": {
431 + "ui:classNames": "dyncfg-grid-col-span-5-2"
432 + },
433 + "instances": {
434 + "ui:classNames": "dyncfg-grid-col-span-1-2"
435 + }
436 + },
437 "config": {
438 + "ui:classNames": "dyncfg-grid dyncfg-grid-col-6",
439 + "summary": {
440 + "ui:classNames": "dyncfg-grid-col-span-1-3"
441 + },
442 + "info": {
443 + "ui:classNames": "dyncfg-grid-col-span-4-3"
444 + },
445 + "type": {
446 + "ui:classNames": "dyncfg-grid-col-span-1-2"
447 + },
448 + "component": {
449 + "ui:classNames": "dyncfg-grid-col-span-3-2"
450 + },
451 + "classification": {
452 + "ui:classNames": "dyncfg-grid-col-span-5-2"
453 + },
454 + "value": {
455 + "ui:classNames": "dyncfg-grid-col-span-1-6",
456 + "database_lookup": {
457 + "ui:classNames": "dyncfg-grid-col-span-1-6",
458 + "after": {
459 + "ui:classNames": "dyncfg-grid-col-span-1-1"
460 + },
461 + "before": {
462 + "ui:classNames": "dyncfg-grid-col-span-2-1"
463 + },
464 + "dimensions": {
465 + "ui:classNames": "dyncfg-grid-col-span-3-4"
466 + },
467 + "grouping": {
468 + "ui:classNames": "dyncfg-grid-col-span-1-3"
469 + },
470 + "options": {
471 + "ui:classNames": "dyncfg-grid-col-span-4-3"
472 + }
473 + },
474 + "calculation": {
475 + "ui:classNames": "dyncfg-grid-col-span-1-5"
476 + },
477 + "units": {
478 + "ui:classNames": "dyncfg-grid-col-span-6-1"
479 + }
480 + },
481 + "conditions": {
482 + "ui:classNames": "dyncfg-grid-col-span-1-6",
483 + "warning_condition": {
484 + "ui:classNames": "dyncfg-grid-col-span-1-2"
485 + },
486 + "critical_condition": {
487 + "ui:classNames": "dyncfg-grid-col-span-3-2"
488 + },
489 + "green": {
490 + "ui:classNames": "dyncfg-grid-col-span-5-1"
491 + },
492 + "red": {
493 + "ui:classNames": "dyncfg-grid-col-span-6-1"
494 + }
495 + },
496 + "action": {
497 + "ui:classNames": "dyncfg-grid-col-span-1-6",
498 + "execute": {
499 + "ui:classNames": "dyncfg-grid-col-span-1-3"
500 + },
501 + "recipient": {
502 + "ui:classNames": "dyncfg-grid-col-span-4-1"
503 + },
504 + "options": {
505 + "ui:classNames": "dyncfg-grid-col-span-5-2"
506 + },
507 + "delay": {
508 + "ui:Collapsible": true,
509 + "ui:InitiallyExpanded": false,
510 + "ui:classNames": "dyncfg-grid-col-span-1-6",
511 + "up": {
512 + "ui:classNames": "dyncfg-grid-col-span-1-2"
513 + },
514 + "down": {
515 + "ui:classNames": "dyncfg-grid-col-span-3-2"
516 + },
517 + "multiplier": {
518 + "ui:classNames": "dyncfg-grid-col-span-5-1"
519 + },
520 + "max": {
521 + "ui:classNames": "dyncfg-grid-col-span-6-1"
522 + }
523 + },
524 + "repeat": {
525 + "ui:Collapsible": true,
526 + "ui:InitiallyExpanded": false,
527 + "ui:classNames": "dyncfg-grid-col-span-1-6",
528 + "enabled": {
529 + "ui:classNames": "dyncfg-grid-col-span-1-2"
530 + },
531 + "warning": {
532 + "ui:classNames": "dyncfg-grid-col-span-3-2"
533 + },
534 + "critical": {
535 + "ui:classNames": "dyncfg-grid-col-span-5-2"
536 + }
537 + }
538 + },
539 "hash": {
540 "ui:widget": "hidden"
541 },
@@ -505,5 +548,8 @@
548 }
549 }
550 }
551 + },
552 + "uiOptions": {
553 + "fullPage": true
554 }
555 }
libnetdata/config/dyncfg.c
+2 -2
@@ -251,7 +251,7 @@ int dyncfg_default_response(BUFFER *wb, int code, const char *msg) {
251
252 int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction, const char *function,
253 usec_t *stop_monotonic_ut, bool *cancelled,
254 - BUFFER *payload, const char *source, BUFFER *result) {
254 + BUFFER *payload, HTTP_ACCESS access, const char *source, BUFFER *result) {
255 if(!function || !*function)
256 return dyncfg_default_response(result, HTTP_RESP_BAD_REQUEST, "command received is empty");
257
@@ -284,7 +284,7 @@ int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction,
284 buffer_flush(result);
285 result->content_type = CT_APPLICATION_JSON;
286
287 - int code = df->cb(transaction, id, cmd, add_name, payload, stop_monotonic_ut, cancelled, result, source, df->data);
287 + int code = df->cb(transaction, id, cmd, add_name, payload, stop_monotonic_ut, cancelled, result, access, source, df->data);
288
289 if(!result->expires)
290 result->expires = now_realtime_sec();
libnetdata/config/dyncfg.h
+4 -2
@@ -66,7 +66,9 @@ char *dyncfg_escape_id_for_filename(const char *id);
66 #include "../buffer/buffer.h"
67 #include "../dictionary/dictionary.h"
68
69 -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);
69 +typedef int (*dyncfg_cb_t)(const char *transaction, const char *id, DYNCFG_CMDS cmd, const char *add_name,
70 + BUFFER *payload, usec_t *stop_monotonic_ut, bool *cancelled, BUFFER *result,
71 + HTTP_ACCESS access, const char *source, void *data);
72
73 struct dyncfg_node {
74 DYNCFG_TYPE type;
@@ -81,6 +83,6 @@ int dyncfg_default_response(BUFFER *wb, int code, const char *msg);
83
84 int dyncfg_node_find_and_call(DICTIONARY *dyncfg_nodes, const char *transaction, const char *function,
85 usec_t *stop_monotonic_ut, bool *cancelled,
84 - BUFFER *payload, const char *source, BUFFER *result);
86 + BUFFER *payload, HTTP_ACCESS access, const char *source, BUFFER *result);
87
88 #endif //LIBNETDATA_DYNCFG_H
libnetdata/functions_evloop/functions_evloop.c
+42 -19
@@ -2,7 +2,9 @@
2
3 #include "functions_evloop.h"
4
5 -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);
5 +static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut,
6 + bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
7 + const char *source, void *data);
8
9 struct functions_evloop_worker_job {
10 bool used;
@@ -14,6 +16,7 @@ struct functions_evloop_worker_job {
16 time_t timeout;
17
18 BUFFER *payload;
19 + HTTP_ACCESS access;
20 const char *source;
21
22 functions_evloop_worker_execute_t cb;
@@ -90,7 +93,7 @@ static void *rrd_functions_worker_globals_worker_main(void *arg) {
93
94 last_acquired = true;
95 j = dictionary_acquired_item_value(acquired);
93 - j->cb(j->transaction, j->cmd, &j->stop_monotonic_ut, &j->cancelled, j->payload, j->source, j->cb_data);
96 + j->cb(j->transaction, j->cmd, &j->stop_monotonic_ut, &j->cancelled, j->payload, j->access, j->source, j->cb_data);
97 dictionary_del(wg->worker_queue, j->transaction);
98 dictionary_acquired_item_release(wg->worker_queue, acquired);
99 dictionary_garbage_collect(wg->worker_queue);
@@ -101,7 +104,7 @@ static void *rrd_functions_worker_globals_worker_main(void *arg) {
104 return NULL;
105 }
106
104 -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) {
107 +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) {
108 if(!transaction || !*transaction || !timeout_s || !*timeout_s || !function || !*function) {
109 nd_log(NDLS_COLLECTORS, NDLP_ERR, "Received incomplete %s (transaction = '%s', timeout = '%s', function = '%s'). Ignoring it.",
110 keyword,
@@ -129,6 +132,7 @@ static void worker_add_job(struct functions_evloop_globals *wg, const char *keyw
132 .stop_monotonic_ut = now_monotonic_usec() + (timeout * USEC_PER_SEC),
133 .used = false,
134 .payload = buffer_dup(payload),
135 + .access = http_access_from_hex(access),
136 .source = source ? strdupz(source) : NULL,
137 .cb = we->cb,
138 .cb_data = we->cb_data,
@@ -164,6 +168,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
168 char *transaction;
169 char *function;
170 char *timeout_s;
171 + char *access;
172 char *source;
173 char *content_type;
174 } deferred = { 0 };
@@ -190,7 +195,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
195 if(deferred.enabled) {
196 char *s = (char *)buffer_tostring(buffer);
197
193 - if(strstr(&s[deferred.last_len], PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END "\n") != NULL) {
198 + if(strstr(&s[deferred.last_len], PLUGINSD_CALL_FUNCTION_PAYLOAD_END "\n") != NULL) {
199 if(deferred.last_len > 0)
200 // remove the trailing newline from the buffer
201 deferred.last_len--;
@@ -198,12 +203,15 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
203 s[deferred.last_len] = '\0';
204 buffer->len = deferred.last_len;
205 buffer->content_type = content_type_string2id(deferred.content_type);
201 - worker_add_job(wg, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD, deferred.transaction, deferred.function, deferred.timeout_s, buffer, deferred.source);
206 + worker_add_job(wg,
207 + PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN, deferred.transaction, deferred.function,
208 + deferred.timeout_s, buffer, deferred.access, deferred.source);
209 buffer_flush(buffer);
210
211 freez(deferred.transaction);
212 freez(deferred.function);
213 freez(deferred.timeout_s);
214 + freez(deferred.access);
215 freez(deferred.source);
216 freez(deferred.content_type);
217 memset(&deferred, 0, sizeof(deferred));
@@ -219,29 +227,32 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
227
228 const char *keyword = get_word(words, num_words, 0);
229
222 - if(keyword && (strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION) == 0)) {
230 + if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION) == 0)) {
231 char *transaction = get_word(words, num_words, 1);
232 char *timeout_s = get_word(words, num_words, 2);
233 char *function = get_word(words, num_words, 3);
226 - char *source = get_word(words, num_words, 4);
227 - worker_add_job(wg, keyword, transaction, function, timeout_s, NULL, source);
234 + char *access = get_word(words, num_words, 4);
235 + char *source = get_word(words, num_words, 5);
236 + worker_add_job(wg, keyword, transaction, function, timeout_s, NULL, access, source);
237 }
229 - else if(keyword && (strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD) == 0)) {
238 + else if(keyword && (strcmp(keyword, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN) == 0)) {
239 char *transaction = get_word(words, num_words, 1);
240 char *timeout_s = get_word(words, num_words, 2);
241 char *function = get_word(words, num_words, 3);
233 - char *source = get_word(words, num_words, 4);
234 - char *content_type = get_word(words, num_words, 5);
242 + char *access = get_word(words, num_words, 4);
243 + char *source = get_word(words, num_words, 5);
244 + char *content_type = get_word(words, num_words, 6);
245
246 deferred.transaction = strdupz(transaction ? transaction : "");
247 deferred.timeout_s = strdupz(timeout_s ? timeout_s : "");
248 deferred.function = strdupz(function ? function : "");
249 + deferred.access = strdupz(access ? access : "");
250 deferred.source = strdupz(source ? source : "");
251 deferred.content_type = strdupz(content_type ? content_type : "");
252 deferred.last_len = 0;
253 deferred.enabled = true;
254 }
244 - else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_CANCEL) == 0) {
255 + else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_CANCEL) == 0) {
256 char *transaction = get_word(words, num_words, 1);
257 const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
258 if(acquired) {
@@ -254,7 +265,7 @@ static void *rrd_functions_worker_globals_reader_main(void *arg) {
265 else
266 nd_log(NDLS_COLLECTORS, NDLP_NOTICE, "Received CANCEL for transaction '%s', but it not available here", transaction);
267 }
257 - else if(keyword && strcmp(keyword, PLUGINSD_KEYWORD_FUNCTION_PROGRESS) == 0) {
268 + else if(keyword && strcmp(keyword, PLUGINSD_CALL_FUNCTION_PROGRESS) == 0) {
269 char *transaction = get_word(words, num_words, 1);
270 const DICTIONARY_ITEM *acquired = dictionary_get_and_acquire_item(wg->worker_queue, transaction);
271 if(acquired) {
@@ -338,11 +349,12 @@ void functions_evloop_cancel_threads(struct functions_evloop_globals *wg){
349 // ----------------------------------------------------------------------------
350
351 static void functions_evloop_config_cb(const char *transaction, char *function, usec_t *stop_monotonic_ut, bool *cancelled,
341 - BUFFER *payload, const char *source, void *data) {
352 + BUFFER *payload, HTTP_ACCESS access, const char *source, void *data) {
353 struct functions_evloop_globals *wg = data;
354
355 CLEAN_BUFFER *result = buffer_create(1024, NULL);
345 - int code = dyncfg_node_find_and_call(wg->dyncfg.nodes, transaction, function, stop_monotonic_ut, cancelled, payload, source, result);
356 + int code = dyncfg_node_find_and_call(wg->dyncfg.nodes, transaction, function, stop_monotonic_ut,
357 + cancelled, payload, access, source, result);
358
359 netdata_mutex_lock(wg->stdout_mutex);
360 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,
364 netdata_mutex_unlock(wg->stdout_mutex);
365 }
366
355 -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) {
367 +void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
368 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type,
369 + const char *source, DYNCFG_CMDS cmds,
370 + HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
371 + dyncfg_cb_t cb, void *data) {
372 +
373 if(!dyncfg_is_valid_id(id)) {
374 nd_log(NDLS_COLLECTORS, NDLP_ERR, "DYNCFG: id '%s' is invalid. Ignoring dynamic configuration for it.", id);
375 return;
@@ -372,9 +389,15 @@ void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char
389 netdata_mutex_lock(wg->stdout_mutex);
390
391 fprintf(stdout,
375 - PLUGINSD_KEYWORD_CONFIG " '%s' " PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE " '%s' '%s' '%s' '%s' '%s' '%s'\n",
376 - id, dyncfg_id2status(status), dyncfg_id2type(type), path,
377 - dyncfg_id2source_type(source_type), source, buffer_tostring(c)
392 + PLUGINSD_KEYWORD_CONFIG " '%s' " PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE " '%s' '%s' '%s' '%s' '%s' '%s' "HTTP_ACCESS_FORMAT" "HTTP_ACCESS_FORMAT"\n",
393 + id,
394 + dyncfg_id2status(status),
395 + dyncfg_id2type(type), path,
396 + dyncfg_id2source_type(source_type),
397 + source,
398 + buffer_tostring(c),
399 + (HTTP_ACCESS_FORMAT_CAST)view_access,
400 + (HTTP_ACCESS_FORMAT_CAST)edit_access
401 );
402 fflush(stdout);
403
libnetdata/functions_evloop/functions_evloop.h
+47 -28
@@ -6,10 +6,11 @@
6 #include "../libnetdata.h"
7
8 #define MAX_FUNCTION_PARAMETERS 1024
9 +#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
10
11 +// plugins.d 1st version of the external plugins and streaming protocol
12 #define PLUGINSD_KEYWORD_CHART "CHART"
13 #define PLUGINSD_KEYWORD_CHART_DEFINITION_END "CHART_DEFINITION_END"
12 -
14 #define PLUGINSD_KEYWORD_DIMENSION "DIMENSION"
15 #define PLUGINSD_KEYWORD_BEGIN "BEGIN"
16 #define PLUGINSD_KEYWORD_SET "SET"
@@ -21,45 +22,59 @@
22 #define PLUGINSD_KEYWORD_OVERWRITE "OVERWRITE"
23 #define PLUGINSD_KEYWORD_CLABEL "CLABEL"
24 #define PLUGINSD_KEYWORD_CLABEL_COMMIT "CLABEL_COMMIT"
25 +#define PLUGINSD_KEYWORD_EXIT "EXIT"
26
25 -#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION"
26 -#define PLUGINSD_KEYWORD_FUNCTION_PAYLOAD "FUNCTION_PAYLOAD"
27 -#define PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END "FUNCTION_PAYLOAD_END"
28 -#define PLUGINSD_KEYWORD_FUNCTION_CANCEL "FUNCTION_CANCEL"
29 -#define PLUGINSD_KEYWORD_FUNCTION_PROGRESS "FUNCTION_PROGRESS"
30 -#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN"
31 -#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END"
32 -
33 -#define PLUGINSD_KEYWORD_CONFIG "CONFIG"
34 -#define PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE "create"
35 -#define PLUGINSD_KEYWORD_CONFIG_ACTION_DELETE "delete"
36 -#define PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS "status"
37 -
38 -#define PLUGINSD_FUNCTION_CONFIG "config"
39 -
40 -#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
41 -#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
42 -#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
43 -#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
44 -#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
45 -#define PLUGINSD_KEYWORD_REPLAY_END "REND"
46 -
27 +// high-speed versions of BEGIN, SET, END
28 #define PLUGINSD_KEYWORD_BEGIN_V2 "BEGIN2"
29 #define PLUGINSD_KEYWORD_SET_V2 "SET2"
30 #define PLUGINSD_KEYWORD_END_V2 "END2"
31
32 +// super high-speed versions of BEGIN, SET, END have this as first parameter
33 +// enabled with the streaming capability STREAM_CAP_SLOTS
34 +#define PLUGINSD_KEYWORD_SLOT "SLOT" // to change the length of this, update pluginsd_extract_chart_slot() too
35 +
36 +// virtual hosts (only for external plugins - for streaming virtual hosts are like all other hosts)
37 #define PLUGINSD_KEYWORD_HOST_DEFINE "HOST_DEFINE"
38 #define PLUGINSD_KEYWORD_HOST_DEFINE_END "HOST_DEFINE_END"
39 #define PLUGINSD_KEYWORD_HOST_LABEL "HOST_LABEL"
40 #define PLUGINSD_KEYWORD_HOST "HOST"
41
56 -#define PLUGINSD_KEYWORD_EXIT "EXIT"
42 +// replication
43 +// enabled with STREAM_CAP_REPLICATION
44 +#define PLUGINSD_KEYWORD_REPLAY_CHART "REPLAY_CHART"
45 +#define PLUGINSD_KEYWORD_REPLAY_BEGIN "RBEGIN"
46 +#define PLUGINSD_KEYWORD_REPLAY_SET "RSET"
47 +#define PLUGINSD_KEYWORD_REPLAY_RRDDIM_STATE "RDSTATE"
48 +#define PLUGINSD_KEYWORD_REPLAY_RRDSET_STATE "RSSTATE"
49 +#define PLUGINSD_KEYWORD_REPLAY_END "REND"
50
58 -#define PLUGINSD_KEYWORD_SLOT "SLOT" // to change the length of this, update pluginsd_extract_chart_slot() too
51 +// plugins.d accepts these for functions (from external plugins or streaming children)
52 +// related to STREAM_CAP_FUNCTIONS, STREAM_CAP_PROGRESS
53 +#define PLUGINSD_KEYWORD_FUNCTION "FUNCTION" // define a function
54 +#define PLUGINSD_KEYWORD_FUNCTION_PROGRESS "FUNCTION_PROGRESS" // send updates about function progress
55 +#define PLUGINSD_KEYWORD_FUNCTION_RESULT_BEGIN "FUNCTION_RESULT_BEGIN" // the result of a function transaction
56 +#define PLUGINSD_KEYWORD_FUNCTION_RESULT_END "FUNCTION_RESULT_END" // the end of the result of a func. trans.
57 +
58 +// plugins.d sends these for functions (to external plugins or streaming children)
59 +// related to STREAM_CAP_FUNCTIONS, STREAM_CAP_PROGRESS
60 +#define PLUGINSD_CALL_FUNCTION "FUNCTION" // call a function to a plugin or remote host
61 +#define PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN "FUNCTION_PAYLOAD" // call a function with a payload
62 +#define PLUGINSD_CALL_FUNCTION_PAYLOAD_END "FUNCTION_PAYLOAD_END" // function payload ends
63 +#define PLUGINSD_CALL_FUNCTION_CANCEL "FUNCTION_CANCEL" // cancel a running function transaction
64 +#define PLUGINSD_CALL_FUNCTION_PROGRESS "FUNCTION_PROGRESS" // let the function know the user is waiting
65 +
66 +// dyncfg
67 +// enabled with STREAM_CAP_DYNCFG
68 +#define PLUGINSD_KEYWORD_CONFIG "CONFIG"
69 +#define PLUGINSD_KEYWORD_CONFIG_ACTION_CREATE "create"
70 +#define PLUGINSD_KEYWORD_CONFIG_ACTION_DELETE "delete"
71 +#define PLUGINSD_KEYWORD_CONFIG_ACTION_STATUS "status"
72 +#define PLUGINSD_FUNCTION_CONFIG "config"
73
60 -#define PLUGINS_FUNCTIONS_TIMEOUT_DEFAULT 10 // seconds
74 +typedef void (*functions_evloop_worker_execute_t)(const char *transaction, char *function, usec_t *stop_monotonic_ut,
75 + bool *cancelled, BUFFER *payload, HTTP_ACCESS access,
76 + const char *source, void *data);
77
62 -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);
78 struct functions_evloop_worker_job;
79 struct functions_evloop_globals *functions_evloop_init(size_t worker_threads, const char *tag, netdata_mutex_t *stdout_mutex, bool *plugin_should_exit);
80 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,
138 fflush(stdout);
139 }
140
126 -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);
141 +void functions_evloop_dyncfg_add(struct functions_evloop_globals *wg, const char *id, const char *path,
142 + DYNCFG_STATUS status, DYNCFG_TYPE type, DYNCFG_SOURCE_TYPE source_type, const char *source, DYNCFG_CMDS cmds,
143 + HTTP_ACCESS view_access, HTTP_ACCESS edit_access,
144 + dyncfg_cb_t cb, void *data);
145 +
146 void functions_evloop_dyncfg_del(struct functions_evloop_globals *wg, const char *id);
147 void functions_evloop_dyncfg_status(struct functions_evloop_globals *wg, const char *id, DYNCFG_STATUS status);
148
libnetdata/http/http_access.c
+165 -22
@@ -3,41 +3,184 @@
3 #include "../libnetdata.h"
4
5 static struct {
6 - HTTP_ACCESS access;
6 + HTTP_USER_ROLE access;
7 const char *name;
8 -} access_levels[] = {
9 - { .access = HTTP_ACCESS_NONE, .name = "none" },
10 - { .access = HTTP_ACCESS_MEMBER, .name = "member" },
11 - { .access = HTTP_ACCESS_ADMIN, .name = "admin" },
12 - { .access = HTTP_ACCESS_ANY, .name = "any" },
8 +} user_roles[] = {
9 + { .access = HTTP_USER_ROLE_NONE, .name = "none" },
10 + { .access = HTTP_USER_ROLE_ADMIN, .name = "admin" },
11 + { .access = HTTP_USER_ROLE_MANAGER, .name = "manager" },
12 + { .access = HTTP_USER_ROLE_TROUBLESHOOTER, .name = "troubleshooter" },
13 + { .access = HTTP_USER_ROLE_OBSERVER, .name = "observer" },
14 + { .access = HTTP_USER_ROLE_MEMBER, .name = "member" },
15 + { .access = HTTP_USER_ROLE_BILLING, .name = "billing" },
16 + { .access = HTTP_USER_ROLE_ANY, .name = "any" },
17
14 - { .access = HTTP_ACCESS_MEMBER, .name = "members" },
15 - { .access = HTTP_ACCESS_ADMIN, .name = "admins" },
16 - { .access = HTTP_ACCESS_ANY, .name = "all" },
18 + { .access = HTTP_USER_ROLE_MEMBER, .name = "members" },
19 + { .access = HTTP_USER_ROLE_ADMIN, .name = "admins" },
20 + { .access = HTTP_USER_ROLE_ANY, .name = "all" },
21
22 // terminator
23 { .access = 0, .name = NULL },
24 };
25
22 -HTTP_ACCESS http_access2id(const char *access) {
23 - if(!access || !*access)
24 - return HTTP_ACCESS_MEMBER;
26 +HTTP_USER_ROLE http_user_role2id(const char *role) {
27 + if(!role || !*role)
28 + return HTTP_USER_ROLE_MEMBER;
29
26 - for(size_t i = 0; access_levels[i].name ;i++) {
27 - if(strcmp(access_levels[i].name, access) == 0)
28 - return access_levels[i].access;
30 + for(size_t i = 0; user_roles[i].name ;i++) {
31 + if(strcmp(user_roles[i].name, role) == 0)
32 + return user_roles[i].access;
33 }
34
31 - nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP access level '%s' is not valid", access);
32 - return HTTP_ACCESS_NONE;
35 + nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP user role '%s' is not valid", role);
36 + return HTTP_USER_ROLE_NONE;
37 }
38
35 -const char *http_id2access(HTTP_ACCESS access) {
36 - for(size_t i = 0; access_levels[i].name ;i++) {
37 - if(access == access_levels[i].access)
38 - return access_levels[i].name;
39 +const char *http_id2user_role(HTTP_USER_ROLE role) {
40 + for(size_t i = 0; user_roles[i].name ;i++) {
41 + if(role == user_roles[i].access)
42 + return user_roles[i].name;
43 }
44
41 - nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP access level %d is not valid", access);
45 + nd_log(NDLS_DAEMON, NDLP_WARNING, "HTTP user role %d is not valid", role);
46 return "none";
47 }
48 +
49 +// --------------------------------------------------------------------------------------------------------------------
50 +
51 +static struct {
52 + const char *name;
53 + uint32_t hash;
54 + HTTP_ACCESS value;
55 +} http_accesses[] = {
56 + {"none" , 0 , HTTP_ACCESS_NONE}
57 + , {"signed-in" , 0 , HTTP_ACCESS_SIGNED_ID}
58 + , {"same-space" , 0 , HTTP_ACCESS_SAME_SPACE}
59 + , {"commercial" , 0 , HTTP_ACCESS_COMMERCIAL_SPACE}
60 + , {"anonymous-data" , 0 , HTTP_ACCESS_ANONYMOUS_DATA}
61 + , {"sensitive-data" , 0 , HTTP_ACCESS_SENSITIVE_DATA}
62 + , {"view-config" , 0 , HTTP_ACCESS_VIEW_AGENT_CONFIG}
63 + , {"edit-config" , 0 , HTTP_ACCESS_EDIT_AGENT_CONFIG}
64 + , {"view-notifications-config" , 0 , HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG}
65 + , {"edit-notifications-config" , 0 , HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG}
66 + , {"view-alerts-silencing" , 0 , HTTP_ACCESS_VIEW_ALERTS_SILENCING}
67 + , {"edit-alerts-silencing" , 0 , HTTP_ACCESS_EDIT_ALERTS_SILENCING}
68 +
69 + , {NULL , 0 , 0}
70 +};
71 +
72 +inline HTTP_ACCESS http_access2id_one(const char *str) {
73 + HTTP_ACCESS ret = 0;
74 +
75 + if(!str || !*str) return ret;
76 +
77 + uint32_t hash = simple_hash(str);
78 + int i;
79 + for(i = 0; http_accesses[i].name ; i++) {
80 + if(unlikely(!http_accesses[i].hash))
81 + http_accesses[i].hash = simple_hash(http_accesses[i].name);
82 +
83 + if (unlikely(hash == http_accesses[i].hash && !strcmp(str, http_accesses[i].name))) {
84 + ret |= http_accesses[i].value;
85 + break;
86 + }
87 + }
88 +
89 + return ret;
90 +}
91 +
92 +inline HTTP_ACCESS http_access2id(char *str) {
93 + HTTP_ACCESS ret = 0;
94 + char *tok;
95 +
96 + while(str && *str && (tok = strsep_skip_consecutive_separators(&str, ", |"))) {
97 + if(!*tok) continue;
98 + ret |= http_access2id_one(tok);
99 + }
100 +
101 + return ret;
102 +}
103 +
104 +void http_access2buffer_json_array(BUFFER *wb, const char *key, HTTP_ACCESS access) {
105 + buffer_json_member_add_array(wb, key);
106 +
107 + HTTP_ACCESS used = 0; // to prevent adding duplicates
108 + for(int i = 0; http_accesses[i].name ; i++) {
109 + if (unlikely((http_accesses[i].value & access) && !(http_accesses[i].value & used))) {
110 + const char *name = http_accesses[i].name;
111 + used |= http_accesses[i].value;
112 +
113 + buffer_json_add_array_item_string(wb, name);
114 + }
115 + }
116 +
117 + buffer_json_array_close(wb);
118 +}
119 +
120 +void http_access2txt(char *buf, size_t size, const char *separator, HTTP_ACCESS access) {
121 + char *write = buf;
122 + char *end = &buf[size - 1];
123 +
124 + HTTP_ACCESS used = 0; // to prevent adding duplicates
125 + int added = 0;
126 + for(int i = 0; http_accesses[i].name ; i++) {
127 + if (unlikely((http_accesses[i].value & access) && !(http_accesses[i].value & used))) {
128 + const char *name = http_accesses[i].name;
129 + used |= http_accesses[i].value;
130 +
131 + if(added && write < end) {
132 + const char *s = separator;
133 + while(*s && write < end)
134 + *write++ = *s++;
135 + }
136 +
137 + while(*name && write < end)
138 + *write++ = *name++;
139 +
140 + added++;
141 + }
142 + }
143 + *write = *end = '\0';
144 +}
145 +
146 +HTTP_ACCESS http_access_from_hex_mapping_old_roles(const char *str) {
147 + if(!str || !*str)
148 + return HTTP_ACCESS_NONE;
149 +
150 + if(strcmp(str, "any") == 0 || strcmp(str, "all") == 0)
151 + return HTTP_ACCESS_MAP_OLD_ANY;
152 +
153 + if(strcmp(str, "member") == 0 || strcmp(str, "members") == 0)
154 + return HTTP_ACCESS_MAP_OLD_MEMBER;
155 +
156 + else if(strcmp(str, "admin") == 0 || strcmp(str, "admins") == 0)
157 + return HTTP_ACCESS_MAP_OLD_ADMIN;
158 +
159 + return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
160 +}
161 +
162 +HTTP_ACCESS http_access_from_hex(const char *str) {
163 + if(!str || !*str)
164 + return HTTP_ACCESS_NONE;
165 +
166 + return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
167 +}
168 +
169 +HTTP_ACCESS http_access_from_source(const char *str) {
170 + if(!str || !*str)
171 + return HTTP_ACCESS_NONE;
172 +
173 + HTTP_ACCESS access = HTTP_ACCESS_NONE;
174 +
175 + const char *permissions = strstr(str, "permissions=");
176 + if(permissions)
177 + access = (HTTP_ACCESS)strtoull(permissions + 12, NULL, 16) & HTTP_ACCESS_ALL;
178 +
179 + return access;
180 +}
181 +
182 +bool log_cb_http_access_to_hex(BUFFER *wb, void *data) {
183 + HTTP_ACCESS access = *((HTTP_ACCESS *)data);
184 + buffer_sprintf(wb, HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)access);
185 + return true;
186 +}
libnetdata/http/http_access.h
+118 -26
@@ -4,38 +4,130 @@
4 #define NETDATA_HTTP_ACCESS_H
5
6 typedef enum __attribute__((packed)) {
7 - HTTP_ACCESS_NONE = 0,
8 - HTTP_ACCESS_ADMIN = 1,
9 - HTTP_ACCESS_MEMBER = 2,
10 - HTTP_ACCESS_ANY = 3,
7 + HTTP_USER_ROLE_NONE = 0,
8 + HTTP_USER_ROLE_ADMIN = 1,
9 + HTTP_USER_ROLE_MANAGER = 2,
10 + HTTP_USER_ROLE_TROUBLESHOOTER = 3,
11 + HTTP_USER_ROLE_OBSERVER = 4,
12 + HTTP_USER_ROLE_MEMBER = 5,
13 + HTTP_USER_ROLE_BILLING = 6,
14 + HTTP_USER_ROLE_ANY = 7,
15
16 // keep this list so that lower numbers are more strict access levels
13 -} HTTP_ACCESS;
17 +} HTTP_USER_ROLE;
18 +const char *http_id2user_role(HTTP_USER_ROLE role);
19 +HTTP_USER_ROLE http_user_role2id(const char *role);
20
15 -const char *http_id2access(HTTP_ACCESS access);
16 -HTTP_ACCESS http_access2id(const char *access);
21 +typedef enum __attribute__((packed)) {
22 + HTTP_ACCESS_NONE = 0, // adm man trb obs mem bil
23 + HTTP_ACCESS_SIGNED_ID = (1 << 0), // User is authenticated A A A A A A
24 + HTTP_ACCESS_SAME_SPACE = (1 << 1), // NC user+agent = same space A A A A A A
25 + HTTP_ACCESS_COMMERCIAL_SPACE = (1 << 2), // NC A - - - - -
26 + HTTP_ACCESS_ANONYMOUS_DATA = (1 << 3), // NC room:Read A A A SR SR -
27 + HTTP_ACCESS_SENSITIVE_DATA = (1 << 4), // NC agent:ViewSensitiveData A A A - - -
28 + HTTP_ACCESS_VIEW_AGENT_CONFIG = (1 << 5), // NC agent:ReadDynCfg P P - - - -
29 + HTTP_ACCESS_EDIT_AGENT_CONFIG = (1 << 6), // NC agent:EditDynCfg P P - - - -
30 + HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG = (1 << 7), // NC agent:ViewNotificationsConfig P - - - - -
31 + HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG = (1 << 8), // NC agent:EditNotificationsConfig P - - - - -
32 + HTTP_ACCESS_VIEW_ALERTS_SILENCING = (1 << 9), // NC space:GetSystemSilencingRules A A A - A -
33 + HTTP_ACCESS_EDIT_ALERTS_SILENCING = (1 << 10), // NC space:CreateSystemSilencingRule P P - - P -
34 +} HTTP_ACCESS; // ---------------------
35 + // A = always
36 + // P = commercial plan
37 + // SR = same room (Us+Ag)
38 +
39 +#define HTTP_ACCESS_FORMAT "0x%" PRIx32
40 +#define HTTP_ACCESS_FORMAT_CAST uint32_t
41 +
42 +#define HTTP_ACCESS_ALL (HTTP_ACCESS)( \
43 + HTTP_ACCESS_SIGNED_ID \
44 + | HTTP_ACCESS_SAME_SPACE \
45 + | HTTP_ACCESS_COMMERCIAL_SPACE \
46 + | HTTP_ACCESS_ANONYMOUS_DATA \
47 + | HTTP_ACCESS_SENSITIVE_DATA \
48 + | HTTP_ACCESS_VIEW_AGENT_CONFIG \
49 + | HTTP_ACCESS_EDIT_AGENT_CONFIG \
50 + | HTTP_ACCESS_VIEW_NOTIFICATIONS_CONFIG \
51 + | HTTP_ACCESS_EDIT_NOTIFICATIONS_CONFIG \
52 + | HTTP_ACCESS_VIEW_ALERTS_SILENCING \
53 + | HTTP_ACCESS_EDIT_ALERTS_SILENCING \
54 +)
55 +
56 +#define HTTP_ACCESS_MAP_OLD_ANY (HTTP_ACCESS)(HTTP_ACCESS_ANONYMOUS_DATA)
57 +
58 +#define HTTP_ACCESS_MAP_OLD_MEMBER (HTTP_ACCESS)( \
59 + HTTP_ACCESS_SIGNED_ID \
60 + | HTTP_ACCESS_SAME_SPACE \
61 + | HTTP_ACCESS_ANONYMOUS_DATA | HTTP_ACCESS_SENSITIVE_DATA)
62 +
63 +#define HTTP_ACCESS_MAP_OLD_ADMIN (HTTP_ACCESS)( \
64 + HTTP_ACCESS_SIGNED_ID \
65 + | HTTP_ACCESS_SAME_SPACE \
66 + | HTTP_ACCESS_ANONYMOUS_DATA | HTTP_ACCESS_SENSITIVE_DATA | HTTP_ACCESS_VIEW_AGENT_CONFIG \
67 + | HTTP_ACCESS_EDIT_AGENT_CONFIG \
68 +)
69 +
70 +HTTP_ACCESS http_access2id_one(const char *str);
71 +HTTP_ACCESS http_access2id(char *str);
72 +struct web_buffer;
73 +void http_access2buffer_json_array(struct web_buffer *wb, const char *key, HTTP_ACCESS access);
74 +void http_access2txt(char *buf, size_t size, const char *separator, HTTP_ACCESS access);
75 +HTTP_ACCESS http_access_from_hex(const char *str);
76 +HTTP_ACCESS http_access_from_hex_mapping_old_roles(const char *str);
77 +HTTP_ACCESS http_access_from_source(const char *str);
78 +bool log_cb_http_access_to_hex(struct web_buffer *wb, void *data);
79 +
80 +#define HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(access) ((access & HTTP_ACCESS_SIGNED_ID) ? HTTP_RESP_FORBIDDEN : HTTP_RESP_PRECOND_FAIL)
81
82 typedef enum __attribute__((packed)) {
83 HTTP_ACL_NONE = (0),
20 - HTTP_ACL_NOCHECK = (1 << 0), // Don't check anything - this should work on all channels
21 - HTTP_ACL_DASHBOARD = (1 << 1),
22 - HTTP_ACL_REGISTRY = (1 << 2),
23 - HTTP_ACL_BADGE = (1 << 3),
24 - HTTP_ACL_MGMT = (1 << 4),
25 - HTTP_ACL_STREAMING = (1 << 5),
26 - HTTP_ACL_NETDATACONF = (1 << 6),
27 - HTTP_ACL_SSL_OPTIONAL = (1 << 7),
28 - HTTP_ACL_SSL_FORCE = (1 << 8),
29 - HTTP_ACL_SSL_DEFAULT = (1 << 9),
30 - HTTP_ACL_ACLK = (1 << 10),
31 - HTTP_ACL_WEBRTC = (1 << 11),
32 - HTTP_ACL_BEARER_IF_PROTECTED = (1 << 12), // allow unprotected access if bearer is not enabled in netdata
33 - HTTP_ACL_BEARER_REQUIRED = (1 << 13), // allow access only if a valid bearer is used
34 - HTTP_ACL_BEARER_OPTIONAL = (1 << 14), // the call may or may not need a bearer - will be determined later
84 +
85 + HTTP_ACL_NOCHECK = (1 << 0), // Don't check anything - adding this to an endpoint, disables ACL checking
86 +
87 + // transports
88 + HTTP_ACL_API = (1 << 1), // from the internal web server (TCP port)
89 + HTTP_ACL_API_UDP = (1 << 2), // from the internal web server (UDP port)
90 + HTTP_ACL_API_UNIX = (1 << 3), // from the internal web server (UNIX socket)
91 + HTTP_ACL_H2O = (1 << 4), // from the h2o web server
92 + HTTP_ACL_ACLK = (1 << 5), // from ACLK
93 + HTTP_ACL_WEBRTC = (1 << 6), // from WebRTC
94 +
95 + // HTTP_ACL_API takes the following additional ACLs, based on pattern matching of the client IP
96 + HTTP_ACL_DASHBOARD = (1 << 10),
97 + HTTP_ACL_REGISTRY = (1 << 11),
98 + HTTP_ACL_BADGES = (1 << 12),
99 + HTTP_ACL_MANAGEMENT = (1 << 13),
100 + HTTP_ACL_STREAMING = (1 << 14),
101 + HTTP_ACL_NETDATACONF = (1 << 15),
102 +
103 + // SSL related
104 + HTTP_ACL_SSL_OPTIONAL = (1 << 28),
105 + HTTP_ACL_SSL_FORCE = (1 << 29),
106 + HTTP_ACL_SSL_DEFAULT = (1 << 30),
107 } HTTP_ACL;
108
37 -#define HTTP_ACL_DASHBOARD_ACLK_WEBRTC (HTTP_ACL_DASHBOARD | HTTP_ACL_ACLK | HTTP_ACL_WEBRTC | HTTP_ACL_BEARER_IF_PROTECTED)
38 -#define HTTP_ACL_ACLK_WEBRTC_DASHBOARD_WITH_OPTIONAL_BEARER (HTTP_ACL_DASHBOARD | HTTP_ACL_ACLK | HTTP_ACL_WEBRTC | HTTP_ACL_BEARER_OPTIONAL)
109 +#define HTTP_ACL_TRANSPORTS (HTTP_ACL)( \
110 + HTTP_ACL_API \
111 + | HTTP_ACL_API_UDP \
112 + | HTTP_ACL_API_UNIX \
113 + | HTTP_ACL_H2O \
114 + | HTTP_ACL_ACLK \
115 + | HTTP_ACL_WEBRTC \
116 +)
117 +
118 +#define HTTP_ACL_TRANSPORTS_WITHOUT_CLIENT_IP_VALIDATION (HTTP_ACL)( \
119 + HTTP_ACL_ACLK \
120 + | HTTP_ACL_WEBRTC \
121 +)
122 +
123 +#define HTTP_ACL_ALL_FEATURES (HTTP_ACL)( \
124 + HTTP_ACL_DASHBOARD \
125 + | HTTP_ACL_REGISTRY \
126 + | HTTP_ACL_BADGES \
127 + | HTTP_ACL_MANAGEMENT \
128 + | HTTP_ACL_STREAMING \
129 + | HTTP_ACL_NETDATACONF \
130 +)
131
132 #ifdef NETDATA_DEV_MODE
133 #define ACL_DEV_OPEN_ACCESS HTTP_ACL_NOCHECK
@@ -45,8 +137,8 @@ typedef enum __attribute__((packed)) {
137
138 #define http_can_access_dashboard(w) ((w)->acl & HTTP_ACL_DASHBOARD)
139 #define http_can_access_registry(w) ((w)->acl & HTTP_ACL_REGISTRY)
48 -#define http_can_access_badges(w) ((w)->acl & HTTP_ACL_BADGE)
49 -#define http_can_access_mgmt(w) ((w)->acl & HTTP_ACL_MGMT)
140 +#define http_can_access_badges(w) ((w)->acl & HTTP_ACL_BADGES)
141 +#define http_can_access_mgmt(w) ((w)->acl & HTTP_ACL_MANAGEMENT)
142 #define http_can_access_stream(w) ((w)->acl & HTTP_ACL_STREAMING)
143 #define http_can_access_netdataconf(w) ((w)->acl & HTTP_ACL_NETDATACONF)
144 #define http_is_using_ssl_optional(w) ((w)->port_acl & HTTP_ACL_SSL_OPTIONAL)
libnetdata/http/http_defs.h
+6 -3
@@ -23,13 +23,16 @@
23
24 // HTTP_CODES 4XX Client Errors
25 #define HTTP_RESP_BAD_REQUEST 400
26 -#define HTTP_RESP_UNAUTHORIZED 401
27 -#define HTTP_RESP_FORBIDDEN 403
26 #define HTTP_RESP_NOT_FOUND 404
27 #define HTTP_RESP_METHOD_NOT_ALLOWED 405
28 #define HTTP_RESP_CONFLICT 409
31 -#define HTTP_RESP_PRECOND_FAIL 412
29 #define HTTP_RESP_CONTENT_TOO_LONG 413
30 +
31 +#define HTTP_RESP_UNAUTHORIZED 401 // do not use 401 when responding to users - it is used by authenticating proxies
32 +#define HTTP_RESP_FORBIDDEN 403 // not enough permissions to access this resource
33 +#define HTTP_RESP_PRECOND_FAIL 412 // An authorization bearer is required by it was not found in the request
34 +#define HTTP_RESP_UNAVAILABLE_FOR_LEGAL_REASONS 451 // Unavailable For Legal Reasons, we use it instead of 403 when access is forbidden due to an ACL.
35 +
36 #define HTTP_RESP_CLIENT_CLOSED_REQUEST 499 // nginx's enxtension to the standard
37
38 // HTTP_CODES 5XX Server Errors
libnetdata/log/log.c
+4
@@ -1128,6 +1128,10 @@ static __thread struct log_field thread_log_fields[_NDF_MAX] = {
1128 .journal = "ND_USER_ROLE",
1129 .logfmt = "role",
1130 },
1131 + [NDF_USER_ACCESS] = {
1132 + .journal = "ND_USER_PERMISSIONS",
1133 + .logfmt = "permissions",
1134 + },
1135 [NDF_SRC_IP] = {
1136 .journal = "ND_SRC_IP",
1137 .logfmt = "src_ip",
libnetdata/log/log.h
+1
@@ -67,6 +67,7 @@ typedef enum __attribute__((__packed__)) {
67 NDF_ACCOUNT_ID,
68 NDF_USER_NAME,
69 NDF_USER_ROLE,
70 + NDF_USER_ACCESS,
71
72 // web server and stream receiver
73 NDF_SRC_IP, // the streaming / web server source IP
libnetdata/socket/socket.c
+7 -4
@@ -528,8 +528,8 @@ HTTP_ACL read_acl(char *st) {
528
529 if (!strcmp(st,"dashboard")) ret |= HTTP_ACL_DASHBOARD;
530 if (!strcmp(st,"registry")) ret |= HTTP_ACL_REGISTRY;
531 - if (!strcmp(st,"badges")) ret |= HTTP_ACL_BADGE;
532 - if (!strcmp(st,"management")) ret |= HTTP_ACL_MGMT;
531 + if (!strcmp(st,"badges")) ret |= HTTP_ACL_BADGES;
532 + if (!strcmp(st,"management")) ret |= HTTP_ACL_MANAGEMENT;
533 if (!strcmp(st,"streaming")) ret |= HTTP_ACL_STREAMING;
534 if (!strcmp(st,"netdata.conf")) ret |= HTTP_ACL_NETDATACONF;
535
@@ -559,12 +559,14 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
559 protocol = IPPROTO_TCP;
560 socktype = SOCK_STREAM;
561 protocol_str = "tcp";
562 + acl_flags |= HTTP_ACL_API;
563 }
564 else if(strncmp(ip, "udp:", 4) == 0) {
565 ip += 4;
566 protocol = IPPROTO_UDP;
567 socktype = SOCK_DGRAM;
568 protocol_str = "udp";
569 + acl_flags |= HTTP_ACL_API_UDP;
570 }
571 else if(strncmp(ip, "unix:", 5) == 0) {
572 char *path = ip + 5;
@@ -578,7 +580,8 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
580
581 sockets->failed++;
582 } else {
581 - acl_flags = HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGE | HTTP_ACL_MGMT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT;
583 + acl_flags = HTTP_ACL_API_UNIX | HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES |
584 + HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT;
585 listen_sockets_add(sockets, fd, AF_UNIX, socktype, protocol_str, path, 0, acl_flags);
586 added++;
587 }
@@ -628,7 +631,7 @@ static inline int bind_to_this(LISTEN_SOCKETS *sockets, const char *definition,
631 }
632 acl_flags |= read_acl(portconfig);
633 } else {
631 - acl_flags = HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGE | HTTP_ACL_MGMT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT;
634 + acl_flags |= HTTP_ACL_DASHBOARD | HTTP_ACL_REGISTRY | HTTP_ACL_BADGES | HTTP_ACL_MANAGEMENT | HTTP_ACL_NETDATACONF | HTTP_ACL_STREAMING | HTTP_ACL_SSL_DEFAULT;
635 }
636
637 //Case the user does not set the option SSL in the "bind to", but he has
logsmanagement/functions.c
+3 -2
@@ -162,7 +162,7 @@ typedef struct function_query_status {
162
163 static void logsmanagement_function_facets(const char *transaction, char *function,
164 usec_t *stop_monotonic_ut, bool *cancelled,
165 - BUFFER *payload __maybe_unused,
165 + BUFFER *payload __maybe_unused, HTTP_ACCESS access __maybe_unused,
166 const char *src __maybe_unused, void *data __maybe_unused){
167
168 struct rusage start, end;
@@ -701,10 +701,11 @@ struct functions_evloop_globals *logsmanagement_func_facets_init(bool *p_logsman
701 used_hashes_registry = dictionary_create(DICT_OPTION_DONT_OVERWRITE_VALUE);
702
703 netdata_mutex_lock(&stdout_mut);
704 - fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" \"members\" %d\n",
704 + fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " GLOBAL \"%s\" %d \"%s\" \"logs\" "HTTP_ACCESS_FORMAT" %d\n",
705 LOGS_MANAG_FUNC_NAME,
706 LOGS_MANAG_QUERY_TIMEOUT_DEFAULT,
707 FUNCTION_LOGSMANAGEMENT_HELP_SHORT,
708 + (HTTP_ACCESS_FORMAT_CAST)(HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_SENSITIVE_DATA),
709 RRDFUNCTIONS_PRIORITY_DEFAULT + 1);
710 netdata_mutex_unlock(&stdout_mut);
711
registry/registry.c
+4 -4
@@ -257,7 +257,7 @@ int registry_request_access_json(RRDHOST *host, struct web_client *w, char *pers
257 registry_json_header(host, w, "access", REGISTRY_STATUS_FAILED);
258 registry_json_footer(w);
259 registry_unlock();
260 - return HTTP_RESP_PRECOND_FAIL;
260 + return HTTP_RESP_INTERNAL_SERVER_ERROR;
261 }
262
263 // set the cookie
@@ -299,7 +299,7 @@ int registry_request_delete_json(RRDHOST *host, struct web_client *w, char *pers
299 registry_json_header(host, w, "delete", REGISTRY_STATUS_FAILED);
300 registry_json_footer(w);
301 registry_unlock();
302 - return HTTP_RESP_PRECOND_FAIL;
302 + return HTTP_RESP_BAD_REQUEST;
303 }
304
305 // generate the response
@@ -320,7 +320,7 @@ int registry_request_search_json(RRDHOST *host, struct web_client *w, char *pers
320 if(!person_guid || !person_guid[0]) {
321 registry_json_header(host, w, "search", REGISTRY_STATUS_FAILED);
322 registry_json_footer(w);
323 - return HTTP_RESP_PRECOND_FAIL;
323 + return HTTP_RESP_BAD_REQUEST;
324 }
325
326 registry_lock();
@@ -362,7 +362,7 @@ int registry_request_switch_json(RRDHOST *host, struct web_client *w, char *pers
362 if(!person_guid || !person_guid[0]) {
363 buffer_flush(w->response.data);
364 buffer_strcat(w->response.data, "Who are you? Person GUID is missing");
365 - return HTTP_RESP_PRECOND_FAIL;
365 + return HTTP_RESP_BAD_REQUEST;
366 }
367
368 if(!registry_is_valid_url(url)) {
streaming/rrdpush.h
+1
@@ -272,6 +272,7 @@ struct sender_state {
272 const char *transaction;
273 const char *timeout_s;
274 const char *function;
275 + const char *access;
276 const char *source;
277 BUFFER *payload;
278 } functions;
streaming/sender.c
+22 -14
@@ -1160,7 +1160,7 @@ static void stream_execute_function_progress_callback(void *data, size_t done, s
1160 }
1161 }
1162
1163 -static void execute_commands_function(struct sender_state *s, const char *command, const char *transaction, const char *timeout_s, const char *function, BUFFER *payload, const char *source) {
1163 +static void execute_commands_function(struct sender_state *s, const char *command, const char *transaction, const char *timeout_s, const char *function, BUFFER *payload, const char *access, const char *source) {
1164 worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST);
1165 nd_log(NDLS_ACCESS, NDLP_INFO, NULL);
1166
@@ -1182,8 +1182,8 @@ static void execute_commands_function(struct sender_state *s, const char *comman
1182 tmp->transaction = string_strdupz(transaction);
1183 BUFFER *wb = buffer_create(1024, &netdata_buffers_statistics.buffers_functions);
1184
1185 - int code = rrd_function_run(s->host, wb,
1186 - timeout,HTTP_ACCESS_ADMIN, function, false, transaction,
1185 + int code = rrd_function_run(s->host, wb, timeout,
1186 + http_access_from_hex_mapping_old_roles(access), function, false, transaction,
1187 stream_execute_function_callback, tmp,
1188 stream_has_capability(s, STREAM_CAP_PROGRESS) ? stream_execute_function_progress_callback : NULL,
1189 stream_has_capability(s, STREAM_CAP_PROGRESS) ? tmp : NULL,
@@ -1200,6 +1200,7 @@ static void cleanup_intercepting_input(struct sender_state *s) {
1200 freez((void *)s->functions.transaction);
1201 freez((void *)s->functions.timeout_s);
1202 freez((void *)s->functions.function);
1203 + freez((void *)s->functions.access);
1204 freez((void *)s->functions.source);
1205 buffer_free(s->functions.payload);
1206
@@ -1207,6 +1208,8 @@ static void cleanup_intercepting_input(struct sender_state *s) {
1208 s->functions.timeout_s = NULL;
1209 s->functions.function = NULL;
1210 s->functions.payload = NULL;
1211 + s->functions.access = NULL;
1212 + s->functions.source = NULL;
1213 s->functions.intercept_input = false;
1214 }
1215
@@ -1230,10 +1233,12 @@ void execute_commands(struct sender_state *s) {
1233 s->line.count++;
1234
1235 if(s->functions.intercept_input) {
1233 - if(strcmp(start, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END "\n") == 0) {
1234 - execute_commands_function(s, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD_END,
1236 + if(strcmp(start, PLUGINSD_CALL_FUNCTION_PAYLOAD_END "\n") == 0) {
1237 + execute_commands_function(s,
1238 + PLUGINSD_CALL_FUNCTION_PAYLOAD_END,
1239 s->functions.transaction, s->functions.timeout_s,
1236 - s->functions.function, s->functions.payload, s->functions.source);
1240 + s->functions.function, s->functions.payload,
1241 + s->functions.access, s->functions.source);
1242
1243 cleanup_intercepting_input(s);
1244 }
@@ -1248,30 +1253,33 @@ void execute_commands(struct sender_state *s) {
1253 s->line.num_words = quoted_strings_splitter_pluginsd(start, s->line.words, PLUGINSD_MAX_WORDS);
1254 const char *command = get_word(s->line.words, s->line.num_words, 0);
1255
1251 - if(command && strcmp(command, PLUGINSD_KEYWORD_FUNCTION) == 0) {
1256 + if(command && strcmp(command, PLUGINSD_CALL_FUNCTION) == 0) {
1257 char *transaction = get_word(s->line.words, s->line.num_words, 1);
1258 char *timeout_s = get_word(s->line.words, s->line.num_words, 2);
1259 char *function = get_word(s->line.words, s->line.num_words, 3);
1255 - char *source = get_word(s->line.words, s->line.num_words, 4);
1260 + char *access = get_word(s->line.words, s->line.num_words, 4);
1261 + char *source = get_word(s->line.words, s->line.num_words, 5);
1262
1257 - execute_commands_function(s, command, transaction, timeout_s, function, NULL, source);
1263 + execute_commands_function(s, command, transaction, timeout_s, function, NULL, access, source);
1264 }
1259 - else if(command && strcmp(command, PLUGINSD_KEYWORD_FUNCTION_PAYLOAD) == 0) {
1265 + else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_PAYLOAD_BEGIN) == 0) {
1266 char *transaction = get_word(s->line.words, s->line.num_words, 1);
1267 char *timeout_s = get_word(s->line.words, s->line.num_words, 2);
1268 char *function = get_word(s->line.words, s->line.num_words, 3);
1263 - char *source = get_word(s->line.words, s->line.num_words, 4);
1264 - char *content_type = get_word(s->line.words, s->line.num_words, 5);
1269 + char *access = get_word(s->line.words, s->line.num_words, 4);
1270 + char *source = get_word(s->line.words, s->line.num_words, 5);
1271 + char *content_type = get_word(s->line.words, s->line.num_words, 6);
1272
1273 s->functions.transaction = strdupz(transaction ? transaction : "");
1274 s->functions.timeout_s = strdupz(timeout_s ? timeout_s : "");
1275 s->functions.function = strdupz(function ? function : "");
1276 + s->functions.access = strdupz(access ? access : "");
1277 s->functions.source = strdupz(source ? source : "");
1278 s->functions.payload = buffer_create(0, NULL);
1279 s->functions.payload->content_type = content_type_string2id(content_type);
1280 s->functions.intercept_input = true;
1281 }
1274 - else if(command && strcmp(command, PLUGINSD_KEYWORD_FUNCTION_CANCEL) == 0) {
1282 + else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_CANCEL) == 0) {
1283 worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST);
1284 nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL);
1285
@@ -1279,7 +1287,7 @@ void execute_commands(struct sender_state *s) {
1287 if(transaction && *transaction)
1288 rrd_function_cancel(transaction);
1289 }
1282 - else if(command && strcmp(command, PLUGINSD_KEYWORD_FUNCTION_PROGRESS) == 0) {
1290 + else if(command && strcmp(command, PLUGINSD_CALL_FUNCTION_PROGRESS) == 0) {
1291 worker_is_busy(WORKER_SENDER_JOB_FUNCTION_REQUEST);
1292 nd_log(NDLS_ACCESS, NDLP_DEBUG, NULL);
1293
web/api/http_auth.c
+3 -4
@@ -11,6 +11,7 @@ struct bearer_token {
11 uuid_t cloud_account_id;
12 char cloud_user_name[CLOUD_USER_NAME_LENGTH];
13 HTTP_ACCESS access;
14 + HTTP_USER_ROLE user_role;
15 time_t created_s;
16 time_t expires_s;
17 };
@@ -22,12 +23,9 @@ bool web_client_bearer_token_auth(struct web_client *w, const char *v) {
23
24 struct bearer_token *z = dictionary_get(netdata_authorized_bearers, uuid_str);
25 if (z && z->expires_s > now_monotonic_sec()) {
25 - w->access = z->access;
26 strncpyz(w->auth.client_name, z->cloud_user_name, sizeof(w->auth.client_name) - 1);
27 uuid_copy(w->auth.cloud_account_id, z->cloud_account_id);
28 -
29 - web_client_flags_clear_auth(w);
30 - web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_BEARER);
28 + web_client_set_permissions(w, z->access, z->user_role, WEB_CLIENT_FLAG_AUTH_BEARER);
29 return true;
30 }
31 }
@@ -72,6 +70,7 @@ time_t bearer_create_token(uuid_t *uuid, struct web_client *w) {
70 if(!z->created_s) {
71 z->created_s = now_monotonic_sec();
72 z->expires_s = z->created_s + BEARER_TOKEN_EXPIRATION;
73 + z->user_role = w->user_role;
74 z->access = w->access;
75 uuid_copy(z->cloud_account_id, w->auth.cloud_account_id);
76 strncpyz(z->cloud_user_name, w->auth.client_name, sizeof(z->cloud_account_id) - 1);
web/api/http_auth.h
+2 -2
@@ -14,8 +14,8 @@ bool extract_bearer_token_from_request(struct web_client *w, char *dst, size_t d
14 time_t bearer_create_token(uuid_t *uuid, struct web_client *w);
15 bool web_client_bearer_token_auth(struct web_client *w, const char *v);
16
17 -static inline bool web_client_has_enough_access_level(HTTP_ACCESS user_level, HTTP_ACCESS endpoint_level) {
18 - return user_level != HTTP_ACCESS_NONE && user_level <= endpoint_level;
17 +static inline bool http_access_user_has_enough_access_level_for_endpoint(HTTP_ACCESS user, HTTP_ACCESS endpoint) {
18 + return ((user & endpoint) == endpoint);
19 }
20
21 #endif //NETDATA_HTTP_AUTH_H
web/api/http_header.c
+32 -27
@@ -122,7 +122,7 @@ static void http_header_x_transaction_id(struct web_client *w, const char *v, si
122 }
123
124 static void http_header_x_netdata_account_id(struct web_client *w, const char *v, size_t len) {
125 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl == HTTP_ACL_ACLK) {
125 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
126 char buffer[UUID_STR_LEN * 2];
127 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
128 uuid_parse_flexi(buffer, w->auth.cloud_account_id); // will not alter w->cloud_account_id if it fails
@@ -130,37 +130,41 @@ static void http_header_x_netdata_account_id(struct web_client *w, const char *v
130 }
131
132 static void http_header_x_netdata_role(struct web_client *w, const char *v, size_t len) {
133 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl == HTTP_ACL_ACLK) {
133 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
134 char buffer[100];
135 strncpyz(buffer, v, (len < sizeof(buffer) - 1 ? len : sizeof(buffer) - 1));
136 if (strcasecmp(buffer, "admin") == 0)
137 - w->access = HTTP_ACCESS_ADMIN;
137 + w->user_role = HTTP_USER_ROLE_ADMIN;
138 else if(strcasecmp(buffer, "manager") == 0)
139 - w->access = HTTP_ACCESS_MEMBER;
139 + w->user_role = HTTP_USER_ROLE_MANAGER;
140 else if(strcasecmp(buffer, "troubleshooter") == 0)
141 - w->access = HTTP_ACCESS_MEMBER;
141 + w->user_role = HTTP_USER_ROLE_TROUBLESHOOTER;
142 else if(strcasecmp(buffer, "observer") == 0)
143 - w->access = HTTP_ACCESS_MEMBER;
143 + w->user_role = HTTP_USER_ROLE_OBSERVER;
144 else if(strcasecmp(buffer, "member") == 0)
145 - w->access = HTTP_ACCESS_MEMBER;
145 + w->user_role = HTTP_USER_ROLE_MEMBER;
146 else if(strcasecmp(buffer, "billing") == 0)
147 - w->access = HTTP_ACCESS_MEMBER;
147 + w->user_role = HTTP_USER_ROLE_BILLING;
148 else
149 - w->access = HTTP_ACCESS_MEMBER;
149 + w->user_role = HTTP_USER_ROLE_MEMBER;
150 + }
151 +}
152
151 - web_client_flags_clear_auth(w);
152 - web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_CLOUD);
153 +static void http_header_x_netdata_permissions(struct web_client *w, const char *v, size_t len __maybe_unused) {
154 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
155 + HTTP_ACCESS access = http_access_from_hex(v);
156 + web_client_set_permissions(w, access, w->user_role, WEB_CLIENT_FLAG_AUTH_CLOUD);
157 }
158 }
159
160 static void http_header_x_netdata_user_name(struct web_client *w, const char *v, size_t len) {
157 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl == HTTP_ACL_ACLK) {
161 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK) {
162 strncpyz(w->auth.client_name, v, (len < sizeof(w->auth.client_name) - 1 ? len : sizeof(w->auth.client_name) - 1));
163 }
164 }
165
166 static void http_header_x_netdata_auth(struct web_client *w, const char *v, size_t len __maybe_unused) {
163 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl == HTTP_ACL_ACLK)
167 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD) && w->acl & HTTP_ACL_ACLK)
168 // we don't need authorization bearer when the request comes from netdata cloud
169 return;
170
@@ -176,20 +180,21 @@ struct {
180 const char *key;
181 void (*cb)(struct web_client *w, const char *value, size_t value_len);
182 } supported_headers[] = {
179 - { .hash = 0, .key = "Origin", .cb = http_header_origin },
180 - { .hash = 0, .key = "Connection", .cb = http_header_connection },
181 - { .hash = 0, .key = "DNT", .cb = http_header_dnt },
182 - { .hash = 0, .key = "User-Agent", .cb = http_header_user_agent},
183 - { .hash = 0, .key = "X-Auth-Token", .cb = http_header_x_auth_token },
184 - { .hash = 0, .key = "Host", .cb = http_header_host },
185 - { .hash = 0, .key = "Accept-Encoding", .cb = http_header_accept_encoding },
186 - { .hash = 0, .key = "X-Forwarded-Host", .cb = http_header_x_forwarded_host },
187 - { .hash = 0, .key = "X-Forwarded-For", .cb = http_header_x_forwarded_for },
188 - { .hash = 0, .key = "X-Transaction-Id", .cb = http_header_x_transaction_id },
189 - { .hash = 0, .key = "X-Netdata-Account-Id", .cb = http_header_x_netdata_account_id },
190 - { .hash = 0, .key = "X-Netdata-Role", .cb = http_header_x_netdata_role },
191 - { .hash = 0, .key = "X-Netdata-User-Name", .cb = http_header_x_netdata_user_name },
192 - { .hash = 0, .key = "X-Netdata-Auth", .cb = http_header_x_netdata_auth },
183 + { .hash = 0, .key = "Origin", .cb = http_header_origin },
184 + { .hash = 0, .key = "Connection", .cb = http_header_connection },
185 + { .hash = 0, .key = "DNT", .cb = http_header_dnt },
186 + { .hash = 0, .key = "User-Agent", .cb = http_header_user_agent},
187 + { .hash = 0, .key = "X-Auth-Token", .cb = http_header_x_auth_token },
188 + { .hash = 0, .key = "Host", .cb = http_header_host },
189 + { .hash = 0, .key = "Accept-Encoding", .cb = http_header_accept_encoding },
190 + { .hash = 0, .key = "X-Forwarded-Host", .cb = http_header_x_forwarded_host },
191 + { .hash = 0, .key = "X-Forwarded-For", .cb = http_header_x_forwarded_for },
192 + { .hash = 0, .key = "X-Transaction-Id", .cb = http_header_x_transaction_id },
193 + { .hash = 0, .key = "X-Netdata-Account-Id", .cb = http_header_x_netdata_account_id },
194 + { .hash = 0, .key = "X-Netdata-Role", .cb = http_header_x_netdata_role },
195 + { .hash = 0, .key = "X-Netdata-Permissions", .cb = http_header_x_netdata_permissions },
196 + { .hash = 0, .key = "X-Netdata-User-Name", .cb = http_header_x_netdata_user_name },
197 + { .hash = 0, .key = "X-Netdata-Auth", .cb = http_header_x_netdata_auth },
198
199 // for historical reasons.
200 // there are a few nightly versions of netdata UI that incorrectly use this instead of X-Netdata-Auth
web/api/web_api.c
+26 -7
@@ -3,15 +3,29 @@
3 #include "web_api.h"
4
5 int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_path_endpoint, struct web_api_command *api_commands) {
6 - if(!web_client_flags_check_auth(w))
7 - w->access = HTTP_ACCESS_ANY;
6 + buffer_no_cacheable(w->response.data);
7 +
8 + internal_fatal(web_client_flags_check_auth(w) && !(w->access & HTTP_ACCESS_SIGNED_ID),
9 + "signed-in permission should be set, but is missing");
10 +
11 + internal_fatal(!web_client_flags_check_auth(w) && (w->access & HTTP_ACCESS_SIGNED_ID),
12 + "signed-in permission is set, but it shouldn't");
13 +
14 + if(!web_client_flags_check_auth(w)) {
15 + w->user_role = (netdata_is_protected_by_bearer) ? HTTP_USER_ROLE_NONE : HTTP_USER_ROLE_ANY;
16 + w->access = (netdata_is_protected_by_bearer) ? HTTP_ACCESS_NONE : HTTP_ACCESS_ANONYMOUS_DATA;
17 + }
18
19 #ifdef NETDATA_GOD_MODE
20 web_client_flag_set(w, WEB_CLIENT_FLAG_AUTH_GOD);
11 - w->access = HTTP_ACCESS_ADMIN;
21 + w->user_role = HTTP_USER_ROLE_ADMIN;
22 + w->access = HTTP_ACCESS_ALL;
23 #endif
24
14 - buffer_no_cacheable(w->response.data);
25 + if((w->access & HTTP_ACCESS_SIGNED_ID) && !(w->access & HTTP_ACCESS_SAME_SPACE)) {
26 + // this should never happen: a signed-in user from a different space
27 + return web_client_permission_denied(w);
28 + }
29
30 if(unlikely(!url_path_endpoint || !*url_path_endpoint)) {
31 buffer_flush(w->response.data);
@@ -30,8 +44,8 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
44
45 uint32_t hash = simple_hash(api_command);
46
33 - for(int i = 0; api_commands[i].command ; i++) {
34 - if(unlikely(hash == api_commands[i].hash && !strcmp(api_command, api_commands[i].command))) {
47 + for(int i = 0; api_commands[i].api ; i++) {
48 + if(unlikely(hash == api_commands[i].hash && !strcmp(api_command, api_commands[i].api))) {
49 if(unlikely(!api_commands[i].allow_subpaths && api_command != url_path_endpoint)) {
50 buffer_flush(w->response.data);
51 buffer_sprintf(w->response.data, "API command '%s' does not support subpaths.", api_command);
@@ -42,8 +56,13 @@ int web_client_api_request_vX(RRDHOST *host, struct web_client *w, char *url_pat
56 if (api_command != url_path_endpoint)
57 freez(api_command);
58
45 - bool acl_allows = (w->acl & api_commands[i].acl) || (api_commands[i].acl & HTTP_ACL_NOCHECK);
59 + bool acl_allows = ((w->acl & api_commands[i].acl) == api_commands[i].acl) || (api_commands[i].acl & HTTP_ACL_NOCHECK);
60 if(!acl_allows)
61 + return web_client_permission_denied_acl(w);
62 +
63 + bool permissions_allows =
64 + http_access_user_has_enough_access_level_for_endpoint(w->access, api_commands[i].access);
65 + if(!permissions_allows)
66 return web_client_permission_denied(w);
67
68 char *query_string = (char *)buffer_tostring(w->url_query_string_decoded);
web/api/web_api.h
+2 -1
@@ -12,9 +12,10 @@
12 #include "web/api/queries/weights.h"
13
14 struct web_api_command {
15 - const char *command;
15 + const char *api;
16 uint32_t hash;
17 HTTP_ACL acl;
18 + HTTP_ACCESS access;
19 int (*callback)(RRDHOST *host, struct web_client *w, char *url);
20 unsigned int allow_subpaths;
21 };
web/api/web_api_v1.c
+235 -61
@@ -1077,12 +1077,12 @@ inline int web_client_api_request_v1_registry(RRDHOST *host, struct web_client *
1077 // HELLO request, dashboard ACL
1078 analytics_log_dashboard();
1079 if(unlikely(!http_can_access_dashboard(w)))
1080 - return web_client_permission_denied(w);
1080 + return web_client_permission_denied_acl(w);
1081 }
1082 else {
1083 // everything else, registry ACL
1084 if(unlikely(!http_can_access_registry(w)))
1085 - return web_client_permission_denied(w);
1085 + return web_client_permission_denied_acl(w);
1086
1087 if(unlikely(do_not_track)) {
1088 buffer_flush(w->response.data);
@@ -1383,21 +1383,6 @@ int web_client_api_request_v1_ml_info(RRDHOST *host, struct web_client *w, char
1383
1384 return HTTP_RESP_OK;
1385 }
1386 -
1387 -int web_client_api_request_v1_ml_models(RRDHOST *host, struct web_client *w, char *url) {
1388 - (void) url;
1389 -
1390 - if (!netdata_ready)
1391 - return HTTP_RESP_SERVICE_UNAVAILABLE;
1392 -
1393 - BUFFER *wb = w->response.data;
1394 - buffer_flush(wb);
1395 - wb->content_type = CT_APPLICATION_JSON;
1396 - ml_host_get_models(host, wb);
1397 - buffer_no_cacheable(wb);
1398 -
1399 - return HTTP_RESP_OK;
1400 -}
1386 #endif // ENABLE_ML
1387
1388 inline int web_client_api_request_v1_info(RRDHOST *host, struct web_client *w, char *url) {
@@ -1509,17 +1494,21 @@ void web_client_source2buffer(struct web_client *w, BUFFER *source) {
1494 else
1495 buffer_sprintf(source, "method=api");
1496
1512 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD) || web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER)) {
1513 - buffer_sprintf(source, ",role=%s", http_id2access(w->access));
1497 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
1498 + buffer_strcat(source, ",role=god");
1499 + else
1500 + buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
1501 +
1502 + buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
1503
1504 + if(w->auth.client_name[0])
1505 + buffer_sprintf(source, ",user=%s", w->auth.client_name);
1506 +
1507 + if(!uuid_is_null(w->auth.cloud_account_id)) {
1508 char uuid_str[UUID_COMPACT_STR_LEN];
1509 uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
1517 - buffer_sprintf(source, ",user=%s,account=%s", w->auth.client_name, uuid_str);
1510 + buffer_sprintf(source, ",account=%s", uuid_str);
1511 }
1519 - else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
1520 - buffer_strcat(source, ",role=god");
1521 - else
1522 - buffer_sprintf(source, ",role=%s", http_id2access(w->access));
1512
1513 if(w->client_ip[0])
1514 buffer_sprintf(source, ",ip=%s", w->client_ip);
@@ -1717,46 +1706,231 @@ int web_client_api_request_v1_mgmt(RRDHOST *host, struct web_client *w, char *ur
1706 }
1707
1708 static struct web_api_command api_commands_v1[] = {
1720 - {"info", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_info, 0 },
1721 - {"data", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_data, 0 },
1722 - {"chart", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_chart, 0 },
1723 - {"charts", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_charts, 0 },
1724 - {"context", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_context, 0 },
1725 - {"contexts", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_contexts, 0 },
1726 -
1709 + // time-series data APIs
1710 + {
1711 + .api = "data",
1712 + .hash = 0,
1713 + .acl = HTTP_ACL_DASHBOARD,
1714 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1715 + .callback = web_client_api_request_v1_data,
1716 + .allow_subpaths = 0
1717 + },
1718 + {
1719 + .api = "weights",
1720 + .hash = 0,
1721 + .acl = HTTP_ACL_DASHBOARD,
1722 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1723 + .callback = web_client_api_request_v1_weights,
1724 + .allow_subpaths = 0
1725 + },
1726 + {
1727 + // deprecated - do not use anymore - use "weights"
1728 + .api = "metric_correlations",
1729 + .hash = 0,
1730 + .acl = HTTP_ACL_DASHBOARD,
1731 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1732 + .callback = web_client_api_request_v1_metric_correlations,
1733 + .allow_subpaths = 0
1734 + },
1735 + {
1736 + // exporting API
1737 + .api = "allmetrics",
1738 + .hash = 0,
1739 + .acl = HTTP_ACL_DASHBOARD,
1740 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1741 + .callback = web_client_api_request_v1_allmetrics,
1742 + .allow_subpaths = 0
1743 + },
1744 + {
1745 + // badges can be fetched with both dashboard and badge ACL
1746 + .api = "badge.svg",
1747 + .hash = 0,
1748 + .acl = HTTP_ACL_BADGES,
1749 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1750 + .callback = web_client_api_request_v1_badge,
1751 + .allow_subpaths = 0
1752 + },
1753 +
1754 + // alerts APIs
1755 + {
1756 + .api = "alarms",
1757 + .hash = 0,
1758 + .acl = HTTP_ACL_DASHBOARD,
1759 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1760 + .callback = web_client_api_request_v1_alarms,
1761 + .allow_subpaths = 0
1762 + },
1763 + {
1764 + .api = "alarms_values",
1765 + .hash = 0,
1766 + .acl = HTTP_ACL_DASHBOARD,
1767 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1768 + .callback = web_client_api_request_v1_alarms_values,
1769 + .allow_subpaths = 0
1770 + },
1771 + {
1772 + .api = "alarm_log",
1773 + .hash = 0,
1774 + .acl = HTTP_ACL_DASHBOARD,
1775 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1776 + .callback = web_client_api_request_v1_alarm_log,
1777 + .allow_subpaths = 0
1778 + },
1779 + {
1780 + .api = "alarm_variables",
1781 + .hash = 0,
1782 + .acl = HTTP_ACL_DASHBOARD,
1783 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1784 + .callback = web_client_api_request_v1_alarm_variables,
1785 + .allow_subpaths = 0
1786 + },
1787 + {
1788 + .api = "variable",
1789 + .hash = 0,
1790 + .acl = HTTP_ACL_DASHBOARD,
1791 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1792 + .callback = web_client_api_request_variable,
1793 + .allow_subpaths = 0
1794 + },
1795 + {
1796 + .api = "alarm_count",
1797 + .hash = 0,
1798 + .acl = HTTP_ACL_DASHBOARD,
1799 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1800 + .callback = web_client_api_request_v1_alarm_count,
1801 + .allow_subpaths = 0
1802 + },
1803 +
1804 + // functions APIs - they check permissions per function call
1805 + {
1806 + .api = "function",
1807 + .hash = 0,
1808 + .acl = HTTP_ACL_DASHBOARD,
1809 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1810 + .callback = web_client_api_request_v1_function,
1811 + .allow_subpaths = 0
1812 + },
1813 + {
1814 + .api = "functions",
1815 + .hash = 0,
1816 + .acl = HTTP_ACL_DASHBOARD,
1817 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1818 + .callback = web_client_api_request_v1_functions,
1819 + .allow_subpaths = 0
1820 + },
1821 +
1822 + // time-series metadata APIs
1823 + {
1824 + .api = "chart",
1825 + .hash = 0,
1826 + .acl = HTTP_ACL_DASHBOARD,
1827 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1828 + .callback = web_client_api_request_v1_chart,
1829 + .allow_subpaths = 0
1830 + },
1831 + {
1832 + .api = "charts",
1833 + .hash = 0,
1834 + .acl = HTTP_ACL_DASHBOARD,
1835 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1836 + .callback = web_client_api_request_v1_charts,
1837 + .allow_subpaths = 0
1838 + },
1839 + {
1840 + .api = "context",
1841 + .hash = 0,
1842 + .acl = HTTP_ACL_DASHBOARD,
1843 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1844 + .callback = web_client_api_request_v1_context,
1845 + .allow_subpaths = 0
1846 + },
1847 + {
1848 + .api = "contexts",
1849 + .hash = 0,
1850 + .acl = HTTP_ACL_DASHBOARD,
1851 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1852 + .callback = web_client_api_request_v1_contexts,
1853 + .allow_subpaths = 0
1854 + },
1855 +
1856 + // registry APIs
1857 + {
1858 // registry checks the ACL by itself, so we allow everything
1728 - {"registry", 0, HTTP_ACL_NOCHECK, web_client_api_request_v1_registry, 0 },
1729 -
1730 - // badges can be fetched with both dashboard and badge permissions
1731 - {"badge.svg", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC | HTTP_ACL_BADGE, web_client_api_request_v1_badge, 0 },
1732 -
1733 - {"alarms", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms, 0 },
1734 - {"alarms_values", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarms_values, 0 },
1735 - {"alarm_log", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_log, 0 },
1736 - {"alarm_variables", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_variables, 0 },
1737 - {"variable", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_variable, 0 },
1738 - {"alarm_count", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_alarm_count, 0 },
1739 - {"allmetrics", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_allmetrics, 0 },
1859 + .api = "registry",
1860 + .hash = 0,
1861 + .acl = HTTP_ACL_NONE, // it manages acl by itself
1862 + .access = HTTP_ACCESS_NONE, // it manages access by itself
1863 + .callback = web_client_api_request_v1_registry,
1864 + .allow_subpaths = 0
1865 + },
1866 +
1867 + // agent information APIs
1868 + {
1869 + .api = "info",
1870 + .hash = 0,
1871 + .acl = HTTP_ACL_DASHBOARD,
1872 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1873 + .callback = web_client_api_request_v1_info,
1874 + .allow_subpaths = 0
1875 + },
1876 + {
1877 + .api = "aclk",
1878 + .hash = 0,
1879 + .acl = HTTP_ACL_DASHBOARD,
1880 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1881 + .callback = web_client_api_request_v1_aclk_state,
1882 + .allow_subpaths = 0
1883 + },
1884 + {
1885 + // deprecated - use /api/v2/info
1886 + .api = "dbengine_stats",
1887 + .hash = 0,
1888 + .acl = HTTP_ACL_DASHBOARD,
1889 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1890 + .callback = web_client_api_request_v1_dbengine_stats,
1891 + .allow_subpaths = 0
1892 + },
1893 +
1894 + // dyncfg APIs
1895 + {
1896 + .api = "config",
1897 + .hash = 0,
1898 + .acl = HTTP_ACL_DASHBOARD,
1899 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1900 + .callback = web_client_api_request_v1_config,
1901 + .allow_subpaths = 0
1902 + },
1903
1904 #if defined(ENABLE_ML)
1742 - {"ml_info", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_ml_info, 0 },
1743 - // { "ml_models", 0, HTTP_ACL_DASHBOARD, web_client_api_request_v1_ml_models },
1905 + {
1906 + .api = "ml_info",
1907 + .hash = 0,
1908 + .acl = HTTP_ACL_DASHBOARD,
1909 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
1910 + .callback = web_client_api_request_v1_ml_info,
1911 + .allow_subpaths = 0
1912 + },
1913 #endif
1914
1746 - {"manage", 0, HTTP_ACL_MGMT | HTTP_ACL_ACLK, web_client_api_request_v1_mgmt, 1 },
1747 - {"aclk", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_aclk_state, 0 },
1748 - {"metric_correlations", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_metric_correlations, 0 },
1749 - {"weights", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_weights, 0 },
1750 -
1751 - {"function", 0, HTTP_ACL_ACLK_WEBRTC_DASHBOARD_WITH_OPTIONAL_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_function, 0 },
1752 - {"functions", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_functions, 0 },
1753 -
1754 - {"config", 0, HTTP_ACL_ACLK_WEBRTC_DASHBOARD_WITH_OPTIONAL_BEARER | ACL_DEV_OPEN_ACCESS, web_client_api_request_v1_config, 0 },
1755 -
1756 - {"dbengine_stats", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v1_dbengine_stats, 0 },
1757 -
1758 - // terminator
1759 - {NULL, 0, HTTP_ACL_NONE, NULL, 0 },
1915 + {
1916 + // deprecated
1917 + .api = "manage",
1918 + .hash = 0,
1919 + .acl = HTTP_ACL_MANAGEMENT,
1920 + .access = HTTP_ACCESS_NONE, // it manages access by itself
1921 + .callback = web_client_api_request_v1_mgmt,
1922 + .allow_subpaths = 1
1923 + },
1924 +
1925 + {
1926 + // terminator - keep this last on this list
1927 + .api = NULL,
1928 + .hash = 0,
1929 + .acl = HTTP_ACL_NONE,
1930 + .access = HTTP_ACCESS_NONE,
1931 + .callback = NULL,
1932 + .allow_subpaths = 0
1933 + },
1934 };
1935
1936 inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
@@ -1765,8 +1939,8 @@ inline int web_client_api_request_v1(RRDHOST *host, struct web_client *w, char *
1939 if(unlikely(initialized == 0)) {
1940 initialized = 1;
1941
1768 - for(int i = 0; api_commands_v1[i].command ; i++)
1769 - api_commands_v1[i].hash = simple_hash(api_commands_v1[i].command);
1942 + for(int i = 0; api_commands_v1[i].api ; i++)
1943 + api_commands_v1[i].hash = simple_hash(api_commands_v1[i].api);
1944 }
1945
1946 return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v1);
web/api/web_api_v2.c
+171 -28
@@ -584,33 +584,176 @@ static int web_client_api_request_v2_progress(RRDHOST *host __maybe_unused, stru
584 }
585
586 static struct web_api_command api_commands_v2[] = {
587 - {"info", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_info, 0},
588 -
589 - {"data", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_data, 0},
590 - {"weights", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_weights, 0},
591 -
592 - {"contexts", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_contexts, 0},
593 - {"nodes", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_nodes, 0},
594 - {"node_instances", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_node_instances, 0},
595 - {"versions", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_versions, 0},
596 - {"functions", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_functions, 0},
597 - {"q", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_q, 0},
598 - {"alerts", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alerts, 0},
599 -
600 - {"alert_transitions", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_transitions, 0},
601 - {"alert_config", 0, HTTP_ACL_DASHBOARD_ACLK_WEBRTC, web_client_api_request_v2_alert_config, 0},
602 -
603 - {"claim", 0, HTTP_ACL_NOCHECK, web_client_api_request_v2_claim, 0},
604 -
605 - {"rtc_offer", 0, HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS, web_client_api_request_v2_webrtc, 0},
606 - {"bearer_protection", 0, HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_protection, 0},
607 - {"bearer_get_token", 0, HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS, api_v2_bearer_token, 0},
608 -
609 - { "ilove.svg", 0, HTTP_ACL_NOCHECK, web_client_api_request_v2_ilove, 0 },
610 - { "progress", 0, HTTP_ACL_NOCHECK, web_client_api_request_v2_progress, 0 },
611 -
587 + // time-series multi-node multi-instance data APIs
588 + {
589 + .api = "data",
590 + .hash = 0,
591 + .acl = HTTP_ACL_DASHBOARD,
592 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
593 + .callback = web_client_api_request_v2_data,
594 + .allow_subpaths = 0
595 + },
596 + {
597 + .api = "weights",
598 + .hash = 0,
599 + .acl = HTTP_ACL_DASHBOARD,
600 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
601 + .callback = web_client_api_request_v2_weights,
602 + .allow_subpaths = 0
603 + },
604 +
605 + // time-series multi-node multi-instance metadata APIs
606 + {
607 + .api = "contexts",
608 + .hash = 0,
609 + .acl = HTTP_ACL_DASHBOARD,
610 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
611 + .callback = web_client_api_request_v2_contexts,
612 + .allow_subpaths = 0
613 + },
614 + {
615 + // full text search
616 + .api = "q",
617 + .hash = 0,
618 + .acl = HTTP_ACL_DASHBOARD,
619 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
620 + .callback = web_client_api_request_v2_q,
621 + .allow_subpaths = 0
622 + },
623 +
624 + // multi-node multi-instance alerts APIs
625 + {
626 + .api = "alerts",
627 + .hash = 0,
628 + .acl = HTTP_ACL_DASHBOARD,
629 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
630 + .callback = web_client_api_request_v2_alerts,
631 + .allow_subpaths = 0
632 + },
633 + {
634 + .api = "alert_transitions",
635 + .hash = 0,
636 + .acl = HTTP_ACL_DASHBOARD,
637 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
638 + .callback = web_client_api_request_v2_alert_transitions,
639 + .allow_subpaths = 0
640 + },
641 + {
642 + .api = "alert_config",
643 + .hash = 0,
644 + .acl = HTTP_ACL_DASHBOARD,
645 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
646 + .callback = web_client_api_request_v2_alert_config,
647 + .allow_subpaths = 0
648 + },
649 +
650 + // agent information APIs
651 + {
652 + .api = "info",
653 + .hash = 0,
654 + .acl = HTTP_ACL_DASHBOARD,
655 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
656 + .callback = web_client_api_request_v2_info,
657 + .allow_subpaths = 0
658 + },
659 + {
660 + .api = "nodes",
661 + .hash = 0,
662 + .acl = HTTP_ACL_DASHBOARD,
663 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
664 + .callback = web_client_api_request_v2_nodes,
665 + .allow_subpaths = 0
666 + },
667 + {
668 + .api = "node_instances",
669 + .hash = 0,
670 + .acl = HTTP_ACL_DASHBOARD,
671 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
672 + .callback = web_client_api_request_v2_node_instances,
673 + .allow_subpaths = 0
674 + },
675 + {
676 + .api = "versions",
677 + .hash = 0,
678 + .acl = HTTP_ACL_DASHBOARD,
679 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
680 + .callback = web_client_api_request_v2_versions,
681 + .allow_subpaths = 0
682 + },
683 + {
684 + .api = "progress",
685 + .hash = 0,
686 + .acl = HTTP_ACL_DASHBOARD,
687 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
688 + .callback = web_client_api_request_v2_progress,
689 + .allow_subpaths = 0
690 + },
691 +
692 + // functions APIs
693 + {
694 + .api = "functions",
695 + .hash = 0,
696 + .acl = HTTP_ACL_DASHBOARD,
697 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
698 + .callback = web_client_api_request_v2_functions,
699 + .allow_subpaths = 0
700 + },
701 +
702 + // WebRTC APIs
703 + {
704 + .api = "rtc_offer",
705 + .hash = 0,
706 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
707 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
708 + .callback = web_client_api_request_v2_webrtc,
709 + .allow_subpaths = 0
710 + },
711 +
712 + // management APIs
713 + {
714 + .api = "claim",
715 + .hash = 0,
716 + .acl = HTTP_ACL_NOCHECK,
717 + .access = HTTP_ACCESS_NONE,
718 + .callback = web_client_api_request_v2_claim,
719 + .allow_subpaths = 0
720 + },
721 + {
722 + .api = "bearer_protection",
723 + .hash = 0,
724 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
725 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE | HTTP_ACCESS_VIEW_AGENT_CONFIG | HTTP_ACCESS_EDIT_AGENT_CONFIG,
726 + .callback = api_v2_bearer_protection,
727 + .allow_subpaths = 0
728 + },
729 + {
730 + .api = "bearer_get_token",
731 + .hash = 0,
732 + .acl = HTTP_ACL_ACLK | ACL_DEV_OPEN_ACCESS,
733 + .access = HTTP_ACCESS_SIGNED_ID | HTTP_ACCESS_SAME_SPACE,
734 + .callback = api_v2_bearer_token,
735 + .allow_subpaths = 0
736 + },
737 +
738 + // Netdata branding APIs
739 + {
740 + .api = "ilove.svg",
741 + .hash = 0,
742 + .acl = HTTP_ACL_DASHBOARD,
743 + .access = HTTP_ACCESS_ANONYMOUS_DATA,
744 + .callback = web_client_api_request_v2_ilove,
745 + .allow_subpaths = 0
746 + },
747 +
748 + {
749 // terminator
613 - {NULL, 0, HTTP_ACL_NONE, NULL, 0},
750 + .api = NULL,
751 + .hash = 0,
752 + .acl = HTTP_ACL_NONE,
753 + .access = HTTP_ACCESS_NONE,
754 + .callback = NULL,
755 + .allow_subpaths = 0
756 + },
757 };
758
759 inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *url_path_endpoint) {
@@ -619,8 +762,8 @@ inline int web_client_api_request_v2(RRDHOST *host, struct web_client *w, char *
762 if(unlikely(initialized == 0)) {
763 initialized = 1;
764
622 - for(int i = 0; api_commands_v2[i].command ; i++)
623 - api_commands_v2[i].hash = simple_hash(api_commands_v2[i].command);
765 + for(int i = 0; api_commands_v2[i].api ; i++)
766 + api_commands_v2[i].hash = simple_hash(api_commands_v2[i].api);
767 }
768
769 return web_client_api_request_vX(host, w, url_path_endpoint, api_commands_v2);
web/rtc/webrtc.c
+4 -3
@@ -248,7 +248,7 @@ static size_t webrtc_send_in_chunks(WEBRTC_DC *chan, const char *data, size_t si
248 message_size,
249 chunk,
250 total_chunks,
251 - web_content_type_to_string(content_type)
251 + content_type_id2string(content_type)
252 );
253
254 internal_fatal((size_t)len != strlen(send_buffer), "WEBRTC compressed header line mismatch");
@@ -294,7 +294,8 @@ static void webrtc_execute_api_request(WEBRTC_DC *chan, const char *request, siz
294 w->interrupt.callback_data = chan;
295 web_client_set_conn_webrtc(w);
296
297 - w->acl = HTTP_ACL_WEBRTC;
297 + w->port_acl = HTTP_ACL_WEBRTC | HTTP_ACL_ALL_FEATURES;
298 + w->acl = w->port_acl;
299
300 char *path = (char *)request;
301 if(strncmp(request, "POST ", 5) == 0) {
@@ -504,7 +505,7 @@ static void cleanupConnections() {
505 spinlock_unlock(&webrtc_base.unsafe.spinlock);
506 }
507
507 -static WEBRTC_CONN *webrtc_create_connection(void) {
508 +static WEBRTC_CONN * webrtc_create_connection(void) {
509 WEBRTC_CONN *conn = callocz(1, sizeof(WEBRTC_CONN));
510
511 spinlock_init(&conn->response.spinlock);
web/server/h2o/http_server.c
+3 -1
@@ -204,6 +204,7 @@ static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
204 if (!api_command.len)
205 return 1;
206
207 + // TODO - get a web_client from the cache
208 // this (emulating struct web_client) is a hack and will be removed
209 // in future PRs but needs bigger changes in old http_api_v1
210 // we need to make the web_client_api_request_v1 to be web server
@@ -216,7 +217,8 @@ static inline int _netdata_uberhandler(h2o_req_t *req, RRDHOST **host)
217 w.response.header = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
218 w.url_query_string_decoded = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
219 w.url_as_received = buffer_create(NBUF_INITIAL_SIZE_RESP, NULL);
219 - w.acl = HTTP_ACL_DASHBOARD;
220 + w.port_acl = HTTP_ACL_H2O | HTTP_ACL_ALL_FEATURES;
221 + w.acl = w.port_acl; // TODO - web_client_update_acl_matches(w) to restrict this based on user configuration
222
223 char *path_c_str = iovec_to_cstr(&api_command);
224 char *path_unescaped = url_unescape(path_c_str);
web/server/web_client.c
+53 -42
@@ -30,20 +30,47 @@ void web_client_set_conn_webrtc(struct web_client *w) {
30 web_client_flag_set(w, WEB_CLIENT_FLAG_CONN_WEBRTC);
31 }
32
33 +void web_client_reset_permissions(struct web_client *w) {
34 + web_client_flags_clear_auth(w);
35 + w->access = HTTP_ACCESS_NONE;
36 +}
37 +
38 +void web_client_set_permissions(struct web_client *w, HTTP_ACCESS access, HTTP_USER_ROLE role, WEB_CLIENT_FLAGS auth) {
39 + web_client_reset_permissions(w);
40 + web_client_flag_set(w, auth & WEB_CLIENT_FLAG_ALL_AUTHS);
41 + w->access = access;
42 + w->user_role = role;
43 +}
44 +
45 +inline int web_client_permission_denied_acl(struct web_client *w) {
46 + w->response.data->content_type = CT_TEXT_PLAIN;
47 + buffer_flush(w->response.data);
48 + buffer_strcat(w->response.data, "You need to be authorized to access this resource");
49 + w->response.code = HTTP_RESP_UNAVAILABLE_FOR_LEGAL_REASONS;
50 + return HTTP_RESP_UNAVAILABLE_FOR_LEGAL_REASONS;
51 +}
52 +
53 inline int web_client_permission_denied(struct web_client *w) {
54 w->response.data->content_type = CT_TEXT_PLAIN;
55 buffer_flush(w->response.data);
36 - buffer_strcat(w->response.data, "You are not allowed to access this resource.");
37 - w->response.code = HTTP_RESP_FORBIDDEN;
38 - return HTTP_RESP_FORBIDDEN;
56 +
57 + if(w->access & HTTP_ACCESS_SIGNED_ID)
58 + buffer_strcat(w->response.data,
59 + "You don't have enough permissions to access this resource");
60 + else
61 + buffer_strcat(w->response.data,
62 + "You need to be authorized to access this resource");
63 +
64 + w->response.code = HTTP_ACCESS_PERMISSION_DENIED_HTTP_CODE(w->access);
65 + return w->response.code;
66 }
67
41 -inline int web_client_bearer_required(struct web_client *w) {
68 +inline int web_client_service_unavailable(struct web_client *w) {
69 w->response.data->content_type = CT_TEXT_PLAIN;
70 buffer_flush(w->response.data);
44 - buffer_strcat(w->response.data, "An authorization bearer is required to access the resource.");
45 - w->response.code = HTTP_RESP_PRECOND_FAIL;
46 - return HTTP_RESP_PRECOND_FAIL;
71 + buffer_strcat(w->response.data, "This service is currently unavailable.");
72 + w->response.code = HTTP_RESP_SERVICE_UNAVAILABLE;
73 + return HTTP_RESP_SERVICE_UNAVAILABLE;
74 }
75
76 static inline int bad_request_multiple_dashboard_versions(struct web_client *w) {
@@ -184,7 +211,7 @@ static void web_client_reset_allocations(struct web_client *w, bool free_all) {
211 }
212
213 memset(w->transaction, 0, sizeof(w->transaction));
187 - web_client_flags_clear_auth(w);
214 + web_client_reset_permissions(w);
215 web_client_flag_clear(w, WEB_CLIENT_ENCODING_GZIP|WEB_CLIENT_ENCODING_DEFLATE);
216 web_client_reset_path_flags(w);
217 }
@@ -225,7 +252,8 @@ void web_client_log_completed_request(struct web_client *w, bool update_web_stat
252 ND_LOG_FIELD_TXT(NDF_SRC_FORWARDED_FOR, w->forwarded_for),
253 ND_LOG_FIELD_UUID(NDF_ACCOUNT_ID, &w->auth.cloud_account_id),
254 ND_LOG_FIELD_TXT(NDF_USER_NAME, w->auth.client_name),
228 - ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2access(w->access)),
255 + ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2user_role(w->user_role)),
256 + ND_LOG_FIELD_CB(NDF_USER_ACCESS, log_cb_http_access_to_hex, &w->access),
257 ND_LOG_FIELD_END(),
258 };
259 ND_LOG_STACK_PUSH(lgs);
@@ -443,7 +471,7 @@ static int mysendfile(struct web_client *w, char *filename) {
471 netdata_log_debug(D_WEB_CLIENT, "%llu: Looking for file '%s/%s'", w->id, netdata_configured_web_dir, filename);
472
473 if(!http_can_access_dashboard(w))
446 - return web_client_permission_denied(w);
474 + return web_client_permission_denied_acl(w);
475
476 // skip leading slashes
477 while (*filename == '/') filename++;
@@ -597,29 +625,9 @@ void buffer_data_options2string(BUFFER *wb, uint32_t options) {
625 }
626
627 static inline int check_host_and_call(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) {
600 - //if(unlikely(host->rrd_memory_mode == RRD_MEMORY_MODE_NONE)) {
601 - // buffer_flush(w->response.data);
602 - // buffer_strcat(w->response.data, "This host does not maintain a database");
603 - // return HTTP_RESP_BAD_REQUEST;
604 - //}
605 -
628 return func(host, w, url);
629 }
630
609 -static inline int UNUSED_FUNCTION(check_host_and_dashboard_acl_and_call)(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) {
610 - if(!http_can_access_dashboard(w))
611 - return web_client_permission_denied(w);
612 -
613 - return check_host_and_call(host, w, url, func);
614 -}
615 -
616 -static inline int UNUSED_FUNCTION(check_host_and_mgmt_acl_and_call)(RRDHOST *host, struct web_client *w, char *url, int (*func)(RRDHOST *, struct web_client *, char *)) {
617 - if(!http_can_access_mgmt(w))
618 - return web_client_permission_denied(w);
619 -
620 - return check_host_and_call(host, w, url, func);
621 -}
622 -
631 int web_client_api_request(RRDHOST *host, struct web_client *w, char *url_path_fragment) {
632 ND_LOG_STACK lgs[] = {
633 ND_LOG_FIELD_TXT(NDF_SRC_IP, w->client_ip),
@@ -633,7 +641,8 @@ int web_client_api_request(RRDHOST *host, struct web_client *w, char *url_path_f
641 ND_LOG_FIELD_UUID(NDF_TRANSACTION_ID, &w->transaction),
642 ND_LOG_FIELD_UUID(NDF_ACCOUNT_ID, &w->auth.cloud_account_id),
643 ND_LOG_FIELD_TXT(NDF_USER_NAME, w->auth.client_name),
636 - ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2access(w->access)),
644 + ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2user_role(w->user_role)),
645 + ND_LOG_FIELD_CB(NDF_USER_ACCESS, log_cb_http_access_to_hex, &w->access),
646 ND_LOG_FIELD_END(),
647 };
648 ND_LOG_STACK_PUSH(lgs);
@@ -1167,7 +1176,8 @@ int web_client_api_request_with_node_selection(RRDHOST *host, struct web_client
1176 ND_LOG_FIELD_UUID(NDF_TRANSACTION_ID, &w->transaction),
1177 ND_LOG_FIELD_UUID(NDF_ACCOUNT_ID, &w->auth.cloud_account_id),
1178 ND_LOG_FIELD_TXT(NDF_USER_NAME, w->auth.client_name),
1170 - ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2access(w->access)),
1179 + ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2user_role(w->user_role)),
1180 + ND_LOG_FIELD_CB(NDF_USER_ACCESS, log_cb_http_access_to_hex, &w->access),
1181 ND_LOG_FIELD_END(),
1182 };
1183 ND_LOG_STACK_PUSH(lgs);
@@ -1211,7 +1221,7 @@ int web_client_api_request_with_node_selection(RRDHOST *host, struct web_client
1221
1222 static inline int web_client_process_url(RRDHOST *host, struct web_client *w, char *decoded_url_path) {
1223 if(unlikely(!service_running(ABILITY_WEB_REQUESTS)))
1214 - return web_client_permission_denied(w);
1224 + return web_client_service_unavailable(w);
1225
1226 static uint32_t
1227 hash_api = 0,
@@ -1278,7 +1288,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1288 }
1289 else if(unlikely(hash == hash_netdata_conf && strcmp(tok, "netdata.conf") == 0)) { // netdata.conf
1290 if(unlikely(!http_can_access_netdataconf(w)))
1281 - return web_client_permission_denied(w);
1291 + return web_client_permission_denied_acl(w);
1292
1293 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: generating netdata.conf ...", w->id);
1294 w->response.data->content_type = CT_TEXT_PLAIN;
@@ -1289,7 +1299,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1299 #ifdef NETDATA_INTERNAL_CHECKS
1300 else if(unlikely(hash == hash_exit && strcmp(tok, "exit") == 0)) {
1301 if(unlikely(!http_can_access_netdataconf(w)))
1292 - return web_client_permission_denied(w);
1302 + return web_client_permission_denied_acl(w);
1303
1304 w->response.data->content_type = CT_TEXT_PLAIN;
1305 buffer_flush(w->response.data);
@@ -1305,7 +1315,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1315 }
1316 else if(unlikely(hash == hash_debug && strcmp(tok, "debug") == 0)) {
1317 if(unlikely(!http_can_access_netdataconf(w)))
1308 - return web_client_permission_denied(w);
1318 + return web_client_permission_denied_acl(w);
1319
1320 buffer_flush(w->response.data);
1321
@@ -1345,7 +1355,7 @@ static inline int web_client_process_url(RRDHOST *host, struct web_client *w, ch
1355 }
1356 else if(unlikely(hash == hash_mirror && strcmp(tok, "mirror") == 0)) {
1357 if(unlikely(!http_can_access_netdataconf(w)))
1348 - return web_client_permission_denied(w);
1358 + return web_client_permission_denied_acl(w);
1359
1360 netdata_log_debug(D_WEB_CLIENT_ACCESS, "%llu: Mirroring...", w->id);
1361
@@ -1393,7 +1403,8 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1403 ND_LOG_FIELD_UUID(NDF_TRANSACTION_ID, &w->transaction),
1404 ND_LOG_FIELD_UUID(NDF_ACCOUNT_ID, &w->auth.cloud_account_id),
1405 ND_LOG_FIELD_TXT(NDF_USER_NAME, w->auth.client_name),
1396 - ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2access(w->access)),
1406 + ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2user_role(w->user_role)),
1407 + ND_LOG_FIELD_CB(NDF_USER_ACCESS, log_cb_http_access_to_hex, &w->access),
1408 ND_LOG_FIELD_END(),
1409 };
1410 ND_LOG_STACK_PUSH(lgs);
@@ -1418,7 +1429,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1429 switch(w->mode) {
1430 case HTTP_REQUEST_MODE_STREAM:
1431 if(unlikely(!http_can_access_stream(w))) {
1421 - web_client_permission_denied(w);
1432 + web_client_permission_denied_acl(w);
1433 return;
1434 }
1435
@@ -1433,7 +1444,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1444 !http_can_access_mgmt(w) &&
1445 !http_can_access_netdataconf(w)
1446 )) {
1436 - web_client_permission_denied(w);
1447 + web_client_permission_denied_acl(w);
1448 break;
1449 }
1450
@@ -1455,7 +1466,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1466 !http_can_access_mgmt(w) &&
1467 !http_can_access_netdataconf(w)
1468 )) {
1458 - web_client_permission_denied(w);
1469 + web_client_permission_denied_acl(w);
1470 break;
1471 }
1472
@@ -1489,7 +1500,7 @@ void web_client_process_request_from_web_server(struct web_client *w) {
1500 break;
1501
1502 default:
1492 - web_client_permission_denied(w);
1503 + web_client_permission_denied_acl(w);
1504 return;
1505 }
1506 break;
web/server/web_client.h
+12 -5
@@ -113,9 +113,13 @@ typedef enum __attribute__((packed)) {
113 #define web_client_check_conn_cloud(w) web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_CLOUD)
114 #define web_client_check_conn_webrtc(w) web_client_flag_check(w, WEB_CLIENT_FLAG_CONN_WEBRTC)
115
116 +#define WEB_CLIENT_FLAG_ALL_AUTHS (WEB_CLIENT_FLAG_AUTH_CLOUD | WEB_CLIENT_FLAG_AUTH_BEARER)
117 #define web_client_flags_clear_conn(w) web_client_flag_clear(w, WEB_CLIENT_FLAG_CONN_TCP | WEB_CLIENT_FLAG_CONN_UNIX | WEB_CLIENT_FLAG_CONN_CLOUD | WEB_CLIENT_FLAG_CONN_WEBRTC)
117 -#define web_client_flags_check_auth(w) web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD | WEB_CLIENT_FLAG_AUTH_BEARER)
118 -#define web_client_flags_clear_auth(w) web_client_flag_clear(w, WEB_CLIENT_FLAG_AUTH_CLOUD | WEB_CLIENT_FLAG_AUTH_BEARER)
118 +#define web_client_flags_check_auth(w) web_client_flag_check(w, WEB_CLIENT_FLAG_ALL_AUTHS)
119 +#define web_client_flags_clear_auth(w) web_client_flag_clear(w, WEB_CLIENT_FLAG_ALL_AUTHS)
120 +
121 +void web_client_reset_permissions(struct web_client *w);
122 +void web_client_set_permissions(struct web_client *w, HTTP_ACCESS access, HTTP_USER_ROLE role, WEB_CLIENT_FLAGS auth);
123
124 void web_client_set_conn_tcp(struct web_client *w);
125 void web_client_set_conn_unix(struct web_client *w);
@@ -166,8 +170,9 @@ struct web_client {
170 WEB_CLIENT_FLAGS flags; // status flags for the client
171 HTTP_REQUEST_MODE mode; // the operational mode of the client
172 HTTP_ACL acl; // the access list of the client
169 - HTTP_ACCESS access; // the access level of the client
170 - int port_acl; // the operations permitted on the port the client connected to
173 + HTTP_ACL port_acl; // the operations permitted on the port the client connected to
174 + HTTP_ACCESS access; // the access permissions of the client
175 + HTTP_USER_ROLE user_role; // the user role of the client
176 size_t header_parse_tries;
177 size_t header_parse_last_size;
178
@@ -234,7 +239,9 @@ struct web_client {
239 };
240
241 int web_client_permission_denied(struct web_client *w);
237 -int web_client_bearer_required(struct web_client *w);
242 +int web_client_permission_denied_acl(struct web_client *w);
243 +
244 +int web_client_service_unavailable(struct web_client *w);
245
246 ssize_t web_client_send(struct web_client *w);
247 ssize_t web_client_receive(struct web_client *w);
web/server/web_client_cache.c
+3
@@ -114,7 +114,10 @@ struct web_client *web_client_get_from_cache(void) {
114
115 // initialize it
116 w->use_count++;
117 + w->port_acl = HTTP_ACL_NONE;
118 + w->acl = HTTP_ACL_NONE;
119 w->mode = HTTP_REQUEST_MODE_GET;
120 + web_client_reset_permissions(w);
121 memset(w->transaction, 0, sizeof(w->transaction));
122
123 return w;
web/server/web_server.c
+35 -33
@@ -44,8 +44,8 @@ void debug_sockets() {
44 buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_NOCHECK) ? "NONE " : "");
45 buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_DASHBOARD) ? "dashboard " : "");
46 buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_REGISTRY) ? "registry " : "");
47 - buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_BADGE) ? "badges " : "");
48 - buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_MGMT) ? "management " : "");
47 + buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_BADGES) ? "badges " : "");
48 + buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_MANAGEMENT) ? "management " : "");
49 buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_STREAMING) ? "streaming " : "");
50 buffer_strcat(wb, (api_sockets.fds_acl_flags[i] & HTTP_ACL_NETDATACONF) ? "netdata.conf " : "");
51 netdata_log_debug(D_WEB_CLIENT, "Socket fd %d name '%s' acl_flags: %s",
@@ -91,37 +91,39 @@ SIMPLE_PATTERN *web_allow_netdataconf_from = NULL;
91 int web_allow_netdataconf_dns;
92
93 void web_client_update_acl_matches(struct web_client *w) {
94 - w->acl = HTTP_ACL_NONE;
95 -
96 - if (!web_allow_dashboard_from ||
97 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
98 - web_allow_dashboard_from, "dashboard", web_allow_dashboard_dns))
99 - w->acl |= HTTP_ACL_DASHBOARD;
100 -
101 - if (!web_allow_registry_from ||
102 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
103 - web_allow_registry_from, "registry", web_allow_registry_dns))
104 - w->acl |= HTTP_ACL_REGISTRY;
105 -
106 - if (!web_allow_badges_from ||
107 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
108 - web_allow_badges_from, "badges", web_allow_badges_dns))
109 - w->acl |= HTTP_ACL_BADGE;
110 -
111 - if (!web_allow_mgmt_from ||
112 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
113 - web_allow_mgmt_from, "management", web_allow_mgmt_dns))
114 - w->acl |= HTTP_ACL_MGMT;
115 -
116 - if (!web_allow_streaming_from ||
117 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
118 - web_allow_streaming_from, "streaming", web_allow_streaming_dns))
119 - w->acl |= HTTP_ACL_STREAMING;
120 -
121 - if (!web_allow_netdataconf_from ||
122 - connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
123 - web_allow_netdataconf_from, "netdata.conf", web_allow_netdataconf_dns))
124 - w->acl |= HTTP_ACL_NETDATACONF;
94 + w->acl = HTTP_ACL_TRANSPORTS;
95 +
96 + if(!(w->port_acl & HTTP_ACL_TRANSPORTS_WITHOUT_CLIENT_IP_VALIDATION)) {
97 + if (!web_allow_dashboard_from ||
98 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
99 + web_allow_dashboard_from, "dashboard", web_allow_dashboard_dns))
100 + w->acl |= HTTP_ACL_DASHBOARD;
101 +
102 + if (!web_allow_registry_from ||
103 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
104 + web_allow_registry_from, "registry", web_allow_registry_dns))
105 + w->acl |= HTTP_ACL_REGISTRY;
106 +
107 + if (!web_allow_badges_from ||
108 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
109 + web_allow_badges_from, "badges", web_allow_badges_dns))
110 + w->acl |= HTTP_ACL_BADGES;
111 +
112 + if (!web_allow_mgmt_from ||
113 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
114 + web_allow_mgmt_from, "management", web_allow_mgmt_dns))
115 + w->acl |= HTTP_ACL_MANAGEMENT;
116 +
117 + if (!web_allow_streaming_from ||
118 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
119 + web_allow_streaming_from, "streaming", web_allow_streaming_dns))
120 + w->acl |= HTTP_ACL_STREAMING;
121 +
122 + if (!web_allow_netdataconf_from ||
123 + connection_allowed(w->ifd, w->client_ip, w->client_host, sizeof(w->client_host),
124 + web_allow_netdataconf_from, "netdata.conf", web_allow_netdataconf_dns))
125 + w->acl |= HTTP_ACL_NETDATACONF;
126 + }
127
128 w->acl &= w->port_acl;
129 }