diff --git a/services/asterisk.sh b/services/asterisk.sh index 0cfa883..d210cb1 100644 --- a/services/asterisk.sh +++ b/services/asterisk.sh @@ -385,6 +385,30 @@ _asterisk_refresh_vendor_files() { else log_warning "entrypoint.sh logger.conf template changed upstream — security events won't be logged to a file. Update the sed patch in this installer." fi + + # Regenerate the self-signed TLS cert when it doesn't match the current + # DOMAIN_NAME. Vendor's own check only asks "does the file exist" and + # "does it have a SAN extension" -- never "does the SAN match the domain + # actually configured now" -- so a domain entered once (even a + # placeholder, or one later changed) sticks in the cert FOREVER: it + # survives every subsequent update *and* full reinstall, because + # /etc/asterisk/certs is a bind-mounted host directory neither install + # mode ever wipes (the same reason pjsip.conf/devices survive reinstalls + # too). Confirmed live: a box's TLS transport kept presenting a cert for + # a stale, originally-entered domain long after DOMAIN_NAME had changed + # and a full reinstall had been run in between -- most SIP/TLS clients + # refuse a cert like that outright with no clear error, and this was the + # actual cause of a "port's open but registration still fails" case that + # every other check (firewall, coturn, DNS) had already come back clean. + if grep -q '^if \$regen_cert; then$' ./docker/entrypoint.sh; then + sed -i '/^if \$regen_cert; then$/i\ +if [[ "$regen_cert" != true && -n "${DOMAIN_NAME:-}" ]] && ! openssl x509 -in /etc/asterisk/certs/server.crt -noout -ext subjectAltName 2>/dev/null | grep -q "DNS:${DOMAIN_NAME}"; then\ + log_info "Existing TLS cert does not match current DOMAIN_NAME (${DOMAIN_NAME}) -- regenerating"\ + regen_cert=true\ +fi' ./docker/entrypoint.sh + else + log_warning "entrypoint.sh cert-regen check changed upstream — a stale-domain cert won't auto-regenerate. Update the sed patch in this installer." + fi } # ── Shared: log rotation for logs/full (unbounded otherwise) ────────────── @@ -1310,6 +1334,44 @@ _asterisk_configure_do_cloud_firewall() { fi } +# ── Non-DO public VPS: no automated network-edge firewall step exists for +# arbitrary providers the way _asterisk_configure_do_cloud_firewall automates +# DigitalOcean via doctl -- there's no universal API to drive. But a box set +# up with a public FQDN is, in practice, almost always sitting behind some +# provider-managed firewall anyway, and skipping this reminder left it +# entirely unmentioned. Confirmed live on an IONOS VPS: UFW showed every SIP/ +# TURN/RTP port as ALLOW, Asterisk's own PJSIP logger showed zero incoming +# packets of any kind, and nothing in this installer's own output pointed at +# the actual cause -- IONOS's separate network-level firewall (Cloud Panel -> +# Networking -> Firewall Policies) only allowed 22/80/443/8443/8447 and +# silently dropped everything else before it ever reached the box. UFW being +# wide open proves nothing about a layer in front of it that UFW can't see. +_asterisk_remind_non_do_firewall() { + local WEB_ADMIN_PORT_VAL="$1" WEB_ADMIN_PUBLIC_ACCESS_NEEDED="$2" USE_EMBEDDED_COTURN_VAL="${3:-true}" + echo "" + log_warning "This box is reachable via FQDN but wasn't set up as a DigitalOcean droplet," + log_warning "so no automatic network-edge firewall was configured (that step only exists" + log_warning "for DO, via doctl). Most VPS/cloud providers run their OWN network-level" + log_warning "firewall in front of the box, separate from UFW and invisible to it — UFW can" + log_warning "show every port as ALLOW while traffic still gets silently dropped before it" + log_warning "ever reaches this box. Check your provider's console for it (e.g. IONOS: Cloud" + log_warning "Panel -> Networking -> Firewall Policies) and allow inbound, matching what UFW" + log_warning "just opened on this box:" + echo " TCP 22 (SSH)" + echo " UDP/TCP 5060 (SIP)" + echo " TCP 5061 (SIP TLS)" + [[ "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" == true ]] && echo " TCP ${WEB_ADMIN_PORT_VAL} (web admin)" + echo " TCP 8088, 8089 (Asterisk HTTP/HTTPS)" + echo " UDP 10000-20000 (RTP media)" + if [[ "$USE_EMBEDDED_COTURN_VAL" == true ]]; then + echo " UDP/TCP 3478 (TURN/STUN)" + echo " UDP 49152-49252 (TURN relay)" + else + echo " UDP/TCP 3478 and UDP 49152-49252 too, if the shared coturn instance" + echo " (services/coturn.sh) lives on this same box." + fi +} + # ── Shared: README ───────────────────────────────────────────────────────── # One document with a droplet-only section appended in public-cloud mode, so # the two deployment shapes can't document themselves differently by accident. @@ -1964,9 +2026,12 @@ ENV log_success "UFW rules added." fi - # ── DigitalOcean Cloud Firewall (network edge) ──────────────────────────── - [[ "$IS_DO" == true ]] && \ + # ── Network-edge firewall (in front of the box, not UFW) ────────────────── + if [[ "$IS_DO" == true ]]; then _asterisk_configure_do_cloud_firewall "$DROPLET_ID" "$WEB_ADMIN_PORT_VAL" "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" + elif [[ -n "$DOMAIN_NAME" ]]; then + _asterisk_remind_non_do_firewall "$WEB_ADMIN_PORT_VAL" "$WEB_ADMIN_PUBLIC_ACCESS_NEEDED" "$USE_EMBEDDED_COTURN" + fi # ── CrowdSec note ────────────────────────────────────────────────────────── # Not installed here — select it separately from the whiptail menu, or diff --git a/services/security-dashboard.sh b/services/security-dashboard.sh index 385de4e..766cf5f 100644 --- a/services/security-dashboard.sh +++ b/services/security-dashboard.sh @@ -3632,7 +3632,14 @@ INDEX_HTML = """ } nav button:hover { color: var(--text); } nav button.active { color: var(--text); border-bottom-color: var(--accent); } - main { padding: var(--sp-6); max-width: 1180px; margin: 0 auto; } + /* 1180 was too narrow for the Extensions table specifically (Ext, Name, + Mobile, Status, Transport, PSTN, Whitelist, Messaging, Voicemail, plus + the row-action column) -- ten columns including a dropdown and a free-text + whitelist field forced .table-wrap's horizontal scrollbar even on a normal + desktop viewport. 1600 gives every tab's tables room without it; narrow + viewports still fall back to that same scrollbar (.table-wrap already + handles it), this only raises the ceiling for wide ones. */ + main { padding: var(--sp-6); max-width: 1600px; margin: 0 auto; } /* ── Cards ────────────────────────────────────────────────────────────── */ .card {