netmaker-ci-runner.sh 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. #!/usr/bin/env bash
  2. # Netmaker CI helper: bring WireGuard up/down and manage ephemeral client lifecycle.
  3. # Subcommands:
  4. # up - fetch config, capture Client-ID, bring interface up, save state
  5. # down - bring interface down, delete local conf, delete client via API
  6. #
  7. # Env vars (can be overridden by flags):
  8. # NETMAKER_BASE_URL (required) e.g. https://nm.example.com or pass --base-url
  9. # NETMAKER_NETWORK (required) e.g. corpnet or pass --network
  10. # NETMAKER_API_JWT (required) Bearer token or pass --jwt
  11. # WG_IFACE (default netmaker) or pass --iface
  12. # WG_CONF_DIR (default /etc/wireguard) or pass --confdir
  13. # NETMAKER_STATE_FILE (default RUNNER_TEMP or /tmp)
  14. # You may also pass --client-id on `down` to avoid relying on the state file.
  15. set -euo pipefail
  16. # ---------- defaults ----------
  17. WG_IFACE="${WG_IFACE:-netmaker}"
  18. WG_CONF_DIR="${WG_CONF_DIR:-/etc/wireguard}"
  19. SUBCMD=""
  20. CLIENT_ID_OVERRIDE=""
  21. usage() {
  22. cat <<USAGE
  23. Usage:
  24. $0 up [--iface IFACE] [--confdir DIR] [--base-url URL] [--network NET] [--jwt TOKEN]
  25. $0 down [--iface IFACE] [--confdir DIR] [--base-url URL] [--network NET] [--jwt TOKEN] [--client-id ID]
  26. Flags override env vars. Env vars documented at top of the script.
  27. Examples:
  28. NETMAKER_BASE_URL=https://nm.example.com NETMAKER_NETWORK=corpnet NETMAKER_API_JWT=... $0 up
  29. $0 down --base-url https://nm.example.com --network corpnet --jwt ... --client-id icy-water
  30. USAGE
  31. }
  32. # ---------- arg parse ----------
  33. if [[ $# -lt 1 ]]; then usage; exit 2; fi
  34. SUBCMD="$1"; shift || true
  35. while [[ $# -gt 0 ]]; do
  36. case "$1" in
  37. --iface) WG_IFACE="$2"; shift 2;;
  38. --confdir) WG_CONF_DIR="$2"; shift 2;;
  39. --base-url) NETMAKER_BASE_URL="$2"; shift 2;;
  40. --network) NETMAKER_NETWORK="$2"; shift 2;;
  41. --jwt) NETMAKER_API_JWT="$2"; shift 2;;
  42. --client-id) CLIENT_ID_OVERRIDE="$2"; shift 2;;
  43. -h|--help) usage; exit 0;;
  44. *) echo "Unknown arg: $1" >&2; usage; exit 2;;
  45. esac
  46. done
  47. STATE_FILE="${NETMAKER_STATE_FILE:-${RUNNER_TEMP:-/tmp}/netmaker_ci_${WG_IFACE}.env}"
  48. require_env() {
  49. : "${NETMAKER_BASE_URL:?ERROR: NETMAKER_BASE_URL not set}"
  50. : "${NETMAKER_NETWORK:?ERROR: NETMAKER_NETWORK not set}"
  51. : "${NETMAKER_API_JWT:?ERROR: NETMAKER_API_JWT not set}"
  52. }
  53. install_deps() {
  54. echo "[*] Checking dependencies ..."
  55. local need=(curl jq wg-quick ip)
  56. local miss=()
  57. for b in "${need[@]}"; do command -v "$b" >/dev/null 2>&1 || miss+=("$b"); done
  58. if [[ ${#miss[@]} -eq 0 ]]; then
  59. echo "[*] All dependencies present."
  60. return
  61. fi
  62. echo "[*] Installing missing deps: ${miss[*]}"
  63. if command -v apt-get >/dev/null 2>&1; then
  64. sudo apt-get update -y
  65. sudo apt-get install -y wireguard-tools jq curl iproute2 resolvconf
  66. elif command -v yum >/dev/null 2>&1; then
  67. sudo yum install -y wireguard-tools jq curl iproute iproute-tc
  68. elif command -v dnf >/dev/null 2>&1; then
  69. sudo dnf install -y wireguard-tools jq curl iproute
  70. else
  71. echo "ERROR: no supported package manager found; install: curl jq wireguard-tools iproute" >&2
  72. exit 1
  73. fi
  74. }
  75. do_up() {
  76. require_env
  77. install_deps
  78. local ep="${NETMAKER_BASE_URL}/api/v1/client_conf/${NETMAKER_NETWORK}"
  79. local tmp_conf="/tmp/${WG_IFACE}.conf"
  80. local tmp_hdr="/tmp/${WG_IFACE}.headers"
  81. echo "[*] Requesting client config: ${ep}"
  82. # Optional headers
  83. declare -a hdrs
  84. hdrs=(-H "Authorization: Bearer ${NETMAKER_API_JWT}")
  85. [[ -n "${NM_CLIENT_LABEL:-}" ]] && hdrs+=(-H "X-NM-Client-Label: ${NM_CLIENT_LABEL}")
  86. [[ -n "${NM_REQUESTED_NAME:-}" ]] && hdrs+=(-H "X-NM-Requested-Name: ${NM_REQUESTED_NAME}")
  87. local code
  88. code="$(curl -sS -L --dump-header "${tmp_hdr}" -w '%{http_code}' -o "${tmp_conf}" "${hdrs[@]}" "${ep}")"
  89. if [[ "${code}" != "200" ]]; then
  90. echo "ERROR: client_conf HTTP ${code}" >&2
  91. curl -sS -L "${hdrs[@]}" "${ep}" | head -c 400 >&2 || true
  92. exit 1
  93. fi
  94. grep -q "^\[Interface\]" "${tmp_conf}" || { echo "ERROR: not a WireGuard conf"; head -n 20 "${tmp_conf}"; exit 1; }
  95. # --- Extract Client-ID (one-liner, trim spaces/quotes) ---
  96. local client_id
  97. client_id="$(grep -i '^Client-ID:' "${tmp_hdr}" | head -n1 | cut -d: -f2- | tr -d '\r' | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"//; s/"$//' -e "s/^'//; s/'$//")"
  98. if [[ -z "${client_id}" ]]; then
  99. echo "ERROR: Client-ID header missing in response; cannot manage lifecycle." >&2
  100. exit 1
  101. fi
  102. echo "[*] Client-ID: ${client_id}"
  103. # Optional marker
  104. if ! grep -q "^#interface-name=" "${tmp_conf}"; then
  105. echo "#interface-name=${WG_IFACE}" | cat - "${tmp_conf}" > "${tmp_conf}.tmp" && mv "${tmp_conf}.tmp" "${tmp_conf}"
  106. fi
  107. # Install & bring up
  108. sudo mkdir -p "${WG_CONF_DIR}"
  109. sudo mv "${tmp_conf}" "${WG_CONF_DIR}/${WG_IFACE}.conf"
  110. sudo chmod 600 "${WG_CONF_DIR}/${WG_IFACE}.conf"
  111. echo "[*] Bringing up ${WG_IFACE} ..."
  112. sudo wg-quick up "${WG_IFACE}"
  113. echo "==== ${WG_IFACE} is up ===="
  114. ip addr show "${WG_IFACE}" || true
  115. wg show "${WG_IFACE}" || true
  116. # Persist state
  117. cat > "${STATE_FILE}" <<EOF
  118. NETMAKER_BASE_URL='${NETMAKER_BASE_URL}'
  119. NETMAKER_NETWORK='${NETMAKER_NETWORK}'
  120. NETMAKER_API_JWT='${NETMAKER_API_JWT}'
  121. WG_IFACE='${WG_IFACE}'
  122. WG_CONF_DIR='${WG_CONF_DIR}'
  123. CLIENT_ID='${client_id}'
  124. EOF
  125. chmod 600 "${STATE_FILE}"
  126. echo "[*] Saved state: ${STATE_FILE}"
  127. }
  128. do_down() {
  129. # Load state if present; flags/env can still override
  130. if [[ -f "${STATE_FILE}" ]]; then
  131. # shellcheck disable=SC1090
  132. source "${STATE_FILE}"
  133. fi
  134. require_env
  135. local client_id="${CLIENT_ID_OVERRIDE:-${CLIENT_ID:-}}"
  136. echo "[*] Bringing down ${WG_IFACE} ..."
  137. sudo wg-quick down "${WG_IFACE}" || echo "WARN: wg-quick down failed (already down?)."
  138. # Remove local conf
  139. if [[ -f "${WG_CONF_DIR}/${WG_IFACE}.conf" ]]; then
  140. sudo shred -u "${WG_CONF_DIR}/${WG_IFACE}.conf" 2>/dev/null || sudo rm -f "${WG_CONF_DIR}/${WG_IFACE}.conf"
  141. fi
  142. # Delete ephemeral client on server (if we know its ID)
  143. if [[ -n "${client_id}" ]]; then
  144. local del_ep="${NETMAKER_BASE_URL}/api/extclients/${NETMAKER_NETWORK}/${client_id}"
  145. echo "[*] Deleting client: DELETE ${del_ep}"
  146. local http
  147. http="$(curl -sS -o /dev/null -w '%{http_code}' -X DELETE -H "Authorization: Bearer ${NETMAKER_API_JWT}" "${del_ep}")"
  148. if [[ "${http}" =~ ^20[0-9]$ ]]; then
  149. echo "[*] Client deleted (HTTP ${http})."
  150. else
  151. echo "WARN: deletion returned HTTP ${http}; verify server state."
  152. fi
  153. else
  154. echo "WARN: client id not known (missing --client-id and state file); skipping server delete."
  155. fi
  156. rm -f "${STATE_FILE}" || true
  157. echo "[*] Teardown finished."
  158. }
  159. case "${SUBCMD}" in
  160. up) do_up ;;
  161. down) do_down ;;
  162. *) usage; exit 2 ;;
  163. esac