Some checks failed
Architecture Governance / architecture (push) Has been cancelled
Tenant subdomains route through api.<base-domain>; nginx forwards the exact storefront host derived from the validated browser origin.
112 lines
3.7 KiB
Bash
Executable File
112 lines
3.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Configure one shared api.<base-domain> for the base storefront and all tenant
|
|
# subdomains. Idempotent. Run as root after the API DNS record resolves here.
|
|
|
|
set -euo pipefail
|
|
|
|
DOMAIN=""
|
|
EMAIL=""
|
|
UPSTREAM="https://127.0.0.1:445"
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--domain) DOMAIN="$2"; shift 2 ;;
|
|
--email) EMAIL="$2"; shift 2 ;;
|
|
--upstream) UPSTREAM="$2"; shift 2 ;;
|
|
*) echo "unknown argument: $1" >&2; exit 2 ;;
|
|
esac
|
|
done
|
|
|
|
[[ $EUID -eq 0 ]] || { echo "must run as root" >&2; exit 1; }
|
|
[[ "$DOMAIN" =~ ^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)+$ ]] || {
|
|
echo "--domain must be a valid lowercase hostname" >&2; exit 2;
|
|
}
|
|
[[ "$EMAIL" =~ ^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$ ]] || {
|
|
echo "--email must be valid" >&2; exit 2;
|
|
}
|
|
[[ "$UPSTREAM" =~ ^https?://[a-zA-Z0-9.:-]+$ ]] || {
|
|
echo "--upstream must be an http(s) origin without a path" >&2; exit 2;
|
|
}
|
|
|
|
API_DOMAIN="api.$DOMAIN"
|
|
CONF="/etc/nginx/sites-available/$API_DOMAIN"
|
|
DOMAIN_REGEX="${DOMAIN//./\\.}"
|
|
|
|
echo "==> checking DNS for $API_DOMAIN"
|
|
getent hosts "$API_DOMAIN" >/dev/null || {
|
|
echo "ERROR: $API_DOMAIN does not resolve; create DNS before provisioning TLS" >&2
|
|
exit 1
|
|
}
|
|
|
|
cat > "$CONF" <<NGINX
|
|
# Managed by marketplaces configure-api-domain.sh. Manual edits are overwritten.
|
|
# Storefront $DOMAIN and its tenant subdomains share https://$API_DOMAIN.
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name $API_DOMAIN;
|
|
|
|
access_log /var/log/nginx/$API_DOMAIN.access.log;
|
|
error_log /var/log/nginx/$API_DOMAIN.error.log;
|
|
|
|
set \$cors_origin "";
|
|
set \$storefront_host "$DOMAIN";
|
|
if (\$http_origin ~* "^https://(?<allowed_storefront>([a-z0-9-]+\\.)*$DOMAIN_REGEX)$") {
|
|
set \$cors_origin \$http_origin;
|
|
set \$storefront_host \$allowed_storefront;
|
|
}
|
|
|
|
add_header Access-Control-Allow-Origin \$cors_origin always;
|
|
add_header Access-Control-Allow-Credentials "true" always;
|
|
add_header Access-Control-Allow-Methods "GET, POST, PUT, PATCH, DELETE, OPTIONS" always;
|
|
add_header Access-Control-Allow-Headers "Authorization, Content-Type, AdminWebSessionID, X-Requested-With" always;
|
|
add_header Vary "Origin" always;
|
|
|
|
if (\$request_method = OPTIONS) { return 204; }
|
|
|
|
location / {
|
|
proxy_pass $UPSTREAM;
|
|
proxy_http_version 1.1;
|
|
# Browser Origin selects the storefront tenant while every tenant under
|
|
# this base domain shares one public API hostname.
|
|
proxy_set_header Host \$storefront_host;
|
|
proxy_set_header X-Forwarded-Host $API_DOMAIN;
|
|
proxy_set_header X-Storefront-Host \$storefront_host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto https;
|
|
proxy_read_timeout 60s;
|
|
proxy_connect_timeout 10s;
|
|
|
|
proxy_ssl_server_name on;
|
|
proxy_ssl_name $DOMAIN;
|
|
}
|
|
}
|
|
NGINX
|
|
|
|
ln -sfn "$CONF" "/etc/nginx/sites-enabled/$API_DOMAIN"
|
|
nginx -t
|
|
|
|
certbot --nginx -d "$API_DOMAIN" \
|
|
--non-interactive --agree-tos --email "$EMAIL" \
|
|
--redirect --keep-until-expiring
|
|
|
|
nginx -t
|
|
systemctl reload nginx
|
|
|
|
echo "==> verifying https://$API_DOMAIN/bootstrap"
|
|
bootstrap_tmp="$(mktemp)"
|
|
trap 'rm -f "$bootstrap_tmp"' EXIT
|
|
content_type="$(curl --resolve "$API_DOMAIN:443:127.0.0.1" -fsS \
|
|
-o "$bootstrap_tmp" -w '%{content_type}' \
|
|
"https://$API_DOMAIN/bootstrap")"
|
|
[[ "$content_type" == application/json* ]] || {
|
|
echo "ERROR: $API_DOMAIN/bootstrap returned $content_type, expected application/json" >&2
|
|
exit 1
|
|
}
|
|
jq -e 'type == "object"' "$bootstrap_tmp" >/dev/null
|
|
rm -f "$bootstrap_tmp"
|
|
trap - EXIT
|
|
|
|
echo "configured: $DOMAIN -> https://$API_DOMAIN -> $UPSTREAM"
|