RELAY-PHASE1 Wave A2/B/C/D/E (12-agent workflow, all tsc-clean, 314/314 tests pass): - A2: control-plane server.ts entry + boot/redis.ts revocation-bus wiring + start script. - B1: relay-run shared-store EnforceDeps (relay-auth ports over the SAME Postgres+Redis as P3). - B2: registry-backed MtlsVerifier (verifyAgentCert, fail-closed, INV14). - B3: store-backed RouteResolver (subdomain->hostId). - B4: Redis relay:revocations subscriber -> tunnel teardown (INV12). - B5: main-phase1.ts production entry (public bind, real TLS, async-mTLS prefetch bridge) + staging /auth/mint. - B6 (PARTIAL): relay-web operator login + browser DPoP; proof offered via term.dpop.<b64u> subprotocol. - C: agent dist/cli.js build (esbuild) + runTunnel run-loop + CliDeps. - D1: same-origin static serve of relay-web/public from the browser WSS. - E: systemd units + gen-ca/gen-capability-key/issue-tls-cert scripts + deploy/RUNBOOK.md. Adversarial review: all hard invariants PASS. Follow-ups (B7): close DPoP-subprotocol read on browser-server (blocks browser connect); rate-limit /auth/mint (F1); wire activeSessionCount (F2); scrub error logs (F5). Excludes unrelated public/style.css (concurrent iOS job).
116 lines
5.4 KiB
Bash
Executable File
116 lines
5.4 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# RELAY-PHASE1 · E (TASK E) — PUBLIC-web TLS cert for the browser :443 endpoint (Let's Encrypt).
|
|
#
|
|
# STAGING TEMPLATE. This is the ONLY public-CA cert in the deployment: the browser hits
|
|
# https://<SUBDOMAIN>.<BASE_DOMAIN> (WSS same-origin), so it must chain to a public root. It is a
|
|
# DIFFERENT trust chain from the private agent-enrollment CA (deploy/scripts/gen-agent-ca.sh) — do
|
|
# NOT cross-wire them.
|
|
#
|
|
# ICP-備案 ASSUMPTION (mainland Alibaba Cloud): BASE_DOMAIN is ALREADY ICP-filed and its A-record
|
|
# <SUBDOMAIN>.<BASE_DOMAIN> -> 8.138.1.192 resolves. Serving :80/:443 on an unfiled domain in
|
|
# mainland CN is blocked upstream; HTTP-01 will then fail. If unfiled, use DNS-01 (no inbound :80).
|
|
#
|
|
# Two challenge methods (pick with ACME_METHOD):
|
|
# http-01 — a one-shot listener on :80 answers the challenge. REQUIRES inbound :80 open in the
|
|
# Aliyun security group DURING issuance (you can close it again after; renewals reopen).
|
|
# dns-01 — a TXT record under _acme-challenge.<SUBDOMAIN>.<BASE_DOMAIN>. No inbound :80. Needs the
|
|
# acme.sh DNS-API creds for your provider (e.g. Ali_Key/Ali_Secret for the Aliyun DNS API).
|
|
#
|
|
# Installs to TLS_CERT_PATH (fullchain) + TLS_KEY_PATH, matching relay.env.
|
|
#
|
|
# Config via ENV only:
|
|
# BASE_DOMAIN REQUIRED (e.g. term.example.com)
|
|
# SUBDOMAIN REQUIRED (the tenant sub; FQDN = <SUBDOMAIN>.<BASE_DOMAIN>)
|
|
# ACME_EMAIL REQUIRED account/registration email
|
|
# ACME_METHOD http-01 | dns-01 (default http-01)
|
|
# ACME_CLIENT acme.sh | certbot (default acme.sh)
|
|
# ACME_DNS_PROVIDER acme.sh dnsapi id for dns-01 (e.g. dns_ali) (dns-01 only)
|
|
# TLS_CERT_PATH install target for fullchain (default /etc/relay/tls/fullchain.pem)
|
|
# TLS_KEY_PATH install target for privkey (default /etc/relay/tls/privkey.pem)
|
|
# ACME_STAGING 1 to use the LE staging CA (untrusted, avoids rate limits while testing)
|
|
#
|
|
# Verify (syntax): bash -n deploy/scripts/issue-tls-cert.sh
|
|
set -euo pipefail
|
|
|
|
: "${BASE_DOMAIN:?FATAL: BASE_DOMAIN is required (your ICP-filed domain)}"
|
|
: "${SUBDOMAIN:?FATAL: SUBDOMAIN is required}"
|
|
: "${ACME_EMAIL:?FATAL: ACME_EMAIL is required}"
|
|
ACME_METHOD="${ACME_METHOD:-http-01}"
|
|
ACME_CLIENT="${ACME_CLIENT:-acme.sh}"
|
|
TLS_CERT_PATH="${TLS_CERT_PATH:-/etc/relay/tls/fullchain.pem}"
|
|
TLS_KEY_PATH="${TLS_KEY_PATH:-/etc/relay/tls/privkey.pem}"
|
|
|
|
FQDN="${SUBDOMAIN}.${BASE_DOMAIN}"
|
|
echo "== Issuing Let's Encrypt cert for ${FQDN} via ${ACME_CLIENT} (${ACME_METHOD}) =="
|
|
|
|
mkdir -p "$(dirname "${TLS_CERT_PATH}")" "$(dirname "${TLS_KEY_PATH}")"
|
|
|
|
case "${ACME_CLIENT}" in
|
|
acme.sh)
|
|
command -v acme.sh >/dev/null 2>&1 || { echo "FATAL: acme.sh not installed. curl https://get.acme.sh | sh" >&2; exit 3; }
|
|
acme.sh --register-account -m "${ACME_EMAIL}" >/dev/null 2>&1 || true
|
|
STAGING_FLAG=""
|
|
[[ "${ACME_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging"
|
|
|
|
case "${ACME_METHOD}" in
|
|
http-01)
|
|
echo ">> Ensure inbound :80 is OPEN in the Aliyun security group for the duration of issuance."
|
|
acme.sh --issue ${STAGING_FLAG} -d "${FQDN}" --standalone --httpport 80
|
|
;;
|
|
dns-01)
|
|
: "${ACME_DNS_PROVIDER:?FATAL: ACME_DNS_PROVIDER required for dns-01 (e.g. dns_ali); export the DNS-API creds too}"
|
|
acme.sh --issue ${STAGING_FLAG} -d "${FQDN}" --dns "${ACME_DNS_PROVIDER}"
|
|
;;
|
|
*)
|
|
echo "FATAL: ACME_METHOD must be http-01 or dns-01" >&2; exit 2 ;;
|
|
esac
|
|
|
|
# --install-cert copies the current material AND registers the renew-reload hook.
|
|
acme.sh --install-cert -d "${FQDN}" \
|
|
--key-file "${TLS_KEY_PATH}" \
|
|
--fullchain-file "${TLS_CERT_PATH}" \
|
|
--reloadcmd "systemctl try-reload-or-restart relay-data-plane.service"
|
|
;;
|
|
|
|
certbot)
|
|
command -v certbot >/dev/null 2>&1 || { echo "FATAL: certbot not installed (apt-get install certbot)." >&2; exit 3; }
|
|
STAGING_FLAG=""
|
|
[[ "${ACME_STAGING:-0}" == "1" ]] && STAGING_FLAG="--staging"
|
|
case "${ACME_METHOD}" in
|
|
http-01)
|
|
echo ">> Ensure inbound :80 is OPEN in the Aliyun security group for the duration of issuance."
|
|
certbot certonly ${STAGING_FLAG} --standalone --non-interactive --agree-tos \
|
|
-m "${ACME_EMAIL}" -d "${FQDN}"
|
|
;;
|
|
dns-01)
|
|
echo "FATAL: certbot dns-01 needs a provider plugin; prefer ACME_CLIENT=acme.sh for Aliyun DNS." >&2
|
|
exit 2 ;;
|
|
*)
|
|
echo "FATAL: ACME_METHOD must be http-01 or dns-01" >&2; exit 2 ;;
|
|
esac
|
|
install -m 644 "/etc/letsencrypt/live/${FQDN}/fullchain.pem" "${TLS_CERT_PATH}"
|
|
install -m 600 "/etc/letsencrypt/live/${FQDN}/privkey.pem" "${TLS_KEY_PATH}"
|
|
;;
|
|
|
|
*)
|
|
echo "FATAL: ACME_CLIENT must be acme.sh or certbot" >&2; exit 2 ;;
|
|
esac
|
|
|
|
chmod 600 "${TLS_KEY_PATH}" || true
|
|
chmod 644 "${TLS_CERT_PATH}" || true
|
|
|
|
cat <<SUMMARY
|
|
|
|
Installed:
|
|
TLS_CERT_PATH = ${TLS_CERT_PATH}
|
|
TLS_KEY_PATH = ${TLS_KEY_PATH}
|
|
|
|
RENEWAL (LE certs last ~90 days):
|
|
- acme.sh installs its OWN cron on install ('acme.sh --cron'); --install-cert registered the
|
|
reload hook above, so renewals auto-reload the data-plane. Verify: 'crontab -l | grep acme'.
|
|
- certbot: add a cron/timer, e.g.
|
|
0 3 * * * certbot renew --quiet --deploy-hook 'systemctl try-reload-or-restart relay-data-plane.service'
|
|
- http-01 renewals need :80 reachable at renew time; dns-01 does not.
|
|
SUMMARY
|