Overview — what it does
Security ▸ Firewall (/security/firewall) draws every port your server fleet leaves open into a single exposure map. It colors each open port by risk (crit / warn / ok / unknown) and distinguishes whether it is a registered service (managed) or a surprise opening (unmanaged), so you can immediately see "what is visible from outside, and why."
It joins three sources:
infr_instance_firewall (per-instance firewall rules = open ports/services)
× infr_instance (host meta — public IP · criticality · env · status)
× infr_service (registered logical services — instance_id · port)
→ classified by FirewallLore → exposure map
- This is a read-only console. Editing/collecting the firewall rules themselves is the job of Assets ▸ Instances ▸ [fw]; this page only re-shapes that ledger into a fleet-wide view.
- Because every source is a new
infr_table, it never toucheslegacy_glory(Suricata, Kitsune, …) — it queries via Eloquent directly and needs noSecurityRepository. - 30-second polling (live). The canonical risk-classification logic lives in
App\Support\FirewallLore; the explanatory text inresources/lang/{ko,en}/fwlore.php.
Role boundary: this page looks at the physical open/exposure of ports (attack surface). Logical service reachability (health checks) belongs to Service ▸ Catalog. The same port is viewed differently in each place.
When to use
- To audit the attack surface of the whole fleet — "what ports are visible from outside right now?"
- To find surprise openings — a DB, cache, or remote-admin port that is open but is not a registered service (unmanaged exposure).
- To triage the worst exposure first — crit-risk ports open on hosts that have a public IP.
- To see which hosts open a given port at a glance (port view).
- To review what and how much a single machine exposes (host view).
- To learn why a port is risky and how to narrow it (port-risk lore modal).
Layout
Header (HERO)
Title/subtitle (Ports your fleet leaves open — attack-surface exposure map), a last collected timestamp, a LIVE / PAUSED toggle (30s polling on/off), and a fullscreen button.
Empty state
If there are no firewall rules at all, an onboarding panel appears — a Go to Instances button sends you to /asset/instances. Import a firewall-cmd result from Instances ▸ [fw], or collect open ports via remote emulate, and the map fills in.
KPI band (5 tiles)
| KPI | Label | Definition | Computed as |
|---|---|---|---|
| ports | Open ports | Total open-port rows across all hosts | count of classified rules |
| hosts | Exposed hosts | Machines that open at least one port | distinct instance_id |
| crit | Risky exposure | Ports classified crit | risk == 'crit' |
| unmanaged | Unmanaged exposure | Ports that are NOT a registered service AND whose risk is not ok |
!managed && risk != 'ok' |
| public_crit | Public · crit | Crit ports open on a host that has a public IP | crit && public |
Public · crit is a deliberate double-count. A crit port on a public IP already shows up under
Risky exposureand (if it is not a registered service) underUnmanaged exposuretoo — but because that intersection (public × crit) is the single worst exposure in the fleet, it is surfaced again as its own KPI so nothing slips through. If this tile is non-zero, fix it first.
Exposure risk band
A four-color stacked bar (crit·warn·ok·unknown) shows the risk mix. The legend below is a click filter — click a risk level to keep only that level (click again to clear). Next to the band, coverage (e.g. 7 / 12 hosts collected) is the number of hosts that have firewall rules versus the total instance count.
Toolbar · filters
- View toggle: by port / by host.
- Protocol: all / TCP / UDP.
- Risk: all / crit / warn / ok / unknown.
- Unmanaged only checkbox: only ports that are not registered services.
- Search: matches port · service · host (including alias).
Port view
A grid of port cards. Each card shows port/protocol · service label · risk badge, the host chips that open this port (up to 8 + +N), and a footer with host count · public-exposure count · category. Click a card → port detail + risk lore modal. Sorted by risk → host count → port number.
Host view
A grid of host cards. Each card shows hostname · alias · env · criticality, a public badge, the port chips that machine opens (risk-colored, check-decagram marker if a registered service), and a footer with per-risk counts · total port count. Click a port chip → the same detail modal. Sorted by crit count → warn count → port count.
Port detail + risk lore modal
Port/protocol · service name · risk badge · category, plus the three-part fwlore explanation:
- What (what) — what service this port is.
- Why (why) — what attacks its exposure can lead to.
- Best (best) — how to narrow the exposure.
Below it, the list of hosts that open this port (env · criticality · zone · public/internal · managed/unmanaged badges) and a Manage in Instances ▸ [fw] link.
How to use
- If LIVE is on, the page refreshes every 30 seconds. If moving values distract you during an investigation, switch to PAUSED.
- Check the Public · crit KPI first — if it is non-zero, click
critin the risk legend to narrow the port view, then tackle hosts that carry a public chip (globe icon) first. - Use Unmanaged only to isolate "surprise openings." If a DB (3306/5432/27017), cache (6379/11211), Docker (2375), or remote-admin (23/3389/VNC) that is not a registered service shows up here, narrow it immediately.
- Click a port card, read why/best in the lore modal, then follow
Manage in Instances ▸ [fw]to tighten the rule or register it as a service. - Actually closing a port or editing a rule happens not here but on the target host's firewall plus Instances ▸ [fw]. This page is a map and an audit tool.
Risk classification rules (FirewallLore)
A port string may be a single value (8080) or a range (6081-6100). A range is matched against the well-known map by its starting port.
| risk | rank | meaning | examples |
|---|---|---|---|
| crit | 3 | Immediate danger if exposed — datastore · remote-admin · file-share · container | MySQL 3306, Redis 6379, RDP 3389, SMB 445, Docker 2375, k8s-api 6443 |
| warn | 2 | Watch — management · legacy · non-standard | SSH 22, SMTP 25, 8080/8443, SNMP 161, Grafana 3000 |
| ok | 1 | Standard public service | HTTP 80, HTTPS 443, DNS 53, NTP 123, WireGuard 51820 |
| unknown | 0 | Unmapped — needs review | ports/ranges not in the catalog |
- The VNC band (5900–5910) is handled as a range rather than individual entries and is
remote_admin · crit. - A wide port range (e.g.
6081-6100) that isunknownis promoted to at least warn, because a wide range enlarges the surface. - For an unmapped port, if a service label was supplied it is shown for display but the risk stays
unknown; if there is no label the front-end rendersUnknown service.
Managed determination
The registered-service map is keyed infr_service.instance_id : port. If the instance has a registered service on that port, the rule is managed (check-decagram marker, managed_name). Range ports match on their starting port (low). Without a registered service the rule is unmanaged — and if its risk is not ok, it lands in the Unmanaged exposure KPI.
Data sources
| Table / source | Role | Populated by |
|---|---|---|
infr_instance_firewall |
Per-instance open port/service rules (ledger) | Assets ▸ Instances ▸ [fw] — manual CRUD / firewall-cmd --list-all raw-text import / scripts/infr_firewall_probe.cjs JSON import |
infr_instance |
Host meta — name · alias · env · criticality · status · ip_public |
Assets ▸ Instances |
infr_service |
Registered logical services (instance_id · port · name · kind) → managed check |
Service ▸ Catalog |
App\Support\FirewallLore |
Port → {key,label,category,risk,icon} classification SOT (locale-independent) | code |
resources/lang/{ko,en}/fwlore.php |
Per risk-level and per category what/why/best lore | lang pack |
infr_instance_firewall rule shape (UNIQUE instance_id, zone, proto, port):
{
"zone": "public",
"ports": [
{ "port": "22", "proto": "tcp", "service": "ssh" },
{ "port": "443", "proto": "tcp", "service": "https" },
{ "port": "3306", "proto": "tcp", "service": "mysql" },
{ "port": "6081-6100", "proto": "tcp", "service": "" }
]
}
The [fw] importer also parses raw firewall-cmd --list-all text (zone header → services: mapping → ports: directly):
public (active)
services: http https ssh
ports: 8080/tcp 6081-6100/tcp 3306/tcp
The built dashboard is cached server-side for 30 seconds (infr:sec:firewall:dashboard). With no rules at all it returns an empty payload (empty=true) that renders the onboarding state.
Access
- Viewing: the entire
/security/*prefix is wrapped in theinfr.level:2middleware → staff (5) and above only (internallyuser_level >= 2). demo (1) is blocked from security data entirely. - This page has no writes (pure read). Editing/importing firewall rules happens in Assets ▸ Instances ▸ [fw] and is gated at level ≥ 2 + audit log.
- Level scheme: demo=1 · staff=5 · admin=10 (some areas add further
infr.level:Ngating). See RBAC levels.
Troubleshooting & FAQ
I only see the empty screen ("No firewall data yet").
It means infr_instance_firewall has no rules. Paste a firewall-cmd --list-all result into Assets ▸ Instances ▸ [fw] to import, or collect open ports via remote emulate.
A port shows as Unknown service.
The port is not in the FirewallLore catalog (risk unknown). If it is a legitimate service, register it in Service ▸ Catalog or add a label in the [fw] rule's service/note. If it is unneeded, close it.
It is a registered service but shows unmanaged.
The managed check requires an infr_service on the same instance at that port. If the service lives on a different host or a different port, it will not match. Range ports match only on their starting port.
Risky exposure is non-zero but Public · crit is 0.
It means all hosts with crit ports open are internal-only (no ip_public) — a small exposure radius, so relatively safer. Still, narrow them anyway to account for insider threats and lateral movement.
Values are not real-time. LIVE may be PAUSED or the tab may be backgrounded. The dashboard is cached for 30 seconds server-side, so for up to 30 seconds after you change a rule in [fw] you may still see the old values.
Can I close a port here? No. This page is a read-only map. Actual blocking is done on the target host's firewall, and rule-ledger updates in Instances ▸ [fw].
Related
- Assets ▸ Instances ▸ [fw] — firewall rule ledger · the write side of this map (collect · edit · import)
- Remote emulate · collect open ports via the tlx.php probe
- Service ▸ Catalog · registered services (managed check) · logical reachability
- Assets ▸ Instances · instance meta (public IP · criticality)
- Security ▸ Overview · security console landing (defense funnel)
- Administration ▸ Operations Policy · reuses the FirewallLore port catalog for the "monitored ports" picker
