Allow runtime request headers through API preflight and keep the storefront shell hidden while admin QR authentication gates backoffice.
115 lines
4.0 KiB
Bash
Executable File
115 lines
4.0 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, WebSessionID, Currency, X-Language, X-Region, 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;
|
|
# nginx has already validated and answered CORS. The existing backend
|
|
# rejects browser Origin on :445, so do not forward it a second time.
|
|
proxy_set_header Origin "";
|
|
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"
|