diff --git a/controllers/ext_client.go b/controllers/ext_client.go index 4d2ac0a6..0ae25205 100644 --- a/controllers/ext_client.go +++ b/controllers/ext_client.go @@ -466,23 +466,6 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) { extclient.IngressGatewayID = targetGwID extclient.Network = networkid extclient.Tags = make(map[models.TagID]struct{}) - // extclient.Tags[models.TagID(fmt.Sprintf("%s.%s", extclient.Network, - // models.RemoteAccessTagName))] = struct{}{} - // set extclient dns to ingressdns if extclient dns is not explicitly set - if (extclient.DNS == "") && (gwnode.IngressDNS != "") { - network, _ := logic.GetNetwork(gwnode.Network) - dns := gwnode.IngressDNS - if len(network.NameServers) > 0 { - if dns == "" { - dns = strings.Join(network.NameServers, ",") - } else { - dns += "," + strings.Join(network.NameServers, ",") - } - - } - extclient.DNS = dns - - } listenPort := logic.GetPeerListenPort(host) extclient.IngressGatewayEndpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), listenPort) @@ -506,6 +489,11 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) { logic.ReturnErrorResponse(w, r, logic.FormatError(err, "internal")) return } + logic.SetDNSOnWgConfig(&gwnode, &client) + defaultDNS := "" + if client.DNS != "" { + defaultDNS = "DNS = " + client.DNS + } addrString := client.Address if addrString != "" { addrString += "/32" @@ -551,13 +539,6 @@ func getExtClientHAConf(w http.ResponseWriter, r *http.Request) { } else { gwendpoint = fmt.Sprintf("%s:%d", host.EndpointIP.String(), host.ListenPort) } - defaultDNS := "" - if client.DNS != "" { - defaultDNS = "DNS = " + client.DNS - } else if gwnode.IngressDNS != "" { - defaultDNS = "DNS = " + gwnode.IngressDNS - } - defaultMTU := 1420 if host.MTU != 0 { defaultMTU = host.MTU @@ -630,6 +611,7 @@ Endpoint = %s name := client.ClientID + ".conf" w.Header().Set("Content-Type", "application/config") + w.Header().Set("Client-ID", client.ClientID) w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") w.WriteHeader(http.StatusOK) _, err = fmt.Fprint(w, config) diff --git a/scripts/ci-runner.sh b/scripts/ci-runner.sh deleted file mode 100644 index 81c0f0b2..00000000 --- a/scripts/ci-runner.sh +++ /dev/null @@ -1,89 +0,0 @@ -#!/usr/bin/env bash -# Fetch WireGuard config from Netmaker via /api/v1/client_conf/{network} and bring it up. -# Required env: -# NETMAKER_BASE_URL, NETMAKER_API_JWT, NETMAKER_NETWORK -# WG_IFACE (default: netmaker), WG_CONF_DIR (default: /etc/wireguard) - -set -euo pipefail - -# --- Fail fast if mandatory variables missing --- -: "${NETMAKER_BASE_URL:?ERROR: NETMAKER_BASE_URL not set}" -: "${NETMAKER_NETWORK:?ERROR: NETMAKER_NETWORK not set}" -: "${NETMAKER_API_JWT:?ERROR: NETMAKER_API_JWT not set}" - -# --- Ensure required packages are present --- -echo "[*] Checking dependencies ..." -DEPS=(curl jq wg-quick ip) -MISSING=() -for bin in "${DEPS[@]}"; do - if ! command -v "$bin" >/dev/null 2>&1; then - MISSING+=("$bin") - fi -done - -if [[ ${#MISSING[@]} -gt 0 ]]; then - echo "[*] Installing missing deps: ${MISSING[*]} ..." - if command -v apt-get >/dev/null 2>&1; then - sudo apt-get update -y - sudo apt-get install -y wireguard-tools jq curl iproute2 resolvconf - elif command -v yum >/dev/null 2>&1; then - sudo yum install -y wireguard-tools jq curl iproute iproute-tc - elif command -v dnf >/dev/null 2>&1; then - sudo dnf install -y wireguard-tools jq curl iproute - else - echo "ERROR: Package manager not found. Install ${MISSING[*]} manually." >&2 - exit 1 - fi -else - echo "[*] All dependencies found." -fi - -# --- Inputs & defaults --- -BASE_URL="${NETMAKER_BASE_URL:?NETMAKER_BASE_URL not set}" -NETWORK="${NETMAKER_NETWORK:?NETMAKER_NETWORK not set}" -JWT="${NETMAKER_API_JWT:?NETMAKER_API_JWT not set}" -WG_IFACE="${WG_IFACE:-netmaker}" -WG_CONF_DIR="${WG_CONF_DIR:-/etc/wireguard}" -TMP_CONF="/tmp/${WG_IFACE}.conf" - -EP="${BASE_URL}/api/v1/client_conf/${NETWORK}" - -echo "[*] Requesting client configuration from: ${EP}" - -HDRS=(-H "Authorization: Bearer ${JWT}") -[[ -n "${NM_CLIENT_LABEL:-}" ]] && HDRS+=(-H "X-NM-Client-Label: ${NM_CLIENT_LABEL}") -[[ -n "${NM_REQUESTED_NAME:-}" ]] && HDRS+=(-H "X-NM-Requested-Name: ${NM_REQUESTED_NAME}") - -# --- Fetch config --- -HTTP_STATUS="$(curl -sS -L -w '%{http_code}' -o "${TMP_CONF}" "${HDRS[@]}" "${EP}")" - -if [[ "${HTTP_STATUS}" != "200" ]]; then - echo "ERROR: client_conf returned HTTP ${HTTP_STATUS}" >&2 - curl -sS -L "${HDRS[@]}" "${EP}" | head -c 400 >&2 || true - exit 1 -fi - -# --- Sanity check --- -if ! grep -q "^\[Interface\]" "${TMP_CONF}"; then - echo "ERROR: Response does not look like a WireGuard config." >&2 - head -n 20 "${TMP_CONF}" >&2 || true - exit 1 -fi - -# --- Add interface-name for traceability --- -if ! grep -q "^#interface-name=" "${TMP_CONF}"; then - echo "#interface-name=${WG_IFACE}" | cat - "${TMP_CONF}" > "${TMP_CONF}.tmp" && mv "${TMP_CONF}.tmp" "${TMP_CONF}" -fi - -# --- Move into place --- -sudo mkdir -p "${WG_CONF_DIR}" -sudo mv "${TMP_CONF}" "${WG_CONF_DIR}/${WG_IFACE}.conf" -sudo chmod 600 "${WG_CONF_DIR}/${WG_IFACE}.conf" - -# --- Bring it up --- -echo "[*] Bringing up ${WG_IFACE} ..." -sudo wg-quick up "${WG_IFACE}" - -echo "==== ${WG_IFACE} is up ====" -ip addr show "${WG_IFACE}" || true -wg show "${WG_IFACE}" || true diff --git a/scripts/netmaker-ci-runner.sh b/scripts/netmaker-ci-runner.sh new file mode 100644 index 00000000..578155e9 --- /dev/null +++ b/scripts/netmaker-ci-runner.sh @@ -0,0 +1,189 @@ +#!/usr/bin/env bash +# Netmaker CI helper: bring WireGuard up/down and manage ephemeral client lifecycle. +# Subcommands: +# up - fetch config, capture Client-ID, bring interface up, save state +# down - bring interface down, delete local conf, delete client via API +# +# Env vars (can be overridden by flags): +# NETMAKER_BASE_URL (required) e.g. https://nm.example.com or pass --base-url +# NETMAKER_NETWORK (required) e.g. corpnet or pass --network +# NETMAKER_API_JWT (required) Bearer token or pass --jwt +# WG_IFACE (default netmaker) or pass --iface +# WG_CONF_DIR (default /etc/wireguard) or pass --confdir +# NETMAKER_STATE_FILE (default RUNNER_TEMP or /tmp) +# You may also pass --client-id on `down` to avoid relying on the state file. + +set -euo pipefail + +# ---------- defaults ---------- +WG_IFACE="${WG_IFACE:-netmaker}" +WG_CONF_DIR="${WG_CONF_DIR:-/etc/wireguard}" +SUBCMD="" +CLIENT_ID_OVERRIDE="" + +usage() { + cat <&2; usage; exit 2;; + esac +done + +STATE_FILE="${NETMAKER_STATE_FILE:-${RUNNER_TEMP:-/tmp}/netmaker_ci_${WG_IFACE}.env}" + +require_env() { + : "${NETMAKER_BASE_URL:?ERROR: NETMAKER_BASE_URL not set}" + : "${NETMAKER_NETWORK:?ERROR: NETMAKER_NETWORK not set}" + : "${NETMAKER_API_JWT:?ERROR: NETMAKER_API_JWT not set}" +} + +install_deps() { + echo "[*] Checking dependencies ..." + local need=(curl jq wg-quick ip) + local miss=() + for b in "${need[@]}"; do command -v "$b" >/dev/null 2>&1 || miss+=("$b"); done + if [[ ${#miss[@]} -eq 0 ]]; then + echo "[*] All dependencies present." + return + fi + echo "[*] Installing missing deps: ${miss[*]}" + if command -v apt-get >/dev/null 2>&1; then + sudo apt-get update -y + sudo apt-get install -y wireguard-tools jq curl iproute2 resolvconf + elif command -v yum >/dev/null 2>&1; then + sudo yum install -y wireguard-tools jq curl iproute iproute-tc + elif command -v dnf >/dev/null 2>&1; then + sudo dnf install -y wireguard-tools jq curl iproute + else + echo "ERROR: no supported package manager found; install: curl jq wireguard-tools iproute" >&2 + exit 1 + fi +} + +do_up() { + require_env + install_deps + + local ep="${NETMAKER_BASE_URL}/api/v1/client_conf/${NETMAKER_NETWORK}" + local tmp_conf="/tmp/${WG_IFACE}.conf" + local tmp_hdr="/tmp/${WG_IFACE}.headers" + + echo "[*] Requesting client config: ${ep}" + # Optional headers + declare -a hdrs + hdrs=(-H "Authorization: Bearer ${NETMAKER_API_JWT}") + [[ -n "${NM_CLIENT_LABEL:-}" ]] && hdrs+=(-H "X-NM-Client-Label: ${NM_CLIENT_LABEL}") + [[ -n "${NM_REQUESTED_NAME:-}" ]] && hdrs+=(-H "X-NM-Requested-Name: ${NM_REQUESTED_NAME}") + + local code + code="$(curl -sS -L --dump-header "${tmp_hdr}" -w '%{http_code}' -o "${tmp_conf}" "${hdrs[@]}" "${ep}")" + if [[ "${code}" != "200" ]]; then + echo "ERROR: client_conf HTTP ${code}" >&2 + curl -sS -L "${hdrs[@]}" "${ep}" | head -c 400 >&2 || true + exit 1 + fi + grep -q "^\[Interface\]" "${tmp_conf}" || { echo "ERROR: not a WireGuard conf"; head -n 20 "${tmp_conf}"; exit 1; } + + # --- Extract Client-ID (one-liner, trim spaces/quotes) --- + local client_id + 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/'$//")" + if [[ -z "${client_id}" ]]; then + echo "ERROR: Client-ID header missing in response; cannot manage lifecycle." >&2 + exit 1 + fi + echo "[*] Client-ID: ${client_id}" + + # Optional marker + if ! grep -q "^#interface-name=" "${tmp_conf}"; then + echo "#interface-name=${WG_IFACE}" | cat - "${tmp_conf}" > "${tmp_conf}.tmp" && mv "${tmp_conf}.tmp" "${tmp_conf}" + fi + + # Install & bring up + sudo mkdir -p "${WG_CONF_DIR}" + sudo mv "${tmp_conf}" "${WG_CONF_DIR}/${WG_IFACE}.conf" + sudo chmod 600 "${WG_CONF_DIR}/${WG_IFACE}.conf" + echo "[*] Bringing up ${WG_IFACE} ..." + sudo wg-quick up "${WG_IFACE}" + + echo "==== ${WG_IFACE} is up ====" + ip addr show "${WG_IFACE}" || true + wg show "${WG_IFACE}" || true + + # Persist state + cat > "${STATE_FILE}" </dev/null || sudo rm -f "${WG_CONF_DIR}/${WG_IFACE}.conf" + fi + + # Delete ephemeral client on server (if we know its ID) + if [[ -n "${client_id}" ]]; then + local del_ep="${NETMAKER_BASE_URL}/api/extclients/${NETMAKER_NETWORK}/${client_id}" + echo "[*] Deleting client: DELETE ${del_ep}" + local http + http="$(curl -sS -o /dev/null -w '%{http_code}' -X DELETE -H "Authorization: Bearer ${NETMAKER_API_JWT}" "${del_ep}")" + if [[ "${http}" =~ ^20[0-9]$ ]]; then + echo "[*] Client deleted (HTTP ${http})." + else + echo "WARN: deletion returned HTTP ${http}; verify server state." + fi + else + echo "WARN: client id not known (missing --client-id and state file); skipping server delete." + fi + + rm -f "${STATE_FILE}" || true + echo "[*] Teardown finished." +} + +case "${SUBCMD}" in + up) do_up ;; + down) do_down ;; + *) usage; exit 2 ;; +esac +