| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "api_v1_calls.h" |
| 4 | |
| 5 | char *api_secret; |
| 6 | |
| 7 | static char *get_mgmt_api_key(void) { |
| 8 | char filename[FILENAME_MAX + 1]; |
| 9 | snprintfz(filename, FILENAME_MAX, "%s/netdata.api.key", netdata_configured_varlib_dir); |
| 10 | const char *api_key_filename = inicfg_get_filename(&netdata_config, CONFIG_SECTION_REGISTRY, "netdata management api key file", filename); |
| 11 | static char guid[GUID_LEN + 1] = ""; |
| 12 | |
| 13 | if(likely(guid[0])) |
| 14 | return guid; |
| 15 | |
| 16 | // read it from disk |
| 17 | #ifdef O_NOFOLLOW |
| 18 | int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC | O_NOFOLLOW); |
| 19 | #else |
| 20 | int fd = open(api_key_filename, O_RDONLY | O_CLOEXEC); |
| 21 | #endif |
| 22 | if(fd != -1) { |
| 23 | struct stat st; |
| 24 | if(fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) |
| 25 | netdata_log_error("Management API key file '%s' is not a regular file, regenerating.", api_key_filename); |
| 26 | else { |
| 27 | char buf[GUID_LEN + 1]; |
| 28 | if(read(fd, buf, GUID_LEN) != GUID_LEN) |
| 29 | netdata_log_error("Failed to read management API key from '%s'", api_key_filename); |
| 30 | else { |
| 31 | buf[GUID_LEN] = '\0'; |
| 32 | if(regenerate_guid(buf, guid) == -1) { |
| 33 | netdata_log_error("Failed to validate management API key '%s' from '%s'.", |
| 34 | buf, api_key_filename); |
| 35 | |
| 36 | guid[0] = '\0'; |
| 37 | } |
| 38 | } |
| 39 | } |
| 40 | close(fd); |
| 41 | } |
| 42 | |
| 43 | // generate a new one? |
| 44 | if(!guid[0]) { |
| 45 | nd_uuid_t uuid; |
| 46 | |
| 47 | uuid_generate(uuid); |
| 48 | uuid_unparse_lower(uuid, guid); |
| 49 | guid[GUID_LEN] = '\0'; |
| 50 | |
| 51 | // save it |
| 52 | #ifdef O_NOFOLLOW |
| 53 | struct stat st; |
| 54 | // O_RDWR avoids blocking on FIFOs (O_WRONLY blocks until a reader arrives). |
| 55 | // O_TRUNC is omitted so truncation only happens after fstat() confirms a regular file. |
| 56 | fd = open(api_key_filename, O_RDWR|O_CREAT|O_CLOEXEC|O_NOFOLLOW, 0600); |
| 57 | if(fd == -1) { |
| 58 | netdata_log_error("Cannot create unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", api_key_filename); |
| 59 | goto temp_key; |
| 60 | } |
| 61 | |
| 62 | if(fstat(fd, &st) != 0 || !S_ISREG(st.st_mode)) { |
| 63 | netdata_log_error("Management API key file '%s' is not a regular file.", api_key_filename); |
| 64 | close(fd); |
| 65 | goto temp_key; |
| 66 | } |
| 67 | |
| 68 | if(ftruncate(fd, 0) != 0) { |
| 69 | netdata_log_error("Cannot truncate management API key file '%s'.", api_key_filename); |
| 70 | close(fd); |
| 71 | goto temp_key; |
| 72 | } |
| 73 | |
| 74 | if(write(fd, guid, GUID_LEN) != GUID_LEN) { |
| 75 | netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", api_key_filename); |
| 76 | close(fd); |
| 77 | goto temp_key; |
| 78 | } |
| 79 | |
| 80 | close(fd); |
| 81 | #else |
| 82 | // Without O_NOFOLLOW: write to a uniquely named temp file then rename atomically. |
| 83 | // Use mkstemp() so the temporary filename is not predictable. |
| 84 | // rename() replaces the destination entry without following symlinks there, |
| 85 | // so a symlink planted at api_key_filename cannot redirect truncation to another file. |
| 86 | char tmp_filename[FILENAME_MAX + 1]; |
| 87 | snprintfz(tmp_filename, FILENAME_MAX, "%s.tmp.XXXXXX", api_key_filename); |
| 88 | |
| 89 | fd = mkstemp(tmp_filename); |
| 90 | if(fd == -1) { |
| 91 | netdata_log_error("Cannot create temporary management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", tmp_filename); |
| 92 | goto temp_key; |
| 93 | } |
| 94 | |
| 95 | if(write(fd, guid, GUID_LEN) != GUID_LEN) { |
| 96 | netdata_log_error("Cannot write the unique management API key file '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file with enough space left.", tmp_filename); |
| 97 | close(fd); |
| 98 | unlink(tmp_filename); |
| 99 | goto temp_key; |
| 100 | } |
| 101 | close(fd); |
| 102 | |
| 103 | if(rename(tmp_filename, api_key_filename) != 0) { |
| 104 | netdata_log_error("Cannot rename temporary API key file '%s' to '%s'. Please adjust config parameter 'netdata management api key file' to a proper path and file.", tmp_filename, api_key_filename); |
| 105 | unlink(tmp_filename); |
| 106 | goto temp_key; |
| 107 | } |
| 108 | #endif |
| 109 | } |
| 110 | |
| 111 | return guid; |
| 112 | |
| 113 | temp_key: |
| 114 | netdata_log_info("You can still continue to use the alarm management API using the authorization token %s during this Netdata session only.", guid); |
| 115 | return guid; |
| 116 | } |
| 117 | |
| 118 | void api_v1_management_init(void) { |
| 119 | api_secret = get_mgmt_api_key(); |
| 120 | } |
| 121 | |
| 122 | #define HLT_MGM "manage/health" |
| 123 | int api_v1_manage(RRDHOST *host, struct web_client *w, char *url) { |
| 124 | const char *haystack = buffer_tostring(w->url_path_decoded); |
| 125 | char *needle; |
| 126 | |
| 127 | buffer_flush(w->response.data); |
| 128 | |
| 129 | if ((needle = strstr(haystack, HLT_MGM)) == NULL) { |
| 130 | buffer_strcat(w->response.data, "Invalid management request. Curently only 'health' is supported."); |
| 131 | return HTTP_RESP_NOT_FOUND; |
| 132 | } |
| 133 | needle += strlen(HLT_MGM); |
| 134 | if (*needle != '\0') { |
| 135 | buffer_strcat(w->response.data, "Invalid management request. Currently only 'health' is supported."); |
| 136 | return HTTP_RESP_NOT_FOUND; |
| 137 | } |
| 138 | return web_client_api_request_v1_mgmt_health(host, w, url); |
| 139 | } |