master
c 860 lines 30 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "aclk_otp.h"
4 #include "aclk_util.h"
5 #include "aclk.h"
6
7 static https_client_resp_t aclk_https_request(https_req_t *request, https_req_response_t *response, bool *fallback_ipv4) {
8 https_client_resp_t rc;
9 // wrapper for ACLK only which loads ACLK specific proxy settings
10 // then only calls https_request
11 struct mqtt_wss_proxy proxy_conf = { .host = NULL, .port = 0, .username = NULL, .password = NULL, .proxy_destination = NULL, .type = MQTT_WSS_DIRECT };
12 aclk_set_proxy(
13 (char **)&proxy_conf.host,
14 &proxy_conf.port,
15 (char **)&proxy_conf.username,
16 (char **)&proxy_conf.password,
17 (char **)&proxy_conf.proxy_destination,
18 &proxy_conf.type);
19
20 if (proxy_conf.type != MQTT_WSS_DIRECT) {
21 request->proxy_host = (char *)proxy_conf.host;
22 request->proxy_port = proxy_conf.port;
23 request->proxy_username = proxy_conf.username;
24 request->proxy_password = proxy_conf.password;
25 request->proxy = proxy_conf.proxy_destination;
26 request->proxy_type = proxy_conf.type;
27 }
28
29 rc = https_request(request, response, fallback_ipv4);
30 freez((char*)proxy_conf.host);
31 freez((char*)proxy_conf.username);
32 char *proxy_password = (char *)proxy_conf.password;
33 aclk_sensitive_free(&proxy_password);
34 return rc;
35 }
36
37 struct auth_data {
38 char *client_id;
39 char *username;
40 char *passwd;
41 };
42
43 #define PARSE_ENV_JSON_CHK_TYPE(it, type, name) \
44 if (json_object_get_type(json_object_iter_peek_value(it)) != type) { \
45 netdata_log_error("ACLK: value of key \"%s\" should be %s", name, #type); \
46 goto exit; \
47 }
48
49 #define JSON_KEY_CLIENTID "clientID"
50 #define JSON_KEY_USER "username"
51 #define JSON_KEY_PASS "password"
52 #define JSON_KEY_TOPICS "topics"
53
54 static int parse_passwd_response(const char *json_str, struct auth_data *auth) {
55 int rc = 1;
56 json_object *json;
57 struct json_object_iterator it;
58 struct json_object_iterator itEnd;
59
60 json = json_tokener_parse(json_str);
61 if (!json) {
62 netdata_log_error("ACLK: JSON-C failed to parse the payload of http response of /env endpoint");
63 return 1;
64 }
65
66 it = json_object_iter_begin(json);
67 itEnd = json_object_iter_end(json);
68
69 while (!json_object_iter_equal(&it, &itEnd)) {
70 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_CLIENTID)) {
71 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_CLIENTID)
72
73 auth->client_id = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
74 json_object_iter_next(&it);
75 continue;
76 }
77 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_USER)) {
78 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_USER)
79
80 auth->username = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
81 json_object_iter_next(&it);
82 continue;
83 }
84 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_PASS)) {
85 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_PASS)
86
87 auth->passwd = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
88 json_object_iter_next(&it);
89 continue;
90 }
91 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_TOPICS)) {
92 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_array, JSON_KEY_TOPICS)
93
94 if (aclk_generate_topic_cache(json_object_iter_peek_value(&it))) {
95 netdata_log_error("ACLK: Failed to generate topic cache!");
96 goto exit;
97 }
98 json_object_iter_next(&it);
99 continue;
100 }
101 netdata_log_error("ACLK: Unknown key \"%s\" in passwd response payload. Ignoring", json_object_iter_peek_name(&it));
102 json_object_iter_next(&it);
103 }
104
105 if (!auth->client_id) {
106 netdata_log_error("ACLK: " JSON_KEY_CLIENTID " is compulsory key in /password response");
107 goto exit;
108 }
109 if (!auth->passwd) {
110 netdata_log_error("ACLK: " JSON_KEY_PASS " is compulsory in /password response");
111 goto exit;
112 }
113 if (!auth->username) {
114 netdata_log_error("ACLK: " JSON_KEY_USER " is compulsory in /password response");
115 goto exit;
116 }
117
118 rc = 0;
119 exit:
120 json_object_put(json);
121 return rc;
122 }
123
124 #define JSON_KEY_ERTRY "errorNonRetryable"
125 #define JSON_KEY_EDELAY "errorRetryDelaySeconds"
126 #define JSON_KEY_EEC "errorCode"
127 #define JSON_KEY_EMSGKEY "errorMsgKey"
128 #define JSON_KEY_EMSG "errorMessage"
129 #if JSON_C_MINOR_VERSION >= 13
130 static const char *get_json_str_by_path(json_object *json, const char *path) {
131 json_object *ptr;
132 if (json_pointer_get(json, path, &ptr)) {
133 netdata_log_error("ACLK: Missing compulsory key \"%s\" in error response", path);
134 return NULL;
135 }
136 if (json_object_get_type(ptr) != json_type_string) {
137 netdata_log_error("ACLK: Value of Key \"%s\" in error response should be string", path);
138 return NULL;
139 }
140 return json_object_get_string(ptr);
141 }
142
143 static int aclk_parse_otp_error(const char *json_str) {
144 int rc = 1;
145 json_object *json, *ptr;
146 const char *ec;
147 const char *ek;
148 const char *emsg;
149 int block_retry = -1, backoff = -1;
150
151
152 json = json_tokener_parse(json_str);
153 if (!json) {
154 netdata_log_error("ACLK: JSON-C failed to parse the payload of http response of /env endpoint");
155 return 1;
156 }
157
158 if ((ec = get_json_str_by_path(json, "/" JSON_KEY_EEC)) == NULL)
159 goto exit;
160
161 if ((ek = get_json_str_by_path(json, "/" JSON_KEY_EMSGKEY)) == NULL)
162 goto exit;
163
164 if ((emsg = get_json_str_by_path(json, "/" JSON_KEY_EMSG)) == NULL)
165 goto exit;
166
167 // optional field
168 if (!json_pointer_get(json, "/" JSON_KEY_ERTRY, &ptr)) {
169 if (json_object_get_type(ptr) != json_type_boolean) {
170 netdata_log_error("ACLK: Error response Key " "/" JSON_KEY_ERTRY " should be of boolean type");
171 goto exit;
172 }
173 block_retry = json_object_get_boolean(ptr);
174 }
175
176 // optional field
177 if (!json_pointer_get(json, "/" JSON_KEY_EDELAY, &ptr)) {
178 if (json_object_get_type(ptr) != json_type_int) {
179 netdata_log_error("ACLK: Error response Key " "/" JSON_KEY_EDELAY " should be of integer type");
180 goto exit;
181 }
182 backoff = json_object_get_int(ptr);
183 }
184
185 if (block_retry > 0)
186 aclk_disable_runtime = 1;
187
188 if (backoff > 0)
189 aclk_block_until = now_monotonic_sec() + backoff;
190
191 netdata_log_error("ACLK: Cloud returned EC=\"%s\", Msg-Key:\"%s\", Msg:\"%s\", BlockRetry:%s, Backoff:%ds (-1 unset by cloud)", ec, ek, emsg, block_retry > 0 ? "true" : "false", backoff);
192 rc = 0;
193 exit:
194 json_object_put(json);
195 return rc;
196 }
197 #else
198 static int aclk_parse_otp_error(const char *json_str) {
199 int rc = 1;
200 int block_retry = -1, backoff = -1;
201
202 const char *ec = NULL;
203 const char *ek = NULL;
204 const char *emsg = NULL;
205
206 json_object *json;
207 struct json_object_iterator it;
208 struct json_object_iterator itEnd;
209
210 json = json_tokener_parse(json_str);
211 if (!json) {
212 netdata_log_error("ACLK: JSON-C failed to parse the payload of http response of /env endpoint");
213 return 1;
214 }
215
216 it = json_object_iter_begin(json);
217 itEnd = json_object_iter_end(json);
218
219 while (!json_object_iter_equal(&it, &itEnd)) {
220 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_EMSG)) {
221 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_EMSG)
222
223 emsg = json_object_get_string(json_object_iter_peek_value(&it));
224 json_object_iter_next(&it);
225 continue;
226 }
227 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_EMSGKEY)) {
228 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_EMSGKEY)
229
230 ek = json_object_get_string(json_object_iter_peek_value(&it));
231 json_object_iter_next(&it);
232 continue;
233 }
234 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_EEC)) {
235 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_EEC)
236
237 ec = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
238 json_object_iter_next(&it);
239 continue;
240 }
241 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_EDELAY)) {
242 if (json_object_get_type(json_object_iter_peek_value(&it)) != json_type_int) {
243 netdata_log_error("ACLK: value of key " JSON_KEY_EDELAY " should be integer");
244 goto exit;
245 }
246
247 backoff = json_object_get_int(json_object_iter_peek_value(&it));
248 json_object_iter_next(&it);
249 continue;
250 }
251 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_ERTRY)) {
252 if (json_object_get_type(json_object_iter_peek_value(&it)) != json_type_boolean) {
253 netdata_log_error("ACLK: value of key " JSON_KEY_ERTRY " should be integer");
254 goto exit;
255 }
256
257 block_retry = json_object_get_boolean(json_object_iter_peek_value(&it));
258 json_object_iter_next(&it);
259 continue;
260 }
261 netdata_log_error("ACLK: Unknown key \"%s\" in error response payload. Ignoring", json_object_iter_peek_name(&it));
262 json_object_iter_next(&it);
263 }
264
265 if (block_retry > 0)
266 aclk_disable_runtime = 1;
267
268 if (backoff > 0)
269 aclk_block_until = now_monotonic_sec() + backoff;
270
271 netdata_log_error("ACLK: Cloud returned EC=\"%s\", Msg-Key:\"%s\", Msg:\"%s\", BlockRetry:%s, Backoff:%ds (-1 unset by cloud)", ec, ek, emsg, block_retry > 0 ? "true" : "false", backoff);
272 rc = 0;
273 exit:
274 json_object_put(json);
275 return rc;
276 }
277 #endif
278
279 #define CHALLENGE_LEN 256
280 #define CHALLENGE_LEN_BASE64 344
281
282 #define OTP_URL_PREFIX "/api/v1/auth/node/"
283 static https_client_resp_t aclk_get_otp_challenge(url_t *target, const char *agent_id, unsigned char **challenge, int *challenge_bytes, bool *fallback_ipv4)
284 {
285 https_client_resp_t rc;
286 https_req_t req = HTTPS_REQ_T_INITIALIZER;
287 https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
288
289 BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
290
291 req.host = target->host;
292 req.port = target->port;
293 buffer_sprintf(url, "%s/node/%s/challenge", target->path, agent_id);
294 req.url = (char *)buffer_tostring(url);
295
296 rc = aclk_https_request(&req, &resp, fallback_ipv4);
297 if (rc != HTTPS_CLIENT_RESP_OK) {
298 netdata_log_error("ACLK: OTP Challenge failed");
299 buffer_free(url);
300 return rc;
301 }
302 if (resp.http_code != 200) {
303 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_NOT_200;
304 netdata_log_error("ACLK: OTP Challenge HTTP code not 200 OK (got %d)", resp.http_code);
305 buffer_free(url);
306 if (resp.payload_size)
307 aclk_parse_otp_error(resp.payload);
308 goto cleanup_resp;
309 }
310 buffer_free(url);
311
312 json_object *json = json_tokener_parse(resp.payload);
313 if (!json) {
314 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
315 netdata_log_error("ACLK: couldn't parse HTTP GET challenge payload");
316 goto cleanup_resp;
317 }
318 json_object *challenge_json;
319 if (!json_object_object_get_ex(json, "challenge", &challenge_json)) {
320 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
321 netdata_log_error("ACLK: No key named \"challenge\" in the returned JSON");
322 goto cleanup_json;
323 }
324 if (!json_object_is_type(challenge_json, json_type_string)) {
325 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
326 netdata_log_error("ACLK: \"challenge\" is not a string JSON type");
327 goto cleanup_json;
328 }
329 const char *challenge_base64;
330 if (!((challenge_base64 = json_object_get_string(challenge_json)))) {
331 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
332 netdata_log_error("ACLK: Failed to extract challenge from JSON object");
333 goto cleanup_json;
334 }
335 if (strlen(challenge_base64) != CHALLENGE_LEN_BASE64) {
336 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
337 netdata_log_error("ACLK: Received Challenge has unexpected length of %zu (expected %d)", strlen(challenge_base64), CHALLENGE_LEN_BASE64);
338 goto cleanup_json;
339 }
340
341 *challenge = mallocz((CHALLENGE_LEN_BASE64 / 4) * 3 + 1);
342 *challenge_bytes = netdata_base64_decode(*challenge, (const unsigned char *) challenge_base64, CHALLENGE_LEN_BASE64);
343
344 if (*challenge_bytes != CHALLENGE_LEN) {
345 rc = HTTPS_CLIENT_RESP_OTP_CHALLENGE_INVALID;
346 netdata_log_error("ACLK: Unexpected challenge length of %d instead of %d", *challenge_bytes, CHALLENGE_LEN);
347 freez(*challenge);
348 *challenge = NULL;
349 goto cleanup_json;
350 }
351
352 rc = HTTPS_CLIENT_RESP_OK;
353
354 cleanup_json:
355 json_object_put(json);
356 cleanup_resp:
357 https_req_response_free(&resp);
358 return rc;
359 }
360
361 static https_client_resp_t aclk_send_otp_response(const char *agent_id, const unsigned char *response, int response_bytes, url_t *target, struct auth_data *mqtt_auth, bool *fallback_ipv4)
362 {
363 https_client_resp_t rc;
364 https_req_t req = HTTPS_REQ_T_INITIALIZER;
365 https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
366
367 req.host = target->host;
368 req.port = target->port;
369 req.request_type = HTTP_REQ_POST;
370
371 unsigned char base64[CHALLENGE_LEN_BASE64 + 1];
372 memset(base64, 0, CHALLENGE_LEN_BASE64 + 1);
373
374 (void) netdata_base64_encode(base64, response, response_bytes);
375
376 BUFFER *url = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
377 BUFFER *resp_json = buffer_create(strlen(OTP_URL_PREFIX) + UUID_STR_LEN + 20, &netdata_buffers_statistics.buffers_aclk);
378
379 buffer_sprintf(url, "%s/node/%s/password", target->path, agent_id);
380 buffer_sprintf(resp_json, "{\"agent_version\":\"%s\", \"response\":\"%s\"}", NETDATA_VERSION, base64);
381
382 req.url = (char *)buffer_tostring(url);
383 req.payload = (char *)buffer_tostring(resp_json);
384 req.payload_size = strlen(req.payload);
385
386 rc = aclk_https_request(&req, &resp, fallback_ipv4);
387 if (rc != HTTPS_CLIENT_RESP_OK) {
388 netdata_log_error("ACLK: OTP Password error trying to post result to password");
389 goto cleanup_buffers;
390 }
391 if (resp.http_code != 201) {
392 rc = HTTPS_CLIENT_RESP_OTP_PASSWORD_NOT_201;
393 netdata_log_error("ACLK: OTP Password HTTP code not 201 Created (got %d)", resp.http_code);
394 if (resp.payload_size)
395 aclk_parse_otp_error(resp.payload);
396 goto cleanup_response;
397 }
398 if (resp.payload_size == 0 || resp.payload == NULL) {
399 rc = HTTPS_CLIENT_RESP_OTP_PASSWORD_EMPTY;
400 netdata_log_error("ACLK: OTP Password response payload is empty despite returning 201 Created!");
401 goto cleanup_response;
402 }
403
404 if (parse_passwd_response(resp.payload, mqtt_auth)){
405 rc = HTTPS_CLIENT_RESP_OTP_PASSWORD_NOT_JSON;
406 netdata_log_error("ACLK: Error parsing response of password endpoint");
407 goto cleanup_response;
408 }
409
410 rc = HTTPS_CLIENT_RESP_OK;
411
412 cleanup_response:
413 https_req_response_free(&resp);
414 cleanup_buffers:
415 buffer_free(resp_json);
416 buffer_free(url);
417 return rc;
418 }
419
420 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
421 static int private_decrypt(EVP_PKEY *p_key, unsigned char * enc_data, int data_len, unsigned char **decrypted)
422 #else
423 static int private_decrypt(RSA *p_key, unsigned char * enc_data, int data_len, unsigned char **decrypted)
424 #endif
425 {
426 int result;
427 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
428 size_t outlen = EVP_PKEY_size(p_key);
429 EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(p_key, NULL);
430 if (!ctx)
431 return 1;
432
433 if (EVP_PKEY_decrypt_init(ctx) <= 0) {
434 EVP_PKEY_CTX_free(ctx);
435 return 1;
436 }
437
438 if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) {
439 EVP_PKEY_CTX_free(ctx);
440 return 1;
441 }
442
443 *decrypted = mallocz(outlen);
444
445 if (EVP_PKEY_decrypt(ctx, *decrypted, &outlen, enc_data, data_len) == 1)
446 result = (int) outlen;
447 else
448 result = -1;
449
450 EVP_PKEY_CTX_free(ctx);
451 #else
452 *decrypted = mallocz(RSA_size(p_key));
453 result = RSA_private_decrypt(data_len, enc_data, *decrypted, p_key, RSA_PKCS1_OAEP_PADDING);
454 #endif
455 if (result == -1)
456 {
457 char err[512];
458 ERR_error_string_n(ERR_get_error(), err, sizeof(err));
459 netdata_log_error("ACLK: Decryption of the challenge failed: %s", err);
460 }
461 return result;
462 }
463
464 #if OPENSSL_VERSION_NUMBER >= OPENSSL_VERSION_300
465 https_client_resp_t aclk_get_mqtt_otp(EVP_PKEY *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target, bool *fallback_ipv4)
466 #else
467 https_client_resp_t aclk_get_mqtt_otp(RSA *p_key, char **mqtt_id, char **mqtt_usr, char **mqtt_pass, url_t *target, bool *fallback_ipv4)
468 #endif
469 {
470 unsigned char *challenge = NULL;
471 int challenge_bytes;
472 https_client_resp_t rc;
473
474 CLAIM_ID claim_id = claim_id_get();
475 if (!claim_id_is_set(claim_id)) {
476 netdata_log_error("ACLK: Agent was not claimed - cannot perform challenge/response");
477 return HTTPS_CLIENT_RESP_OTP_AGENT_NOT_CLAIMED;
478 }
479
480 // Get Challenge
481 rc = aclk_get_otp_challenge(target, claim_id.str, &challenge, &challenge_bytes, fallback_ipv4);
482 if (rc != HTTPS_CLIENT_RESP_OK) {
483 netdata_log_error("ACLK: error getting challenge");
484 return rc;
485 }
486
487 // Decrypt Challenge / Get response
488 unsigned char *response_plaintext = NULL;
489 int response_plaintext_bytes = private_decrypt(p_key, challenge, challenge_bytes, &response_plaintext);
490 if (response_plaintext_bytes < 0) {
491 netdata_log_error("ACLK: Couldn't decrypt the challenge received");
492 freez(response_plaintext);
493 freez(challenge);
494 return HTTPS_CLIENT_RESP_OTP_CHALLENGE_DECRYPTION_FAILED;
495 }
496 freez(challenge);
497
498 // Encode and Send Challenge
499 struct auth_data data = { .client_id = NULL, .passwd = NULL, .username = NULL };
500 rc = aclk_send_otp_response(claim_id.str, response_plaintext, response_plaintext_bytes, target, &data, fallback_ipv4);
501 if (rc != HTTPS_CLIENT_RESP_OK) {
502 netdata_log_error("ACLK: Error getting response");
503 freez(response_plaintext);
504 return rc;
505 }
506
507 *mqtt_pass = data.passwd;
508 *mqtt_usr = data.username;
509 *mqtt_id = data.client_id;
510
511 freez(response_plaintext);
512 return HTTPS_CLIENT_RESP_OK;
513 }
514
515 #define JSON_KEY_ENC "encoding"
516 #define JSON_KEY_AUTH_ENDPOINT "authEndpoint"
517 #define JSON_KEY_TRP "transports"
518 #define JSON_KEY_TRP_TYPE "type"
519 #define JSON_KEY_TRP_ENDPOINT "endpoint"
520 #define JSON_KEY_BACKOFF "backoff"
521 #define JSON_KEY_BACKOFF_BASE "base"
522 #define JSON_KEY_BACKOFF_MAX "maxSeconds"
523 #define JSON_KEY_BACKOFF_MIN "minSeconds"
524 #define JSON_KEY_CAPS "capabilities"
525
526 static int parse_json_env_transport(json_object *json, aclk_transport_desc_t *trp) {
527 struct json_object_iterator it;
528 struct json_object_iterator itEnd;
529
530 it = json_object_iter_begin(json);
531 itEnd = json_object_iter_end(json);
532
533 while (!json_object_iter_equal(&it, &itEnd)) {
534 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_TRP_TYPE)) {
535 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_TRP_TYPE)
536 if (trp->type != ACLK_TRP_UNKNOWN) {
537 netdata_log_error("ACLK: " JSON_KEY_TRP_TYPE " set already");
538 goto exit;
539 }
540 trp->type = aclk_transport_type_t_from_str(json_object_get_string(json_object_iter_peek_value(&it)));
541 if (trp->type == ACLK_TRP_UNKNOWN) {
542 netdata_log_error("ACLK: " JSON_KEY_TRP_TYPE " unknown type \"%s\"", json_object_get_string(json_object_iter_peek_value(&it)));
543 goto exit;
544 }
545 json_object_iter_next(&it);
546 continue;
547 }
548
549 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_TRP_ENDPOINT)) {
550 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_TRP_ENDPOINT)
551 if (trp->endpoint) {
552 netdata_log_error("ACLK: " JSON_KEY_TRP_ENDPOINT " set already");
553 goto exit;
554 }
555 trp->endpoint = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
556 json_object_iter_next(&it);
557 continue;
558 }
559
560 netdata_log_error("ACLK: unknown JSON key in dictionary (\"%s\")", json_object_iter_peek_name(&it));
561 json_object_iter_next(&it);
562 }
563
564 if (!trp->endpoint) {
565 netdata_log_error("ACLK: " JSON_KEY_TRP_ENDPOINT " is missing from JSON dictionary");
566 goto exit;
567 }
568
569 if (trp->type == ACLK_TRP_UNKNOWN) {
570 netdata_log_error("ACLK: transport type not set");
571 goto exit;
572 }
573
574 return 0;
575
576 exit:
577 aclk_transport_desc_t_destroy(trp);
578 return 1;
579 }
580
581 static int parse_json_env_transports(json_object *json_array, aclk_env_t *env) {
582 aclk_transport_desc_t *trp;
583 json_object *obj;
584
585 if (env->transports) {
586 netdata_log_error("ACLK: transports have been set already");
587 return 1;
588 }
589
590 env->transport_count = json_object_array_length(json_array);
591
592 env->transports = callocz(env->transport_count , sizeof(aclk_transport_desc_t *));
593
594 for (size_t i = 0; i < env->transport_count; i++) {
595 trp = callocz(1, sizeof(aclk_transport_desc_t));
596 obj = json_object_array_get_idx(json_array, i);
597 if (parse_json_env_transport(obj, trp)) {
598 netdata_log_error("ACLK: error parsing transport idx %d", (int)i);
599 freez(trp);
600 return 1;
601 }
602 env->transports[i] = trp;
603 }
604
605 return 0;
606 }
607
608 #define MATCHED_CORRECT 1
609 #define MATCHED_ERROR -1
610 #define NOT_MATCHED 0
611 static int parse_json_backoff_int(struct json_object_iterator *it, int *out, const char* name, int min, int max) {
612 if (!strcmp(json_object_iter_peek_name(it), name)) {
613 if (json_object_get_type(json_object_iter_peek_value(it)) != json_type_int) {
614 netdata_log_error("ACLK: Could not parse \"%s\". Not an integer as expected.", name);
615 return MATCHED_ERROR;
616 }
617
618 *out = json_object_get_int(json_object_iter_peek_value(it));
619
620 if (*out < min || *out > max) {
621 netdata_log_error("ACLK: Value of \"%s\"=%d out of range (%d-%d).", name, *out, min, max);
622 return MATCHED_ERROR;
623 }
624
625 return MATCHED_CORRECT;
626 }
627 return NOT_MATCHED;
628 }
629
630 static int parse_json_backoff(json_object *json, aclk_backoff_t *backoff) {
631 struct json_object_iterator it;
632 struct json_object_iterator itEnd;
633 int ret;
634
635 it = json_object_iter_begin(json);
636 itEnd = json_object_iter_end(json);
637
638 while (!json_object_iter_equal(&it, &itEnd)) {
639 if ( (ret = parse_json_backoff_int(&it, &backoff->base, JSON_KEY_BACKOFF_BASE, 1, 10)) ) {
640 if (ret == MATCHED_ERROR) {
641 return 1;
642 }
643 json_object_iter_next(&it);
644 continue;
645 }
646
647 if ( (ret = parse_json_backoff_int(&it, &backoff->max_s, JSON_KEY_BACKOFF_MAX, 500, INT_MAX)) ) {
648 if (ret == MATCHED_ERROR) {
649 return 1;
650 }
651 json_object_iter_next(&it);
652 continue;
653 }
654
655 if ( (ret = parse_json_backoff_int(&it, &backoff->min_s, JSON_KEY_BACKOFF_MIN, 0, INT_MAX)) ) {
656 if (ret == MATCHED_ERROR) {
657 return 1;
658 }
659 json_object_iter_next(&it);
660 continue;
661 }
662
663 netdata_log_error("ACLK: unknown JSON key in dictionary (\"%s\")", json_object_iter_peek_name(&it));
664 json_object_iter_next(&it);
665 }
666
667 return 0;
668 }
669
670 static int parse_json_env_caps(json_object *json, aclk_env_t *env) {
671 json_object *obj;
672 const char *str;
673
674 if (env->capabilities) {
675 netdata_log_error("ACLK: transports have been set already");
676 return 1;
677 }
678
679 env->capability_count = json_object_array_length(json);
680
681 // empty capabilities list is allowed
682 if (!env->capability_count)
683 return 0;
684
685 env->capabilities = callocz(env->capability_count , sizeof(char *));
686
687 for (size_t i = 0; i < env->capability_count; i++) {
688 obj = json_object_array_get_idx(json, i);
689 if (json_object_get_type(obj) != json_type_string) {
690 netdata_log_error("ACLK: Capability at index %d not a string!", (int)i);
691 return 1;
692 }
693 str = json_object_get_string(obj);
694 if (!str) {
695 netdata_log_error("ACLK: Error parsing capabilities");
696 return 1;
697 }
698 env->capabilities[i] = strdupz(str);
699 }
700
701 return 0;
702 }
703
704 static int parse_json_env(const char *json_str, aclk_env_t *env) {
705 json_object *json;
706 struct json_object_iterator it;
707 struct json_object_iterator itEnd;
708
709 json = json_tokener_parse(json_str);
710 if (!json) {
711 netdata_log_error("ACLK: JSON-C failed to parse the payload of http response of /env endpoint");
712 return 1;
713 }
714
715 it = json_object_iter_begin(json);
716 itEnd = json_object_iter_end(json);
717
718 while (!json_object_iter_equal(&it, &itEnd)) {
719 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_AUTH_ENDPOINT)) {
720 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_AUTH_ENDPOINT)
721 if (env->auth_endpoint) {
722 netdata_log_error("ACLK: authEndpoint set already");
723 goto exit;
724 }
725 env->auth_endpoint = strdupz(json_object_get_string(json_object_iter_peek_value(&it)));
726 json_object_iter_next(&it);
727 continue;
728 }
729
730 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_ENC)) {
731 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_string, JSON_KEY_ENC)
732 if (env->encoding != ACLK_ENC_UNKNOWN) {
733 netdata_log_error("ACLK: " JSON_KEY_ENC " set already");
734 goto exit;
735 }
736 env->encoding = aclk_encoding_type_t_from_str(json_object_get_string(json_object_iter_peek_value(&it)));
737 json_object_iter_next(&it);
738 continue;
739 }
740
741 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_TRP)) {
742 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_array, JSON_KEY_TRP)
743
744 json_object *now = json_object_iter_peek_value(&it);
745 parse_json_env_transports(now, env);
746
747 json_object_iter_next(&it);
748 continue;
749 }
750
751 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_BACKOFF)) {
752 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_object, JSON_KEY_BACKOFF)
753
754 if (parse_json_backoff(json_object_iter_peek_value(&it), &env->backoff)) {
755 env->backoff.base = 0;
756 netdata_log_error("ACLK: Error parsing Backoff parameters in env");
757 goto exit;
758 }
759
760 json_object_iter_next(&it);
761 continue;
762 }
763
764 if (!strcmp(json_object_iter_peek_name(&it), JSON_KEY_CAPS)) {
765 PARSE_ENV_JSON_CHK_TYPE(&it, json_type_array, JSON_KEY_CAPS)
766
767 if (parse_json_env_caps(json_object_iter_peek_value(&it), env)) {
768 netdata_log_error("ACLK: Error parsing capabilities list");
769 goto exit;
770 }
771
772 json_object_iter_next(&it);
773 continue;
774 }
775
776 netdata_log_error("ACLK: unknown JSON key in dictionary (\"%s\")", json_object_iter_peek_name(&it));
777 json_object_iter_next(&it);
778 }
779
780 // Check all compulsory keys have been set
781 if (env->transport_count < 1) {
782 netdata_log_error("ACLK: env has to return at least one transport");
783 goto exit;
784 }
785 if (!env->auth_endpoint) {
786 netdata_log_error("ACLK: " JSON_KEY_AUTH_ENDPOINT " is compulsory");
787 goto exit;
788 }
789 if (env->encoding == ACLK_ENC_UNKNOWN) {
790 netdata_log_error("ACLK: " JSON_KEY_ENC " is compulsory");
791 goto exit;
792 }
793 if (!env->backoff.base) {
794 netdata_log_error("ACLK: " JSON_KEY_BACKOFF " is compulsory");
795 goto exit;
796 }
797
798 json_object_put(json);
799 return 0;
800
801 exit:
802 json_object_put(json);
803 return 1;
804 }
805
806 https_client_resp_t aclk_get_env(aclk_env_t *env, const char* aclk_hostname, int aclk_port, bool *fallback_ipv4) {
807 BUFFER *buf = buffer_create(1024, &netdata_buffers_statistics.buffers_aclk);
808
809 https_client_resp_t rc;
810 https_req_t req = HTTPS_REQ_T_INITIALIZER;
811 https_req_response_t resp = HTTPS_REQ_RESPONSE_T_INITIALIZER;
812
813 req.request_type = HTTP_REQ_GET;
814
815 CLAIM_ID claim_id = claim_id_get();
816 if (!claim_id_is_set(claim_id)) {
817 netdata_log_error("ACLK: failed to get ACLK environment (agent is not claimed)");
818 buffer_free(buf);
819 return HTTPS_CLIENT_RESP_ENV_AGENT_NOT_CLAIMED;
820 }
821
822 buffer_sprintf(buf, "/api/v1/env?v=%s&cap=proto,ctx&claim_id=%s", &(NETDATA_VERSION[1]) /* skip 'v' at beginning */, claim_id.str);
823
824 req.host = (char*)aclk_hostname;
825 req.port = aclk_port;
826 req.url = buf->buffer;
827 rc = aclk_https_request(&req, &resp, fallback_ipv4);
828 if (rc != HTTPS_CLIENT_RESP_OK) {
829 netdata_log_error("ACLK: failed to get ACLK environment (cannot contact ENV endpoint)");
830 https_req_response_free(&resp);
831 buffer_free(buf);
832 return rc;
833 }
834 if (resp.http_code != 200) {
835 netdata_log_error("ACLK: failed to get ACLK environment (ENV response code is not 200) (got %d)", resp.http_code);
836 if (resp.payload_size)
837 aclk_parse_otp_error(resp.payload);
838 https_req_response_free(&resp);
839 buffer_free(buf);
840 return HTTPS_CLIENT_RESP_ENV_NOT_200;
841 }
842
843 if (!resp.payload || !resp.payload_size) {
844 netdata_log_error("ACLK: failed to get ACLK environment (ENV response is empty)");
845 https_req_response_free(&resp);
846 buffer_free(buf);
847 return HTTPS_CLIENT_RESP_ENV_EMPTY;
848 }
849
850 if (parse_json_env(resp.payload, env)) {
851 netdata_log_error("ACLK: failed to get ACLK environment (ENV response is not JSON)");
852 https_req_response_free(&resp);
853 buffer_free(buf);
854 return HTTPS_CLIENT_RESP_ENV_NOT_JSON;
855 }
856
857 https_req_response_free(&resp);
858 buffer_free(buf);
859 return HTTPS_CLIENT_RESP_OK;
860 }