@cryptotaxi247 / netdata-1 / commits / fb561f84a

Dyncfg users actions log (#19674)

* log all DYNCFG user actions that alter data * do not show sensitive user info when the user is not logged in * log template actions * add DYNFG message id to annotations --------- Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud>

Costa Tsaousis committed Feb 19, 2025 at 13:29 UTC fb561f84ab7a39b31a560a1f48d6a4492cedc2dc
11 files changed +264 -56
CMakeLists.txt
+2
@@ -1674,6 +1674,8 @@ set(WEB_PLUGIN_FILES
1674 src/web/api/queries/backfill.h
1675 src/web/api/functions/function-metrics-cardinality.c
1676 src/web/api/functions/function-metrics-cardinality.h
1677 + src/web/api/request_source.c
1678 + src/web/api/request_source.h
1679 )
1680
1681 set(CLAIM_PLUGIN_FILES
src/collectors/systemd-journal.plugin/systemd-journal-annotations.c
+1
@@ -619,6 +619,7 @@ static void netdata_systemd_journal_message_ids_init(void) {
619 msgid_into_dict("ec87a56120d5431bace51e2fb8bba243", "Netdata log flood protection");
620 msgid_into_dict("acb33cb95778476baac702eb7e4e151d", "Netdata Cloud connection");
621 msgid_into_dict("d1f59606dd4d41e3b217a0cfcae8e632", "Netdata extreme cardinality");
622 + msgid_into_dict("4fdf40816c124623a032b7fe73beacb8", "Netdata dynamic configuration");
623 }
624
625 void netdata_systemd_journal_transform_message_id(FACETS *facets __maybe_unused, BUFFER *wb, FACETS_TRANSFORMATION_SCOPE scope __maybe_unused, void *data __maybe_unused) {
src/daemon/dyncfg/dyncfg-intercept.c
+123 -11
@@ -3,21 +3,112 @@
3 #include "dyncfg-internals.h"
4 #include "dyncfg.h"
5
6 -// ----------------------------------------------------------------------------
7 -// we intercept the config function calls of the plugin
8 -
6 struct dyncfg_call {
7 + ND_UUID transaction;
8 BUFFER *payload;
11 - char *function;
12 - char *id;
13 - char *add_name;
14 - char *source;
9 + const char *function;
10 + const char *id;
11 + const char *add_name;
12 + const char *source;
13 DYNCFG_CMDS cmd;
14 rrd_function_result_callback_t result_cb;
15 void *result_cb_data;
16 bool from_dyncfg_echo;
17 };
18
19 +// ----------------------------------------------------------------------------
20 +
21 +ENUM_STR_MAP_DEFINE(DYNCFG_CMDS) = {
22 + { DYNCFG_CMD_GET, "get" },
23 + { DYNCFG_CMD_SCHEMA, "schema" },
24 + { DYNCFG_CMD_UPDATE, "update" },
25 + { DYNCFG_CMD_ADD, "add" },
26 + { DYNCFG_CMD_TEST, "test" },
27 + { DYNCFG_CMD_REMOVE, "remove" },
28 + { DYNCFG_CMD_ENABLE, "enable" },
29 + { DYNCFG_CMD_DISABLE, "disable" },
30 + { DYNCFG_CMD_RESTART, "restart" },
31 + { DYNCFG_CMD_USERCONFIG, "userconfig" },
32 +
33 + // terminator
34 + { 0, NULL }
35 +};
36 +
37 +ENUM_STR_DEFINE_FUNCTIONS(DYNCFG_CMDS, DYNCFG_CMD_NONE, "none");
38 +
39 +static void dyncfg_log_user_action(DYNCFG *df, struct dyncfg_call *dc) {
40 + if(dc->cmd == DYNCFG_CMD_USERCONFIG || dc->cmd == DYNCFG_CMD_GET || dc->cmd == DYNCFG_CMD_SCHEMA)
41 + return;
42 +
43 + const char *type;
44 + switch(df->type) {
45 + default:
46 + case DYNCFG_TYPE_SINGLE:
47 + type = "on";
48 + break;
49 +
50 + case DYNCFG_TYPE_TEMPLATE:
51 + type = "on template";
52 + break;
53 + case DYNCFG_TYPE_JOB:
54 + type = "on job";
55 + break;
56 + }
57 +
58 + PARSED_REQUEST_SOURCE req;
59 + if(!parse_request_source(dc->source, &req)) {
60 + ND_LOG_STACK lgs[] = {
61 + ND_LOG_FIELD_TXT(NDF_MODULE, "DYNCFG"),
62 + ND_LOG_FIELD_STR(NDF_NIDL_NODE, localhost->hostname),
63 + ND_LOG_FIELD_TXT(NDF_REQUEST, dc->function),
64 + ND_LOG_FIELD_UUID(NDF_TRANSACTION_ID, &dc->transaction.uuid),
65 + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &dyncfg_user_action_msgid),
66 + ND_LOG_FIELD_END(),
67 + };
68 + ND_LOG_STACK_PUSH(lgs);
69 +
70 + nd_log(NDLS_DAEMON, NDLP_NOTICE,
71 + "DYNCFG USER ACTION '%s' %s%s%s '%s' from source: %s",
72 + DYNCFG_CMDS_2str(dc->cmd),
73 + dc->add_name ? dc->add_name : "",
74 + dc->add_name ? " " : "",
75 + type, dc->id, dc->source);
76 +
77 + return;
78 + }
79 +
80 + char access_str[1024];
81 + http_access2txt(access_str, sizeof(access_str), " ", req.access);
82 +
83 + ND_LOG_STACK lgs[] = {
84 + ND_LOG_FIELD_TXT(NDF_MODULE, "DYNCFG"),
85 + ND_LOG_FIELD_STR(NDF_NIDL_NODE, localhost->hostname),
86 + ND_LOG_FIELD_TXT(NDF_REQUEST, dc->function),
87 + ND_LOG_FIELD_UUID(NDF_TRANSACTION_ID, &dc->transaction.uuid),
88 + ND_LOG_FIELD_UUID(NDF_MESSAGE_ID, &dyncfg_user_action_msgid),
89 +
90 + ND_LOG_FIELD_UUID(NDF_ACCOUNT_ID, &req.cloud_account_id.uuid),
91 + ND_LOG_FIELD_TXT(NDF_SRC_IP, req.client_ip),
92 + ND_LOG_FIELD_TXT(NDF_SRC_FORWARDED_FOR, req.forwarded_for),
93 + ND_LOG_FIELD_TXT(NDF_USER_NAME, req.client_name),
94 + ND_LOG_FIELD_TXT(NDF_USER_ROLE, http_id2user_role(req.user_role)),
95 + ND_LOG_FIELD_CB(NDF_USER_ACCESS, log_cb_http_access_to_hex, &req.access),
96 + ND_LOG_FIELD_END(),
97 + };
98 + ND_LOG_STACK_PUSH(lgs);
99 +
100 + nd_log(NDLS_DAEMON, NDLP_NOTICE,
101 + "DYNCFG USER ACTION '%s' %s%s%s '%s' by user '%s', IP '%s'",
102 + DYNCFG_CMDS_2str(dc->cmd),
103 + dc->add_name ? dc->add_name : "",
104 + dc->add_name ? " " : "",
105 + type, dc->id, req.client_name,
106 + req.forwarded_for[0] ? req.forwarded_for : req.client_ip);
107 +}
108 +
109 +// ----------------------------------------------------------------------------
110 +// we intercept the config function calls of the plugin
111 +
112 static void dyncfg_function_intercept_job_successfully_added(DYNCFG *df_template, int code, struct dyncfg_call *dc) {
113 char id[strlen(dc->id) + 1 + strlen(dc->add_name) + 1];
114 snprintfz(id, sizeof(id), "%s:%s", dc->id, dc->add_name);
@@ -107,6 +198,8 @@ void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_d
198
199 if (save_required || old_user_disabled != df->dyncfg.user_disabled)
200 dyncfg_file_save(dc->id, df);
201 +
202 + dyncfg_log_user_action(df, dc);
203 }
204 else
205 nd_log(NDLS_DAEMON, NDLP_ERR,
@@ -125,10 +218,10 @@ void dyncfg_function_intercept_result_cb(BUFFER *wb, int code, void *result_cb_d
218 dc->result_cb(wb, code, dc->result_cb_data);
219
220 buffer_free(dc->payload);
128 - freez(dc->function);
129 - freez(dc->id);
130 - freez(dc->source);
131 - freez(dc->add_name);
221 + freez((void *)dc->function);
222 + freez((void *)dc->id);
223 + freez((void *)dc->source);
224 + freez((void *)dc->add_name);
225 freez(dc);
226 }
227
@@ -377,6 +470,24 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
470
471 if (df->dyncfg.user_disabled != old_user_disabled)
472 dyncfg_file_save(id, df);
473 +
474 + // log it
475 + {
476 + struct dyncfg_call dc = {
477 + .function = rfe->function,
478 + .id = id,
479 + .source = rfe->source,
480 + .add_name = add_name,
481 + .cmd = cmd,
482 + .result_cb = NULL,
483 + .result_cb_data = NULL,
484 + .payload = rfe->payload,
485 + .from_dyncfg_echo = called_from_dyncfg_echo,
486 + };
487 + uuid_copy(dc.transaction.uuid, *rfe->transaction);
488 +
489 + dyncfg_log_user_action(df, &dc);
490 + }
491 }
492
493 dyncfg_apply_action_on_all_template_jobs(rfe, id, cmd);
@@ -405,6 +516,7 @@ int dyncfg_function_intercept_cb(struct rrd_function_execute *rfe, void *data __
516
517 if(make_the_call_to_plugin) {
518 struct dyncfg_call *dc = callocz(1, sizeof(*dc));
519 + uuid_copy(dc->transaction.uuid, *rfe->transaction);
520 dc->function = strdupz(rfe->function);
521 dc->id = strdupz(id);
522 dc->source = rfe->source ? strdupz(rfe->source) : NULL;
src/daemon/dyncfg/dyncfg-tree.c
+6 -6
@@ -17,7 +17,7 @@ static int dyncfg_tree_compar(const void *a, const void *b) {
17 return rc;
18 }
19
20 -static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
20 +static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb, bool anonymous) {
21 buffer_json_member_add_object(wb, id);
22 {
23 buffer_json_member_add_string(wb, "type", dyncfg_id2type(df->type));
@@ -34,7 +34,7 @@ static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
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));
37 + buffer_json_member_add_string(wb, "source", df->current.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && anonymous ? "anonymous access does not present sensitive user info" : string2str(df->current.source));
38 buffer_json_member_add_boolean(wb, "sync", df->sync);
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);
@@ -45,7 +45,7 @@ static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
45 buffer_json_member_add_boolean(wb, "available", true);
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));
48 + buffer_json_member_add_string(wb, "source", df->dyncfg.source_type == DYNCFG_SOURCE_TYPE_DYNCFG && anonymous ? "anonymous access does not present sensitive user info" :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));
@@ -61,7 +61,7 @@ static void dyncfg_to_json(DYNCFG *df, const char *id, BUFFER *wb) {
61 buffer_json_object_close(wb);
62 }
63
64 -static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, const char *id) {
64 +static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, const char *id, bool anonymous) {
65 size_t entries = dictionary_entries(dyncfg_globals.nodes);
66 size_t used = 0;
67 const DICTIONARY_ITEM *items[entries];
@@ -112,7 +112,7 @@ static void dyncfg_tree_for_host(RRDHOST *host, BUFFER *wb, const char *path, co
112 buffer_json_member_add_object(wb, string2str(last_path));
113 }
114
115 - dyncfg_to_json(df, dictionary_acquired_item_name(items[i]), wb);
115 + dyncfg_to_json(df, dictionary_acquired_item_name(items[i]), wb, anonymous);
116
117 if (df->dyncfg.plugin_rejected)
118 plugin_rejected++;
@@ -195,7 +195,7 @@ static int dyncfg_config_execute_cb(struct rrd_function_execute *rfe, void *data
195 }
196
197 code = HTTP_RESP_OK;
198 - dyncfg_tree_for_host(host, rfe->result.wb, path, id);
198 + dyncfg_tree_for_host(host, rfe->result.wb, path, id, !(rfe->user_access & HTTP_ACCESS_SENSITIVE_DATA));
199 }
200 else {
201 const char *name = id;
src/libnetdata/http/http_access.c
+8 -1
@@ -166,6 +166,13 @@ HTTP_ACCESS http_access_from_hex(const char *str) {
166 return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
167 }
168
169 +HTTP_ACCESS http_access_from_hex_str(const char *str) {
170 + if (!str || !*str)
171 + return HTTP_ACCESS_NONE;
172 +
173 + return (HTTP_ACCESS)strtoull(str, NULL, 16) & HTTP_ACCESS_ALL;
174 +}
175 +
176 HTTP_ACCESS http_access_from_source(const char *str) {
177 if(!str || !*str)
178 return HTTP_ACCESS_NONE;
@@ -174,7 +181,7 @@ HTTP_ACCESS http_access_from_source(const char *str) {
181
182 const char *permissions = strstr(str, "permissions=");
183 if(permissions)
177 - access = (HTTP_ACCESS)strtoull(permissions + 12, NULL, 16) & HTTP_ACCESS_ALL;
184 + access = http_access_from_hex_str(permissions + 12);
185
186 return access;
187 }
src/libnetdata/http/http_access.h
+1
@@ -74,6 +74,7 @@ void http_access2buffer_json_array(struct web_buffer *wb, const char *key, HTTP_
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_hex_str(const char *str);
78 HTTP_ACCESS http_access_from_source(const char *str);
79 bool log_cb_http_access_to_hex(struct web_buffer *wb, void *data);
80
src/libnetdata/uuid/uuid.h
+1
@@ -37,6 +37,7 @@ ND_UUID_DEFINE(log_flood_protection_msgid, 0xec, 0x87, 0xa5, 0x61, 0x20, 0xd5, 0
37 ND_UUID_DEFINE(netdata_startup_msgid, 0x1e, 0x60, 0x61, 0xa9, 0xfb, 0xd4, 0x45, 0x01, 0xb3, 0xcc, 0xc3, 0x68, 0x11, 0x9f, 0x2b, 0x69);
38 ND_UUID_DEFINE(aclk_connection_msgid, 0xac, 0xb3, 0x3c, 0xb9, 0x57, 0x78, 0x47, 0x6b, 0xaa, 0xc7, 0x02, 0xeb, 0x7e, 0x4e, 0x15, 0x1d);
39 ND_UUID_DEFINE(extreme_cardinality_msgid, 0xd1, 0xf5, 0x96, 0x06, 0xdd, 0x4d, 0x41, 0xe3, 0xb2, 0x17, 0xa0, 0xcf, 0xca, 0xe8, 0xe6, 0x32);
40 +ND_UUID_DEFINE(dyncfg_user_action_msgid, 0x4f, 0xdf, 0x40, 0x81, 0x6c, 0x12, 0x46, 0x23, 0xa0, 0x32, 0xb7, 0xfe, 0x73, 0xbe, 0xac, 0xb8);
41
42 ND_UUID UUID_generate_from_hash(const void *payload, size_t payload_len);
43
src/web/api/request_source.c new
+97
@@ -0,0 +1,97 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "request_source.h"
4 +
5 +bool request_source_is_cloud(const char *source) {
6 + return source && *source && strstartswith(source, "method=NC,");
7 +}
8 +
9 +void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source) {
10 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
11 + buffer_sprintf(source, "method=NC");
12 + else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
13 + buffer_sprintf(source, "method=api-bearer");
14 + else
15 + buffer_sprintf(source, "method=api");
16 +
17 + if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
18 + buffer_strcat(source, ",role=god");
19 + else
20 + buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
21 +
22 + buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
23 +
24 + if(w->auth.client_name[0])
25 + buffer_sprintf(source, ",user=%s", w->auth.client_name);
26 +
27 + if(!uuid_is_null(w->auth.cloud_account_id)) {
28 + char uuid_str[UUID_COMPACT_STR_LEN];
29 + uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
30 + buffer_sprintf(source, ",account=%s", uuid_str);
31 + }
32 +
33 + if(w->client_ip[0])
34 + buffer_sprintf(source, ",ip=%s", w->client_ip);
35 +
36 + if(w->forwarded_for)
37 + buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
38 +}
39 +
40 +bool parse_request_source(const char *src, PARSED_REQUEST_SOURCE *parsed) {
41 + char *buf, *token, *saveptr;
42 +
43 + if (!src || !parsed)
44 + return false;
45 +
46 + // Initialize the structure.
47 + memset(parsed, 0, sizeof(*parsed));
48 +
49 + // Duplicate the source string because strtok_r modifies it.
50 + buf = strdupz(src);
51 + token = strtok_r(buf, ",", &saveptr);
52 + while (token) {
53 + char *equal_sign = strchr(token, '=');
54 + if (equal_sign) {
55 + *equal_sign = '\0';
56 + const char *key = token;
57 + const char *value = equal_sign + 1;
58 +
59 + if (strcmp(key, "method") == 0) {
60 + if (strcmp(value, "NC") == 0)
61 + parsed->auth |= WEB_CLIENT_FLAG_AUTH_CLOUD;
62 + else if (strcmp(value, "api-bearer") == 0)
63 + parsed->auth |= WEB_CLIENT_FLAG_AUTH_BEARER;
64 + // "api" does not add an extra flag.
65 + }
66 + else if (strcmp(key, "role") == 0) {
67 + if (strcmp(value, "god") == 0)
68 + parsed->auth |= WEB_CLIENT_FLAG_AUTH_GOD;
69 + else
70 + parsed->user_role = http_user_role2id(value);
71 + }
72 + else if (strcmp(key, "permissions") == 0)
73 + parsed->access = http_access_from_hex_str(value);
74 +
75 + else if (strcmp(key, "user") == 0) {
76 + strncpy(parsed->client_name, value, CLOUD_CLIENT_NAME_LENGTH - 1);
77 + parsed->client_name[CLOUD_CLIENT_NAME_LENGTH - 1] = '\0';
78 + }
79 + else if (strcmp(key, "account") == 0) {
80 + if (uuid_parse(value, parsed->cloud_account_id.uuid) != 0)
81 + parsed->cloud_account_id = UUID_ZERO;
82 + }
83 + else if (strcmp(key, "ip") == 0) {
84 + strncpy(parsed->client_ip, value, INET6_ADDRSTRLEN - 1);
85 + parsed->client_ip[INET6_ADDRSTRLEN - 1] = '\0';
86 + }
87 + else if (strcmp(key, "forwarded_for") == 0) {
88 + strncpy(parsed->forwarded_for, value, INET6_ADDRSTRLEN - 1);
89 + parsed->forwarded_for[INET6_ADDRSTRLEN - 1] = '\0';
90 + }
91 + }
92 + token = strtok_r(NULL, ",", &saveptr);
93 + }
94 +
95 + freez(buf);
96 + return true;
97 +}
src/web/api/request_source.h new
+24
@@ -0,0 +1,24 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_REQUEST_SOURCE_H
4 +#define NETDATA_REQUEST_SOURCE_H
5 +
6 +#include "web_api.h"
7 +
8 +typedef struct {
9 + char client_ip[INET6_ADDRSTRLEN];
10 + char forwarded_for[INET6_ADDRSTRLEN];
11 + char client_name[CLOUD_CLIENT_NAME_LENGTH];
12 + ND_UUID cloud_account_id;
13 + WEB_CLIENT_FLAGS auth;
14 + HTTP_USER_ROLE user_role;
15 + HTTP_ACCESS access;
16 +} PARSED_REQUEST_SOURCE;
17 +
18 +bool parse_request_source(const char *src, PARSED_REQUEST_SOURCE *parsed);
19 +
20 +bool request_source_is_cloud(const char *source);
21 +
22 +void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source);
23 +
24 +#endif //NETDATA_REQUEST_SOURCE_H
src/web/api/web_api.c
-36
@@ -138,42 +138,6 @@ void nd_web_api_init(void) {
138 time_grouping_init();
139 }
140
141 -
142 -bool request_source_is_cloud(const char *source) {
143 - return source && *source && strstartswith(source, "method=NC,");
144 -}
145 -
146 -void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source) {
147 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_CLOUD))
148 - buffer_sprintf(source, "method=NC");
149 - else if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_BEARER))
150 - buffer_sprintf(source, "method=api-bearer");
151 - else
152 - buffer_sprintf(source, "method=api");
153 -
154 - if(web_client_flag_check(w, WEB_CLIENT_FLAG_AUTH_GOD))
155 - buffer_strcat(source, ",role=god");
156 - else
157 - buffer_sprintf(source, ",role=%s", http_id2user_role(w->user_role));
158 -
159 - buffer_sprintf(source, ",permissions="HTTP_ACCESS_FORMAT, (HTTP_ACCESS_FORMAT_CAST)w->access);
160 -
161 - if(w->auth.client_name[0])
162 - buffer_sprintf(source, ",user=%s", w->auth.client_name);
163 -
164 - if(!uuid_is_null(w->auth.cloud_account_id)) {
165 - char uuid_str[UUID_COMPACT_STR_LEN];
166 - uuid_unparse_lower_compact(w->auth.cloud_account_id, uuid_str);
167 - buffer_sprintf(source, ",account=%s", uuid_str);
168 - }
169 -
170 - if(w->client_ip[0])
171 - buffer_sprintf(source, ",ip=%s", w->client_ip);
172 -
173 - if(w->forwarded_for)
174 - buffer_sprintf(source, ",forwarded_for=%s", w->forwarded_for);
175 -}
176 -
141 void web_client_progress_functions_update(void *data, size_t done, size_t all) {
142 // handle progress updates from the plugin
143 struct web_client *w = data;
src/web/api/web_api.h
+1 -2
@@ -16,11 +16,10 @@ struct web_client;
16 #include "web/api/http_auth.h"
17 #include "web/api/formatters/rrd2json.h"
18 #include "web/api/queries/weights.h"
19 +#include "web/api/request_source.h"
20
21 void nd_web_api_init(void);
22
22 -bool request_source_is_cloud(const char *source);
23 -void web_client_api_request_vX_source_to_buffer(struct web_client *w, BUFFER *source);
23 void web_client_progress_functions_update(void *data, size_t done, size_t all);
24
25 void host_labels2json(RRDHOST *host, BUFFER *wb, const char *key);