#!/usr/bin/env bash set -euo pipefail ADMIN_API_URL="${ADMIN_API_URL:-http://localhost:9180}" ADMIN_API_KEY="${ADMIN_API_KEY:-edd1c9f034335f136f87ad84b625c8f1}" TENANT_ID="${TENANT_ID:-2081a403-c773-427a-86a4-543f0983d921}" usage() { cat <<'EOF' Usage: ./route_create.sh Params: route-prefix APISIX public route prefix, for example: ssm-vn-dev dataverse-host Dataverse host without scheme, for example: tngredev.crm5.dynamics.com client-id Microsoft Entra application client id client-secret Microsoft Entra application client secret Derived values: route id -route route uri //* token cache key dataverse_token_ upstream https:// token scope https:///.default Example: ./route_create.sh ssm-vn-dev tngredev.crm5.dynamics.com 2f680fb4-35b8-4be2-99ee-a3d183b607c9 'YOUR_CLIENT_SECRET' Optional env: ADMIN_API_URL Default: http://localhost:9180 ADMIN_API_KEY Default: edd1c9f034335f136f87ad84b625c8f1 TENANT_ID Default: 2081a403-c773-427a-86a4-543f0983d921 EOF } if [ "$#" -ne 4 ]; then echo "ERROR: Missing required params. Expected 4 params, got $#." >&2 echo >&2 usage >&2 exit 1 fi ROUTE_PREFIX="$1" DATAVERSE_HOST="$2" CLIENT_ID="$3" CLIENT_SECRET="$4" if ! [[ "$ROUTE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_-]*$ ]]; then echo "ERROR: route-prefix is invalid: $ROUTE_PREFIX" >&2 echo "Use only letters, numbers, hyphen, and underscore. Example: ssm-vn-dev" >&2 exit 1 fi if [[ "$DATAVERSE_HOST" == http://* || "$DATAVERSE_HOST" == https://* || "$DATAVERSE_HOST" == */* ]]; then echo "ERROR: dataverse-host must be host only, without scheme/path." >&2 echo "Example: tngredev.crm5.dynamics.com" >&2 exit 1 fi json_escape() { sed \ -e 's/\\/\\\\/g' \ -e 's/"/\\"/g' \ -e ':a;N;$!ba;s/\n/\\n/g' } lua_escape() { sed \ -e 's/\\/\\\\/g' \ -e 's/"/\\"/g' \ -e ':a;N;$!ba;s/\n/\\n/g' } ROUTE_ID="${ROUTE_PREFIX}-route" TOKEN_SUFFIX="${ROUTE_PREFIX//-/_}" TOKEN_CACHE_KEY="dataverse_token_${TOKEN_SUFFIX}" ROUTE_URI="/${ROUTE_PREFIX}/*" REWRITE_REGEX="^/${ROUTE_PREFIX}(/.*)$" UPSTREAM_NODE="${DATAVERSE_HOST}:443" TOKEN_SCOPE="https://${DATAVERSE_HOST}/.default" TOKEN_CACHE_KEY_LUA="$(printf '%s' "$TOKEN_CACHE_KEY" | lua_escape)" TENANT_ID_LUA="$(printf '%s' "$TENANT_ID" | lua_escape)" CLIENT_ID_LUA="$(printf '%s' "$CLIENT_ID" | lua_escape)" CLIENT_SECRET_LUA="$(printf '%s' "$CLIENT_SECRET" | lua_escape)" TOKEN_SCOPE_LUA="$(printf '%s' "$TOKEN_SCOPE" | lua_escape)" LUA_FUNCTION="return function(conf, ctx) local core = require(\"apisix.core\"); local http = require(\"resty.http\"); local cache = ngx.shared.oauth_cache; if not cache then core.log.error(\"oauth_cache not defined in config.yaml\"); return end; local token = cache:get(\"${TOKEN_CACHE_KEY_LUA}\"); if not token then local httpc = http.new(); httpc:set_timeout(5000); local tenant_id = \"${TENANT_ID_LUA}\"; local client_id = \"${CLIENT_ID_LUA}\"; local client_secret = \"${CLIENT_SECRET_LUA}\"; local scope = \"${TOKEN_SCOPE_LUA}\"; local body = \"grant_type=client_credentials&client_id=\" .. ngx.escape_uri(client_id) .. \"&client_secret=\" .. ngx.escape_uri(client_secret) .. \"&scope=\" .. ngx.escape_uri(scope); local res, err = httpc:request_uri(\"https://login.microsoftonline.com/\" .. tenant_id .. \"/oauth2/v2.0/token\", { method = \"POST\", body = body, headers = { [\"Content-Type\"] = \"application/x-www-form-urlencoded\" }, ssl_verify = false }); if not res or res.status ~= 200 then core.log.error(\"Failed to fetch Azure AD token: \", err or (res and res.body)); return core.response.exit(500, { error = \"Auth failed to upstream provider\" }) end; local data, decode_err = core.json.decode(res.body); if not data or not data.access_token then core.log.error(\"Failed to decode token response\"); return core.response.exit(500, { error = \"Invalid token response\" }) end; token = data.access_token; local expires_in = data.expires_in or 3600; cache:set(\"${TOKEN_CACHE_KEY_LUA}\", token, expires_in - 60) end; ngx.req.set_header(\"Authorization\", \"Bearer \" .. token) end" ROUTE_ID_JSON="$(printf '%s' "$ROUTE_ID" | json_escape)" ROUTE_URI_JSON="$(printf '%s' "$ROUTE_URI" | json_escape)" REWRITE_REGEX_JSON="$(printf '%s' "$REWRITE_REGEX" | json_escape)" UPSTREAM_NODE_JSON="$(printf '%s' "$UPSTREAM_NODE" | json_escape)" LUA_FUNCTION_JSON="$(printf '%s' "$LUA_FUNCTION" | json_escape)" PAYLOAD_FILE="$(mktemp)" trap 'rm -f "$PAYLOAD_FILE"' EXIT cat > "$PAYLOAD_FILE" < /..." echo " token cache key: ${TOKEN_CACHE_KEY}" echo curl -i "${ADMIN_API_URL}/apisix/admin/routes/${ROUTE_ID}" \ -H "X-API-KEY: ${ADMIN_API_KEY}" \ -H 'Content-Type: application/json' \ -X PUT \ --data-binary "@${PAYLOAD_FILE}"