From 4f102b12c1135164b398edd7bb48186dae1088ea Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 24 Jul 2026 05:56:54 +0000 Subject: [PATCH] One dashboard to manage Asterisk: core tabs always on, optional tabs self-hide Restructure the nav so it reflects what's actually installed on this box, letting one dashboard URL cover everything from a bare LAN Asterisk box up to a full droplet with a trunk and CrowdSec: - Security Log and a new Extensions tab (Groups + Internal SIP messaging, split out of the old "PSTN Trunk" tab) are always available - they only need Asterisk itself, not a trunk or CrowdSec. - Asterisk Admin, PSTN Trunk, and CrowdSec each check their own live install state on every page load and hide their own nav button entirely when not present, instead of showing an empty/placeholder tab. - Add crowdsec_installed() (checks for /usr/bin/cscli) and a /api/crowdsec-status endpoint, mirroring the existing pstn_installed()/ /api/pstn-status pattern. This fixes the earlier design where messaging/groups management lived inside the PSTN Trunk tab even though both work with plain Asterisk and no trunk at all - hiding that tab would have taken them down with it. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01Ho9mZgAkVpdz7S5wJkg8Nf --- services/security-dashboard.sh | 155 +++++++++++++++++++-------------- 1 file changed, 91 insertions(+), 64 deletions(-) diff --git a/services/security-dashboard.sh b/services/security-dashboard.sh index 564e8e7..fc8dcf5 100644 --- a/services/security-dashboard.sh +++ b/services/security-dashboard.sh @@ -194,52 +194,24 @@ protected page. Runs natively on the host (systemd service \`security-dashboard\ not in Docker — it needs to call \`cscli\` and read Asterisk's log directly. ## Tabs +The nav only ever shows tabs for things actually present on this box — no +tab for a service you haven't installed. **Security Log** and **Extensions** +are always there (they only need Asterisk itself, detected once at install +time). **Asterisk Admin**, **PSTN Trunk**, and **CrowdSec** each check their +own live install state on every page load and hide their own nav button +entirely if not found, so this one page/URL scales from a bare LAN Asterisk +box (just those first two tabs) up to a full droplet with a trunk and +CrowdSec, without ever showing a tab for something that isn't set up. + - **Security Log** — parses \`$ASTERISK_LOG_DIR/full\` for SIP auth failures (wrong password, unknown extension, etc.) with timestamp/account/remote IP, - filterable per column (each header has its own text filter, live as you type). -- **CrowdSec** — current bans (\`cscli decisions list\`), a delete/unban button - per entry, carrier/ASN + country columns, and management of the ASN-exempt - Asterisk brute-force scenarios (see \`services/crowdsec.sh\`'s "Exempt - specific carrier ASNs" option) without SSHing in: - - **Currently-exempt ASNs** are listed with carrier name (resolved from - current bans, falling back to alert history for ASNs with no active ban - right now) regardless of when they were added. - - **Unwhitelist** removes an ASN from the exemption list — future Asterisk - auth failures from it are evaluated normally again. - - **Unwhitelist + Ban** does that *and* immediately bans (24h) every IP - CrowdSec has ever recorded for that ASN, for accidental-whitelist cases - where you don't want to wait for it to misbehave again. -- **PSTN Trunk** — a **Groups** card at the top (always available, whether - or not a PSTN trunk has ever been installed) lets you name a set of - extensions and bulk-enable/disable messaging for all of them at once — - a management convenience only, not a runtime concept: applying an action - just writes the same per-extension \`pstn-permissions.conf\` key each - member's own checkbox would, and membership changes never retroactively - affect anything already applied. Below that, the rest of the tab detects - whether \`services/pstn-trunk.sh\`'s dialplan is - actually installed (\`pstn-trunk-dialplan.conf\` present) and shows a - clear "not installed" message instead of the calling-permissions editor - if not, so it never shows real-looking-but-unenforced defaults. When - installed: the outbound/inbound concurrent-call caps, and every known - extension's permission tier (internal / restricted / full) and, for - restricted, its approved numbers — all editable live, no Asterisk - restart, no reinstall. Also manages personal-number assignments (DID -> - owner extension), additive to the shared trunk DID. Writes directly to - \`pstn-limits.conf\` / \`pstn-permissions.conf\` / \`pstn-personal-dids.conf\`, - which the dialplan reads fresh on every call. The spend-cap kill-switch - and international-calling allow-list are deliberately **not** managed - here — CLI-only, via \`sudo ./setup.sh pstn-trunk\` — since both are more - security-sensitive than what this tab already exposes. An "Internal SIP - messaging" card at the **bottom** of the tab (a checkbox chip per known - extension, independent of PSTN calling entirely — no cost, no carrier, no - DID, no dependency on a PSTN trunk being installed) is always available - regardless of any of the above. + sortable per column (click a header to sort, click again to reverse). - **Asterisk Admin** — an embedded, lazy-loaded iframe of the real Asterisk web admin (only fetched the first time you open the tab), plus an - "open in a new tab" fallback link that's always there regardless. Only - shows up once an Asterisk install is detected. If a local Caddy install is - found for both this dashboard and the Asterisk admin's own domain, install - automatically patches the admin's Caddy site block from + "open in a new tab" fallback link that's always there regardless. Its nav + button only appears once an Asterisk install is detected. If a local Caddy + install is found for both this dashboard and the Asterisk admin's own + domain, install automatically patches the admin's Caddy site block from `X-Frame-Options` to a `Content-Security-Policy: frame-ancestors` entry naming only this dashboard's domain, so the browser actually allows the frame — every other site is still refused framing exactly as before. This @@ -248,6 +220,43 @@ not in Docker — it needs to call \`cscli\` and read Asterisk's log directly. confirmed against Authelia's own portal-framing behavior on a live install) — if the tab shows a blank frame, use the fallback link and check this service's own log output from install time for a manual one-line fix. +- **Extensions** — always available, independent of any PSTN trunk. A + **Groups** card lets you name a set of extensions and bulk-enable/disable + messaging for all of them at once — a management convenience only, not a + runtime concept: applying an action just writes the same per-extension + \`pstn-permissions.conf\` key each member's own checkbox would, and + membership changes never retroactively affect anything already applied. + An **Internal SIP messaging** card (a checkbox chip per known extension, + independent of PSTN calling entirely — no cost, no carrier, no DID, no + dependency on a PSTN trunk being installed) sits below it. +- **PSTN Trunk** — its nav button only appears once + \`services/pstn-trunk.sh\`'s dialplan is actually installed + (\`pstn-trunk-dialplan.conf\` present), so it never shows a + real-looking-but-unenforced editor. When present: the outbound/inbound + concurrent-call caps, and every known extension's permission tier + (internal / restricted / full) and, for restricted, its approved numbers — + all editable live, no Asterisk restart, no reinstall, sortable per column. + Also manages personal-number assignments (DID -> owner extension or + group), additive to the shared trunk DID. Writes directly to + \`pstn-limits.conf\` / \`pstn-permissions.conf\` / \`pstn-personal-dids.conf\`, + which the dialplan reads fresh on every call. The spend-cap kill-switch + and international-calling allow-list are deliberately **not** managed + here — CLI-only, via \`sudo ./setup.sh pstn-trunk\` — since both are more + security-sensitive than what this tab already exposes. +- **CrowdSec** — its nav button only appears once \`cscli\` is detected on + this host. Current bans (\`cscli decisions list\`), a delete/unban button + per entry, carrier/ASN + country columns (sortable per column), and + management of the ASN-exempt Asterisk brute-force scenarios (see + \`services/crowdsec.sh\`'s "Exempt specific carrier ASNs" option) without + SSHing in: + - **Currently-exempt ASNs** are listed with carrier name (resolved from + current bans, falling back to alert history for ASNs with no active ban + right now) regardless of when they were added. + - **Unwhitelist** removes an ASN from the exemption list — future Asterisk + auth failures from it are evaluated normally again. + - **Unwhitelist + Ban** does that *and* immediately bans (24h) every IP + CrowdSec has ever recorded for that ASN, for accidental-whitelist cases + where you don't want to wait for it to misbehave again. ## Manage \`\`\` @@ -814,6 +823,14 @@ def run_sudo(args, timeout=15): return False, "", str(e) +def crowdsec_installed(): + """True if cscli is actually present on this host — mirrors + pstn_installed()'s approach of checking for the real thing rather than a + stored flag, so the CrowdSec tab tracks live state without needing this + dashboard reinstalled after CrowdSec is added or removed.""" + return os.path.isfile("/usr/bin/cscli") + + def get_decisions(): ok, out, err = run_sudo(["/usr/bin/cscli", "decisions", "list", "-o", "json"]) if not ok or not out.strip(): @@ -1524,9 +1541,10 @@ INDEX_HTML = """

Security Dashboard

@@ -1566,7 +1584,7 @@ INDEX_HTML = """
-