netdata-claim.sh.in 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. #!/usr/bin/env bash
  2. # netdata
  3. # real-time performance and health monitoring, done right!
  4. # (C) 2017 Costa Tsaousis <costa@tsaousis.gr>
  5. # SPDX-License-Identifier: GPL-3.0-or-later
  6. # Exit code: 0 - Success
  7. # Exit code: 1 - Unknown argument
  8. # Exit code: 2 - Problems with claiming working directory
  9. # Exit code: 3 - Missing dependencies
  10. # Exit code: 4 - Failure to connect to endpoint
  11. # Exit code: 5 - Unknown HTTP error message
  12. # Exit code: 6 - The CLI didn't work
  13. # Exit code: 7 - Wrong user
  14. #
  15. # OK: Agent claimed successfully
  16. # HTTP Status code: 204
  17. # Exit code: 0
  18. #
  19. # Error: The agent id is invalid; it does not fulfill the constraints
  20. # HTTP Status code: 422
  21. # Error key: "ErrInvalidNodeID"
  22. # Error message: "invalid node id"
  23. # Exit code: 6
  24. # Error: The agent hostname is invalid; it does not fulfill the constraints
  25. # HTTP Status code: 422
  26. # Error key: "ErrInvalidNodeName"
  27. # Error message: "invalid node name"
  28. # Exit code: 7
  29. #
  30. # Error: At least one of the given rooms ids is invalid; it does not fulfill the constraints
  31. # HTTP Status code: 422
  32. # Error key: "ErrInvalidRoomID"
  33. # Error message: "invalid room id"
  34. # Exit code: 8
  35. #
  36. # Error: Invalid public key; the public key is empty or not present
  37. # HTTP Status code: 422
  38. # Error key: "ErrInvalidPublicKey"
  39. # Error message: "invalid public key"
  40. # Exit code: 9
  41. #
  42. # Error: Expired, missing or invalid token
  43. # HTTP Status code: 403
  44. # Error key: "ErrForbidden"
  45. # Error message: "token expired" | "token not found" | "invalid token"
  46. # Exit code: 10
  47. #
  48. # Error: Duplicate agent id; an agent with the same id is already registered in the cloud
  49. # HTTP Status code: 409
  50. # Error key: "ErrAlreadyClaimed"
  51. # Error message: "already claimed"
  52. # Exit code: 11
  53. #
  54. # Error: The node claiming process is still in progress.
  55. # HTTP Status code: 102
  56. # Error key: "ErrProcessingClaim"
  57. # Error message: "processing claiming"
  58. # Exit code: 12
  59. #
  60. # Error: Internal server error. Any other unexpected error (DB problems, etc.)
  61. # HTTP Status code: 500
  62. # Error key: "ErrInternalServerError"
  63. # Error message: "Internal Server Error"
  64. # Exit code: 13
  65. #
  66. # Error: There was a timout processing the claim.
  67. # HTTP Status code: 504
  68. # Error key: "ErrGatewayTimeout"
  69. # Error message: "Gateway Timeout"
  70. # Exit code: 14
  71. #
  72. # Error: The service cannot handle the claiming request at this time.
  73. # HTTP Status code: 503
  74. # Error key: "ErrServiceUnavailable"
  75. # Error message: "Service Unavailable"
  76. # Exit code: 15
  77. if command -v curl >/dev/null 2>&1 ; then
  78. URLTOOL="curl"
  79. elif command -v wget >/dev/null 2>&1 ; then
  80. URLTOOL="wget"
  81. else
  82. echo >&2 "I need curl or wget to proceed, but neither is available on this system."
  83. exit 3
  84. fi
  85. if ! command -v openssl >/dev/null 2>&1 ; then
  86. echo >&2 "I need openssl to proceed, but it is not available on this system."
  87. exit 3
  88. fi
  89. # -----------------------------------------------------------------------------
  90. # defaults to allow running this script by hand
  91. [ -z "${NETDATA_USER_CONFIG_DIR}" ] && NETDATA_USER_CONFIG_DIR="@configdir_POST@"
  92. MACHINE_GUID_FILE="@registrydir_POST@/netdata.public.unique.id"
  93. CLAIMING_DIR="${NETDATA_USER_CONFIG_DIR}/claim.d"
  94. TOKEN="unknown"
  95. URL_BASE="https://netdata.cloud"
  96. ID="unknown"
  97. ROOMS=""
  98. HOSTNAME=$(hostname)
  99. CLOUD_CERTIFICATE_FILE="${CLAIMING_DIR}/cloud_fullchain.pem"
  100. VERBOSE=0
  101. INSECURE=0
  102. RELOAD=1
  103. NETDATA_USER=netdata
  104. [ -z "$EUID" ] && EUID="$(id -u)"
  105. CONF_USER=$(grep '^[ #]*run as user[ ]*=' "${NETDATA_USER_CONFIG_DIR}/netdata.conf" 2>/dev/null)
  106. if [ -n "$CONF_USER" ]; then
  107. NETDATA_USER=$(echo "$CONF_USER" | sed 's/^[^=]*=[ \t]*//' | sed 's/[ \t]*$//')
  108. fi
  109. # get the MACHINE_GUID by default
  110. if [ -r "${MACHINE_GUID_FILE}" ]; then
  111. ID="$(cat "${MACHINE_GUID_FILE}")"
  112. fi
  113. # get token from file
  114. if [ -r "${CLAIMING_DIR}/token" ]; then
  115. TOKEN="$(cat "${CLAIMING_DIR}/token")"
  116. fi
  117. # get rooms from file
  118. if [ -r "${CLAIMING_DIR}/rooms" ]; then
  119. ROOMS="$(cat "${CLAIMING_DIR}/rooms")"
  120. fi
  121. for arg in "$@"
  122. do
  123. case $arg in
  124. -token=*) TOKEN=${arg:7} ;;
  125. -url=*) URL_BASE=${arg:5} ;;
  126. -id=*) ID=${arg:4} ;;
  127. -rooms=*) ROOMS=${arg:7} ;;
  128. -hostname=*) HOSTNAME=${arg:10} ;;
  129. -verbose) VERBOSE=1 ;;
  130. -insecure) INSECURE=1 ;;
  131. -proxy=*) PROXY=${arg:7} ;;
  132. -noproxy) NOPROXY=yes ;;
  133. -noreload) RELOAD=0 ;;
  134. -user=*) NETDATA_USER=${arg:6} ;;
  135. *) echo >&2 "Unknown argument ${arg}"
  136. exit 1 ;;
  137. esac
  138. shift 1
  139. done
  140. if [ "$EUID" != "0" ] && [ "$(whoami)" != "$NETDATA_USER" ]; then
  141. echo >&2 "This script must be run by the $NETDATA_USER user account"
  142. exit 7
  143. fi
  144. # if curl not installed give warning SOCKS can't be used
  145. if [[ "${URLTOOL}" != "curl" && "${PROXY:0:5}" = socks ]] ; then
  146. echo >&2 "wget doesn't support SOCKS. Please install curl or disable SOCKS proxy."
  147. exit 1
  148. fi
  149. echo >&2 "Token: ****************"
  150. echo >&2 "Base URL: $URL_BASE"
  151. echo >&2 "Id: $ID"
  152. echo >&2 "Rooms: $ROOMS"
  153. echo >&2 "Hostname: $HOSTNAME"
  154. echo >&2 "Proxy: $PROXY"
  155. echo >&2 "Netdata user: $NETDATA_USER"
  156. # create the claiming directory for this user
  157. if [ ! -d "${CLAIMING_DIR}" ] ; then
  158. mkdir -p "${CLAIMING_DIR}" && chmod 0770 "${CLAIMING_DIR}"
  159. # shellcheck disable=SC2181
  160. if [ $? -ne 0 ] ; then
  161. echo >&2 "Failed to create claiming working directory ${CLAIMING_DIR}"
  162. exit 2
  163. fi
  164. fi
  165. if [ ! -w "${CLAIMING_DIR}" ] ; then
  166. echo >&2 "No write permission in claiming working directory ${CLAIMING_DIR}"
  167. exit 2
  168. fi
  169. if [ ! -f "${CLAIMING_DIR}/private.pem" ] ; then
  170. echo >&2 "Generating private/public key for the first time."
  171. if ! openssl genrsa -out "${CLAIMING_DIR}/private.pem" 2048 ; then
  172. echo >&2 "Failed to generate private/public key pair."
  173. exit 2
  174. fi
  175. fi
  176. if [ ! -f "${CLAIMING_DIR}/public.pem" ] ; then
  177. echo >&2 "Extracting public key from private key."
  178. if ! openssl rsa -in "${CLAIMING_DIR}/private.pem" -outform PEM -pubout -out "${CLAIMING_DIR}/public.pem" ; then
  179. echo >&2 "Failed to extract public key."
  180. exit 2
  181. fi
  182. fi
  183. TARGET_URL="${URL_BASE%/}/api/v1/spaces/nodes/${ID}"
  184. # shellcheck disable=SC2002
  185. KEY=$(cat "${CLAIMING_DIR}/public.pem" | tr '\n' '!' | sed -e 's/!/\\n/g')
  186. # shellcheck disable=SC2001
  187. [ -n "$ROOMS" ] && ROOMS=\"$(echo "$ROOMS" | sed s'/,/", "/g')\"
  188. cat > "${CLAIMING_DIR}/tmpin.txt" <<EMBED_JSON
  189. {
  190. "node": {
  191. "id": "$ID",
  192. "hostname": "$HOSTNAME"
  193. },
  194. "token": "$TOKEN",
  195. "rooms" : [ $ROOMS ],
  196. "publicKey" : "$KEY"
  197. }
  198. EMBED_JSON
  199. if [ "${VERBOSE}" == 1 ] ; then
  200. echo "Request to server:"
  201. cat "${CLAIMING_DIR}/tmpin.txt"
  202. fi
  203. if [ "${URLTOOL}" = "curl" ] ; then
  204. URLCOMMAND="curl --connect-timeout 5 --retry 0 -s -i -X PUT -d \"@${CLAIMING_DIR}/tmpin.txt\""
  205. if [ "${NOPROXY}" = "yes" ] ; then
  206. URLCOMMAND="${URLCOMMAND} -x \"\""
  207. elif [ -n "${PROXY}" ] ; then
  208. URLCOMMAND="${URLCOMMAND} -x \"${PROXY}\""
  209. fi
  210. else
  211. URLCOMMAND="wget -T 15 -O - -q --save-headers --content-on-error=on --method=PUT \
  212. --body-file=\"${CLAIMING_DIR}/tmpin.txt\""
  213. if [ "${NOPROXY}" = "yes" ] ; then
  214. URLCOMMAND="${URLCOMMAND} --no-proxy"
  215. elif [ "${PROXY:0:4}" = http ] ; then
  216. URLCOMMAND="export http_proxy=${PROXY}; ${URLCOMMAND}"
  217. fi
  218. fi
  219. if [ "${INSECURE}" == 1 ] ; then
  220. if [ "${URLTOOL}" = "curl" ] ; then
  221. URLCOMMAND="${URLCOMMAND} --insecure"
  222. else
  223. URLCOMMAND="${URLCOMMAND} --no-check-certificate"
  224. fi
  225. fi
  226. if [ -r "${CLOUD_CERTIFICATE_FILE}" ] ; then
  227. if [ "${URLTOOL}" = "curl" ] ; then
  228. URLCOMMAND="${URLCOMMAND} --cacert \"${CLOUD_CERTIFICATE_FILE}\""
  229. else
  230. URLCOMMAND="${URLCOMMAND} --ca-certificate \"${CLOUD_CERTIFICATE_FILE}\""
  231. fi
  232. fi
  233. if [ "${VERBOSE}" == 1 ]; then
  234. echo "${URLCOMMAND} \"${TARGET_URL}\""
  235. fi
  236. eval "${URLCOMMAND} \"${TARGET_URL}\"" >"${CLAIMING_DIR}/tmpout.txt"
  237. URLCOMMAND_EXIT_CODE=$?
  238. if [ "${URLTOOL}" = "wget" ] && [ "${URLCOMMAND_EXIT_CODE}" -eq 8 ] ; then
  239. # We consider the server issuing an error response a successful attempt at communicating
  240. URLCOMMAND_EXIT_CODE=0
  241. fi
  242. rm -f "${CLAIMING_DIR}/tmpin.txt"
  243. # Check if URLCOMMAND connected and received reply
  244. if [ "${URLCOMMAND_EXIT_CODE}" -ne 0 ] ; then
  245. echo >&2 "Failed to connect to ${URL_BASE}, return code ${URLCOMMAND_EXIT_CODE}"
  246. rm -f "${CLAIMING_DIR}/tmpout.txt"
  247. exit 4
  248. fi
  249. if [ "${VERBOSE}" == 1 ] ; then
  250. echo "Response from server:"
  251. cat "${CLAIMING_DIR}/tmpout.txt"
  252. fi
  253. HTTP_STATUS_CODE=$(grep "HTTP" "${CLAIMING_DIR}/tmpout.txt" | awk -F " " '{print $2}')
  254. if [ "${HTTP_STATUS_CODE}" = "204" ] ; then
  255. rm -f "${CLAIMING_DIR}/tmpout.txt"
  256. echo -n "${ID}" >"${CLAIMING_DIR}/claimed_id" || (echo >&2 "Claiming failed"; set -e; exit 2)
  257. rm -f "${CLAIMING_DIR}/token" || (echo >&2 "Claiming failed"; set -e; exit 2)
  258. if [ "$EUID" == "0" ]; then
  259. chown -R "${NETDATA_USER}:${NETDATA_USER}" ${CLAIMING_DIR} || (echo >&2 "Claiming failed"; set -e; exit 2)
  260. fi
  261. if [ "${RELOAD}" == "0" ] ; then
  262. exit 0
  263. fi
  264. netdatacli reload-claiming-state && echo >&2 "Node was successfully claimed." && exit 0
  265. echo "The claim was successful but the agent could not be notified ($?)- it requires a restart to connect to the cloud"
  266. exit 6
  267. fi
  268. ERROR_MESSAGE=$(grep "\"errorMsgKey\":" "${CLAIMING_DIR}/tmpout.txt" | awk -F "errorMsgKey\":\"" '{print $2}' | awk -F "\"" '{print $1}')
  269. case ${ERROR_MESSAGE} in
  270. "ErrInvalidNodeID") EXIT_CODE=6 ;;
  271. "ErrInvalidNodeName") EXIT_CODE=7 ;;
  272. "ErrInvalidRoomID") EXIT_CODE=8 ;;
  273. "ErrInvalidPublicKey") EXIT_CODE=9 ;;
  274. "ErrForbidden") EXIT_CODE=10 ;;
  275. "ErrAlreadyClaimed") EXIT_CODE=11 ;;
  276. "ErrProcessingClaim") EXIT_CODE=12 ;;
  277. "ErrInternalServerError") EXIT_CODE=13 ;;
  278. "ErrGatewayTimeout") EXIT_CODE=14 ;;
  279. "ErrServiceUnavailable") EXIT_CODE=15 ;;
  280. *) EXIT_CODE=5 ;;
  281. esac
  282. echo >&2 "Failed to claim node."
  283. rm -f "${CLAIMING_DIR}/tmpout.txt"
  284. exit $EXIT_CODE