templates and data classes are done
This commit is contained in:
102
app/main.py
102
app/main.py
@@ -3,14 +3,13 @@ import logging
|
|||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# REPORT - make darkmode
|
# LOGGING - make better format
|
||||||
# TLS SCANNING
|
# TLS SCANNING
|
||||||
# TLS Version PROBE
|
# TLS Version PROBE
|
||||||
# EMAIL
|
# EMAIL
|
||||||
|
|
||||||
import time
|
import time
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import asdict
|
|
||||||
from ipaddress import ip_address
|
from ipaddress import ip_address
|
||||||
from typing import Any, Dict, List, Set
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
@@ -50,33 +49,29 @@ def results_to_open_sets(
|
|||||||
def build_reports(
|
def build_reports(
|
||||||
scan_config: "ScanConfigFile",
|
scan_config: "ScanConfigFile",
|
||||||
discovered: Dict[str, Dict[str, Set[int]]],
|
discovered: Dict[str, Dict[str, Set[int]]],
|
||||||
) -> Dict[str, Dict[str, List[int]]]:
|
) -> GroupedReports:
|
||||||
"""
|
"""
|
||||||
Create the per-IP delta structure using expected ports from `scan_config.scan_targets`
|
Build per-IP deltas and return a grouped, template-friendly result.
|
||||||
and discovered ports from `discovered`.
|
|
||||||
|
|
||||||
Output format:
|
Returns:
|
||||||
{
|
GroupedReports:
|
||||||
ip: {
|
- issues: hosts with any deltas (sorted by IP)
|
||||||
"unexpected_tcp": [...],
|
- expected: hosts with no deltas (sorted by IP)
|
||||||
"missing_tcp": [...],
|
- by_ip: mapping of ip -> HostReport for random access
|
||||||
"unexpected_udp": [...],
|
|
||||||
"missing_udp": [...]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- If a host has no expected UDP ports in the config, `expected_udp` is empty here.
|
- Works with `scan_config.scan_targets` where each target has:
|
||||||
(This function reflects *expectations*, not what to scan. Your scan logic can still
|
ip, expected_tcp (List[int]), expected_udp (List[int]).
|
||||||
choose 'top UDP ports' for those hosts.)
|
- `discovered` is expected to be { ip: { "tcp": Set[int], "udp": Set[int] } }.
|
||||||
- The `discovered` dict is expected to use keys "tcp" / "udp" per host.
|
Lists are accepted and coerced to sets.
|
||||||
|
- Supports IPv4 and IPv6 sorting. Falls back to string compare if ip parsing fails.
|
||||||
"""
|
"""
|
||||||
# Build `expected` from scan_config.scan_targets
|
# ---- 1) Build expectations from scan_config ----
|
||||||
expected: Dict[str, Dict[str, Set[int]]] = {}
|
expected: Dict[str, Dict[str, Set[int]]] = {}
|
||||||
cfg_targets = getattr(scan_config, "scan_targets", []) or []
|
cfg_targets = getattr(scan_config, "scan_targets", []) or []
|
||||||
|
|
||||||
for t in cfg_targets:
|
for t in cfg_targets:
|
||||||
# Works whether ScanTarget is a dataclass or a dict-like object
|
# Support dataclass-like or dict-like objects
|
||||||
ip = getattr(t, "ip", None) if hasattr(t, "ip") else t.get("ip")
|
ip = getattr(t, "ip", None) if hasattr(t, "ip") else t.get("ip")
|
||||||
if not ip:
|
if not ip:
|
||||||
continue
|
continue
|
||||||
@@ -92,38 +87,63 @@ def build_reports(
|
|||||||
"expected_udp": exp_udp,
|
"expected_udp": exp_udp,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Union of IPs present in either expectations or discoveries
|
# ---- 2) Union of IPs present in either expectations or discoveries ----
|
||||||
all_ips = set(expected.keys()) | set(discovered.keys())
|
all_ips = set(expected.keys()) | set(discovered.keys())
|
||||||
|
|
||||||
reports: Dict[str, Dict[str, List[int]]] = {}
|
# ---- 3) Compute per-host deltas into HostReport objects ----
|
||||||
for ip in sorted(all_ips):
|
by_ip: Dict[str, HostReport] = {}
|
||||||
# Expected sets (default to empty sets if not present)
|
|
||||||
exp_tcp = expected.get(ip, {}).get("expected_tcp", set())
|
|
||||||
exp_udp = expected.get(ip, {}).get("expected_udp", set())
|
|
||||||
|
|
||||||
# Discovered sets (default to empty sets if not present)
|
for ip in all_ips:
|
||||||
|
# Expected sets (default to empty sets if not present)
|
||||||
|
exp_tcp: Set[int] = expected.get(ip, {}).get("expected_tcp", set()) or set()
|
||||||
|
exp_udp: Set[int] = expected.get(ip, {}).get("expected_udp", set()) or set()
|
||||||
|
|
||||||
|
# Discovered sets (default to empty sets if not present); coerce lists -> sets
|
||||||
disc_tcp = discovered.get(ip, {}).get("tcp", set()) or set()
|
disc_tcp = discovered.get(ip, {}).get("tcp", set()) or set()
|
||||||
disc_udp = discovered.get(ip, {}).get("udp", set()) or set()
|
disc_udp = discovered.get(ip, {}).get("udp", set()) or set()
|
||||||
|
|
||||||
# Ensure sets in case caller provided lists
|
|
||||||
if not isinstance(disc_tcp, set):
|
if not isinstance(disc_tcp, set):
|
||||||
disc_tcp = set(disc_tcp)
|
disc_tcp = set(disc_tcp)
|
||||||
if not isinstance(disc_udp, set):
|
if not isinstance(disc_udp, set):
|
||||||
disc_udp = set(disc_udp)
|
disc_udp = set(disc_udp)
|
||||||
|
|
||||||
reports[ip] = {
|
hr = HostReport(
|
||||||
"unexpected_tcp": sorted(disc_tcp - exp_tcp),
|
ip=ip,
|
||||||
"missing_tcp": sorted(exp_tcp - disc_tcp),
|
unexpected_tcp=sorted(disc_tcp - exp_tcp),
|
||||||
"unexpected_udp": sorted(disc_udp - exp_udp),
|
missing_tcp=sorted(exp_tcp - disc_tcp),
|
||||||
"missing_udp": sorted(exp_udp - disc_udp),
|
unexpected_udp=sorted(disc_udp - exp_udp),
|
||||||
}
|
missing_udp=sorted(exp_udp - disc_udp),
|
||||||
|
)
|
||||||
|
by_ip[ip] = hr
|
||||||
|
|
||||||
return reports
|
# ---- 4) Split into issues vs expected ----
|
||||||
|
issues: List[HostReport] = []
|
||||||
|
expected_clean: List[HostReport] = []
|
||||||
|
|
||||||
|
for hr in by_ip.values():
|
||||||
|
if hr.has_issues():
|
||||||
|
issues.append(hr)
|
||||||
|
else:
|
||||||
|
expected_clean.append(hr)
|
||||||
|
|
||||||
|
# ---- 5) Sort both lists by numeric IP (IPv4/IPv6); fallback to string ----
|
||||||
|
def ip_sort_key(hr: HostReport):
|
||||||
|
try:
|
||||||
|
return ip_address(hr.ip)
|
||||||
|
except ValueError:
|
||||||
|
return hr.ip # non-IP strings (unlikely) fall back to lexical
|
||||||
|
|
||||||
|
issues.sort(key=ip_sort_key)
|
||||||
|
expected_clean.sort(key=ip_sort_key)
|
||||||
|
|
||||||
|
return GroupedReports(
|
||||||
|
issues=issues,
|
||||||
|
expected=expected_clean,
|
||||||
|
by_ip=by_ip,
|
||||||
|
)
|
||||||
|
|
||||||
def run_repo_scan(scan_config:ScanConfigFile):
|
def run_repo_scan(scan_config:ScanConfigFile):
|
||||||
logger.info(f"Starting scan for {scan_config.name}")
|
logger.info(f"Starting scan for {scan_config.name}")
|
||||||
logger.info(f"Options: udp={scan_config.scan_options.udp_scan} tls_sec={scan_config.scan_options.tls_security_scan} tls_exp={scan_config.scan_options.tls_exp_check}",)
|
logger.info(f"Options: udp={scan_config.scan_options.udp_scan} tls_sec={scan_config.scan_options.tls_security_scan} tls_exp={scan_config.scan_options.tls_exp_check}",)
|
||||||
logger.info(f"Reporting Options: Dark Mode: {scan_config.reporting.dark_mode}")
|
|
||||||
logger.info("Targets: %d hosts", len(scan_config.scan_targets))
|
logger.info("Targets: %d hosts", len(scan_config.scan_targets))
|
||||||
|
|
||||||
# tack the filename on the end of our data path
|
# tack the filename on the end of our data path
|
||||||
@@ -137,7 +157,13 @@ def run_repo_scan(scan_config:ScanConfigFile):
|
|||||||
if scan_config.reporting.dark_mode:
|
if scan_config.reporting.dark_mode:
|
||||||
template = DARK_TEMPLATE
|
template = DARK_TEMPLATE
|
||||||
|
|
||||||
|
if scan_config.reporting.full_details:
|
||||||
|
show_only_issues = False
|
||||||
|
else:
|
||||||
|
show_only_issues = True
|
||||||
|
|
||||||
logger.info(f"Reporting Template Set to: {template}")
|
logger.info(f"Reporting Template Set to: {template}")
|
||||||
|
logger.info(f"Reporting Only Issues: {show_only_issues}")
|
||||||
|
|
||||||
scanner = nmap_scanner(scan_config)
|
scanner = nmap_scanner(scan_config)
|
||||||
scan_results = scanner.scan_targets()
|
scan_results = scanner.scan_targets()
|
||||||
@@ -150,7 +176,7 @@ def run_repo_scan(scan_config:ScanConfigFile):
|
|||||||
out_path=file_out_path,
|
out_path=file_out_path,
|
||||||
title=scan_config.reporting.report_name,
|
title=scan_config.reporting.report_name,
|
||||||
template_name=template,
|
template_name=template,
|
||||||
only_issues=True)
|
only_issues=show_only_issues)
|
||||||
scanner.cleanup()
|
scanner.cleanup()
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
|
# utils/reporting_jinja.py
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from html import escape
|
from html import escape
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict, List
|
from typing import Any, Dict, List, Set, Union
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||||
|
|
||||||
from utils.models import HostResult
|
from utils.models import HostResult, HostReport, GroupedReports # <-- add HostReport, GroupedReports
|
||||||
|
from ipaddress import ip_address
|
||||||
|
|
||||||
def fmt_ports(ports: List[int]) -> str:
|
def fmt_ports(ports: List[int]) -> str:
|
||||||
if not ports:
|
if not ports:
|
||||||
@@ -45,8 +47,53 @@ def _env(templates_dir: Path) -> Environment:
|
|||||||
env.globals.update(badge=badge, pill_state=pill_state, pill_proto=pill_proto, fmt_ports=fmt_ports)
|
env.globals.update(badge=badge, pill_state=pill_state, pill_proto=pill_proto, fmt_ports=fmt_ports)
|
||||||
return env
|
return env
|
||||||
|
|
||||||
|
|
||||||
|
# --- NEW: compatibility shim so renderer can accept either shape ---
|
||||||
|
|
||||||
|
ReportsArg = Union[GroupedReports, Dict[str, Dict[str, List[int]]]]
|
||||||
|
|
||||||
|
def _coerce_to_grouped(reports: ReportsArg) -> GroupedReports:
|
||||||
|
"""
|
||||||
|
Accept either:
|
||||||
|
- GroupedReports (new, preferred), or
|
||||||
|
- legacy dict: { ip: {"unexpected_tcp":[], "missing_tcp":[], "unexpected_udp":[], "missing_udp":[] } }
|
||||||
|
and return a GroupedReports instance.
|
||||||
|
"""
|
||||||
|
if isinstance(reports, GroupedReports):
|
||||||
|
return reports
|
||||||
|
|
||||||
|
# Legacy dict -> build HostReport objects
|
||||||
|
by_ip: Dict[str, HostReport] = {}
|
||||||
|
for ip, d in reports.items():
|
||||||
|
hr = HostReport(
|
||||||
|
ip=str(ip),
|
||||||
|
unexpected_tcp=list(d.get("unexpected_tcp", []) or []),
|
||||||
|
missing_tcp=list(d.get("missing_tcp", []) or []),
|
||||||
|
unexpected_udp=list(d.get("unexpected_udp", []) or []),
|
||||||
|
missing_udp=list(d.get("missing_udp", []) or []),
|
||||||
|
)
|
||||||
|
by_ip[ip] = hr
|
||||||
|
|
||||||
|
# Split & sort by IP
|
||||||
|
issues: List[HostReport] = []
|
||||||
|
expected: List[HostReport] = []
|
||||||
|
for hr in by_ip.values():
|
||||||
|
(issues if hr.has_issues() else expected).append(hr)
|
||||||
|
|
||||||
|
def _ip_key(hr: HostReport):
|
||||||
|
try:
|
||||||
|
return ip_address(hr.ip)
|
||||||
|
except ValueError:
|
||||||
|
return hr.ip
|
||||||
|
|
||||||
|
issues.sort(key=_ip_key)
|
||||||
|
expected.sort(key=_ip_key)
|
||||||
|
|
||||||
|
return GroupedReports(issues=issues, expected=expected, by_ip=by_ip)
|
||||||
|
|
||||||
|
|
||||||
def render_html_report_jinja(
|
def render_html_report_jinja(
|
||||||
reports: Dict[str, Dict[str, List[int]]],
|
reports: ReportsArg,
|
||||||
host_results: List[HostResult],
|
host_results: List[HostResult],
|
||||||
templates_dir: Path,
|
templates_dir: Path,
|
||||||
template_name: str = "report.html.j2",
|
template_name: str = "report.html.j2",
|
||||||
@@ -56,30 +103,46 @@ def render_html_report_jinja(
|
|||||||
env = _env(templates_dir)
|
env = _env(templates_dir)
|
||||||
template = env.get_template(template_name)
|
template = env.get_template(template_name)
|
||||||
|
|
||||||
total_hosts = len(reports)
|
grouped = _coerce_to_grouped(reports)
|
||||||
hosts_with_issues = [ip for ip, r in reports.items() if any(r.values())]
|
|
||||||
ok_hosts = total_hosts - len(hosts_with_issues)
|
|
||||||
|
|
||||||
# No filtering — we'll show all, but only_issues changes template behavior.
|
total_hosts = len(grouped.by_ip)
|
||||||
by_ip = {hr.address: hr for hr in host_results}
|
ok_hosts = len(grouped.expected)
|
||||||
for hr in by_ip.values():
|
hosts_with_issues = [hr.ip for hr in grouped.issues]
|
||||||
|
|
||||||
|
# Build a mapping of IP -> HostResult and sort port rows for stable output
|
||||||
|
by_ip_results = {hr.address: hr for hr in host_results}
|
||||||
|
for hr in by_ip_results.values():
|
||||||
if hr and hr.ports:
|
if hr and hr.ports:
|
||||||
hr.ports.sort(key=lambda p: (p.protocol, p.port))
|
hr.ports.sort(key=lambda p: (p.protocol, p.port))
|
||||||
|
|
||||||
html = template.render(
|
html = template.render(
|
||||||
title=title,
|
title=title,
|
||||||
generated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
generated=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||||
|
|
||||||
|
# Summary bar
|
||||||
total_hosts=total_hosts,
|
total_hosts=total_hosts,
|
||||||
ok_hosts=ok_hosts,
|
ok_hosts=ok_hosts,
|
||||||
hosts_with_issues=hosts_with_issues,
|
hosts_with_issues=hosts_with_issues,
|
||||||
|
|
||||||
|
# New grouped context for simple “issues first, expected later” loops
|
||||||
|
issues=grouped.issues, # list[HostReport]
|
||||||
|
expected=grouped.expected, # list[HostReport]
|
||||||
|
by_ip=grouped.by_ip, # dict[str, HostReport]
|
||||||
|
|
||||||
|
# Legacy context (kept for compatibility if your template still uses it)
|
||||||
reports=reports,
|
reports=reports,
|
||||||
host_results_by_ip=by_ip,
|
|
||||||
|
# Host scan details
|
||||||
|
host_results_by_ip=by_ip_results,
|
||||||
|
|
||||||
|
# Existing behavior switch
|
||||||
only_issues=only_issues,
|
only_issues=only_issues,
|
||||||
)
|
)
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
def write_html_report_jinja(
|
def write_html_report_jinja(
|
||||||
reports: Dict[str, Dict[str, List[int]]],
|
reports: ReportsArg,
|
||||||
host_results: List[HostResult],
|
host_results: List[HostResult],
|
||||||
out_path: Path,
|
out_path: Path,
|
||||||
templates_dir: Path = Path("templates"),
|
templates_dir: Path = Path("templates"),
|
||||||
|
|||||||
@@ -54,32 +54,9 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# ===== Host Sections ===== #}
|
{# ===== Host Sections (Issues first, then Expected) ===== #}
|
||||||
{% for ip_key, r in reports|dictsort %}
|
|
||||||
{% set has_issues = r.unexpected_tcp or r.missing_tcp or r.unexpected_udp or r.missing_udp %}
|
|
||||||
{% set hr = host_results_by_ip.get(ip_key) %}
|
|
||||||
{% set header_title = ip_key ~ ((' (' ~ hr.host ~ ')') if hr and hr.host else '') %}
|
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
|
||||||
|
|
||||||
{{ header_title }}
|
|
||||||
{% if has_issues %}
|
|
||||||
{{ badge('ISSUES', '#ef4444') }}
|
|
||||||
{% else %}
|
|
||||||
{{ badge('OK', '#16a34a') }}
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% if has_issues %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
|
||||||
{{ badge('ISSUES', '#ef4444') }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
{# A small macro reused for delta lines #}
|
||||||
{% macro delta_row(label, ports) -%}
|
{% macro delta_row(label, ports) -%}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
@@ -89,6 +66,27 @@
|
|||||||
</tr>
|
</tr>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{# ---------- 1) Issues (already sorted by IP in Python) ---------- #}
|
||||||
|
{% for hr in issues %}
|
||||||
|
{% set ip_key = hr.ip %}
|
||||||
|
{% set r = hr %} {# keep "r" alias so the rest of the block looks familiar #}
|
||||||
|
{% set has_issues = true %}
|
||||||
|
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||||
|
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||||
|
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
||||||
|
{{ header_title }} {{ badge('ISSUES', '#ef4444') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||||
|
{{ badge('ISSUES', '#ef4444') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||||
@@ -107,19 +105,14 @@
|
|||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
|
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% if hr and hr.ports %}
|
{% if host_row and host_row.ports %}
|
||||||
{# Track visible rows after filtering (e.g., skip closed UDP) #}
|
|
||||||
{% set ns = namespace(visible_rows=0) %}
|
{% set ns = namespace(visible_rows=0) %}
|
||||||
|
|
||||||
{% for p in hr.ports %}
|
{% for p in host_row.ports %}
|
||||||
{# --- Normalize helpers --- #}
|
|
||||||
{% set proto = (p.protocol|string|lower) %}
|
{% set proto = (p.protocol|string|lower) %}
|
||||||
{% set state = (p.state|string|lower) %}
|
{% set state = (p.state|string|lower) %}
|
||||||
|
|
||||||
{# --- Skip rule: hide UDP rows that are "closed" (substring match) --- #}
|
|
||||||
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
||||||
|
|
||||||
{# --- Expectation labeling --- #}
|
|
||||||
{% set is_tcp = (proto == 'tcp') %}
|
{% set is_tcp = (proto == 'tcp') %}
|
||||||
{% set is_udp = (proto == 'udp') %}
|
{% set is_udp = (proto == 'udp') %}
|
||||||
{% set is_open = ('open' in state) %}
|
{% set is_open = ('open' in state) %}
|
||||||
@@ -158,16 +151,34 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
</table>
|
||||||
{# Host has no issues #}
|
{% endfor %}
|
||||||
|
|
||||||
|
{# ---------- 2) Expected / OK hosts (only if not only_issues) ---------- #}
|
||||||
|
{% if not only_issues %}
|
||||||
|
{% for hr in expected %}
|
||||||
|
{% set ip_key = hr.ip %}
|
||||||
|
{% set r = hr %}
|
||||||
|
{% set has_issues = false %}
|
||||||
|
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||||
|
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||||
|
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
||||||
|
{{ header_title }} {{ badge('OK', '#16a34a') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||||
{{ badge('OK', '#16a34a') }} <span style="color:#cbd5e1">Matches expected ports.</span>
|
{{ badge('OK', '#16a34a') }} <span style="color:#cbd5e1">Matches expected ports.</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
|
||||||
</table>
|
</table>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
|
||||||
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
|
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
|
||||||
Report generated by mass-scan-v2 • {{ generated }}
|
Report generated by mass-scan-v2 • {{ generated }}
|
||||||
|
|||||||
@@ -38,41 +38,44 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{# Host sections #}
|
{# ===== Host Sections (LIGHT theme) ===== #}
|
||||||
{% for ip_key, r in reports|dictsort %}
|
|
||||||
{% set has_issues = r.unexpected_tcp or r.missing_tcp or r.unexpected_udp or r.missing_udp %}
|
|
||||||
{% set hr = host_results_by_ip.get(ip_key) %}
|
|
||||||
{% set header_title = ip_key ~ ((' (' ~ hr.host ~ ')') if hr and hr.host else '') %}
|
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
|
||||||
{{ header_title }} {% if has_issues %} {{ badge('ISSUES', '#ef4444') }} {% else %} {{ badge('OK', '#16a34a') }} {% endif %}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% if has_issues %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" style="padding:10px 10px 6px 10px;font-size:13px">
|
|
||||||
{{ badge('ISSUES', '#ef4444') }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
|
{# Reusable delta row #}
|
||||||
{% macro delta_row(label, ports) -%}
|
{% macro delta_row(label, ports) -%}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
<strong>{{ label }}:</strong> {{ fmt_ports(ports) }}
|
<strong>{{ label }}:</strong> {{ fmt_ports(ports) }}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{%- endmacro %}
|
{%- endmacro %}
|
||||||
|
|
||||||
|
{# ---------- 1) Issues first (already IP-sorted in Python) ---------- #}
|
||||||
|
{% for hr in issues %}
|
||||||
|
{% set ip_key = hr.ip %}
|
||||||
|
{% set r = hr %}
|
||||||
|
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||||
|
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||||
|
|
||||||
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||||
|
{{ header_title }} {{ badge('ISSUES', '#ef4444') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:10px 10px 6px 10px;font-size:13px">
|
||||||
|
{{ badge('ISSUES', '#ef4444') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
{{ delta_row('Unexpected TCP open ports', r.unexpected_tcp) }}
|
||||||
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
{{ delta_row('Expected TCP ports not seen', r.missing_tcp) }}
|
||||||
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
{{ delta_row('Unexpected UDP open ports', r.unexpected_udp) }}
|
||||||
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
{{ delta_row('Expected UDP ports not seen', r.missing_udp) }}
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="4" style="padding:8px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||||
<div style="font-weight:600;margin:8px 0">Discovered Ports</div>
|
<div style="font-weight:600;margin:8px 0">Discovered Ports</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -84,19 +87,14 @@
|
|||||||
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{% if hr and hr.ports %}
|
{% if host_row and host_row.ports %}
|
||||||
{# Track whether we rendered any visible rows after filtering #}
|
|
||||||
{% set ns = namespace(visible_rows=0) %}
|
{% set ns = namespace(visible_rows=0) %}
|
||||||
|
|
||||||
{% for p in hr.ports %}
|
{% for p in host_row.ports %}
|
||||||
{# Normalize helpers #}
|
|
||||||
{% set proto = (p.protocol|string|lower) %}
|
{% set proto = (p.protocol|string|lower) %}
|
||||||
{% set state = (p.state|string|lower) %}
|
{% set state = (p.state|string|lower) %}
|
||||||
|
|
||||||
{# Skip rule: hide UDP rows that are 'closed' #}
|
|
||||||
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
{% set skip_row = (proto == 'udp' and ('closed' in state)) %}
|
||||||
|
|
||||||
{# Compute expectation labeling (only matters for displayed rows) #}
|
|
||||||
{% set is_tcp = (proto == 'tcp') %}
|
{% set is_tcp = (proto == 'tcp') %}
|
||||||
{% set is_udp = (proto == 'udp') %}
|
{% set is_udp = (proto == 'udp') %}
|
||||||
{% set is_open = ('open' in state) %}
|
{% set is_open = ('open' in state) %}
|
||||||
@@ -121,7 +119,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{# If everything was filtered out, show a friendly note #}
|
|
||||||
{% if ns.visible_rows == 0 %}
|
{% if ns.visible_rows == 0 %}
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
|
<td colspan="5" style="padding:8px 10px;border:1px solid #e5e7eb;color:#64748b">
|
||||||
@@ -136,18 +133,31 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
|
||||||
{# Host has no issues #}
|
|
||||||
<tr>
|
|
||||||
<td colspan="4" style="padding:10px 10px 8px 10px;font-size:13px">
|
|
||||||
{{ badge('OK', '#16a34a') }} Matches expected ports.
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
</table>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
|
{# ---------- 2) Expected / OK (only if not only_issues) ---------- #}
|
||||||
|
{% if not only_issues %}
|
||||||
|
{% for hr in expected %}
|
||||||
|
{% set ip_key = hr.ip %}
|
||||||
|
{% set host_row = host_results_by_ip.get(ip_key) %}
|
||||||
|
{% set header_title = ip_key ~ ((' (' ~ host_row.host ~ ')') if host_row and host_row.host else '') %}
|
||||||
|
|
||||||
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||||
|
{{ header_title }} {{ badge('OK', '#16a34a') }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||||
|
{{ badge('OK', '#16a34a') }} Matches expected ports.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
|
||||||
Report generated by mass-scan-v2 • {{ generated }}
|
Report generated by mass-scan-v2 • {{ generated }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -162,6 +162,7 @@ class ScanConfigRepository:
|
|||||||
report_name=str(rep_raw.get("report_name", "Scan Report")),
|
report_name=str(rep_raw.get("report_name", "Scan Report")),
|
||||||
report_filename=str(rep_raw.get("report_filename", "report.html")),
|
report_filename=str(rep_raw.get("report_filename", "report.html")),
|
||||||
full_details=bool(rep_raw.get("full_details", False)),
|
full_details=bool(rep_raw.get("full_details", False)),
|
||||||
|
dark_mode = bool(rep_raw.get("dark_mode", False)),
|
||||||
email_to=self._as_str_list(rep_raw.get("email_to", []), "email_to"),
|
email_to=self._as_str_list(rep_raw.get("email_to", []), "email_to"),
|
||||||
email_cc=self._as_str_list(rep_raw.get("email_cc", []), "email_cc"),
|
email_cc=self._as_str_list(rep_raw.get("email_cc", []), "email_cc"),
|
||||||
)
|
)
|
||||||
@@ -236,7 +237,6 @@ class ScanConfigRepository:
|
|||||||
raise TypeError(f"'{field_name}' must be a string or a list of strings.")
|
raise TypeError(f"'{field_name}' must be a string or a list of strings.")
|
||||||
|
|
||||||
# helpers
|
# helpers
|
||||||
|
|
||||||
def list_configs(self) -> List[str]:
|
def list_configs(self) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Return names of loaded configs for UI selection.
|
Return names of loaded configs for UI selection.
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ class nmap_scanner:
|
|||||||
Run a command and raise on non-zero exit with a readable message.
|
Run a command and raise on non-zero exit with a readable message.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
subprocess.run(cmd, check=True)
|
subprocess.run(cmd, check=True,stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
except subprocess.CalledProcessError as exc:
|
except subprocess.CalledProcessError as exc:
|
||||||
raise RuntimeError(f"Command failed ({exc.returncode}): {' '.join(cmd)}") from exc
|
raise RuntimeError(f"Command failed ({exc.returncode}): {' '.join(cmd)}") from exc
|
||||||
|
|
||||||
|
|||||||
@@ -5,35 +5,23 @@
|
|||||||
<title>Corporate WAN Perimeter</title>
|
<title>Corporate WAN Perimeter</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
</head>
|
</head>
|
||||||
<body style="margin:0;padding:16px;background:#0b1020;color:#e5e7eb">
|
<body style="margin:0;padding:16px;background:#ffffff">
|
||||||
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
|
<div style="max-width:860px;margin:0 auto;font-family:Segoe UI,Arial,sans-serif">
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 12px 0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:14px 16px;background:#1e293b;border:1px solid #334155;border-radius:10px;color:#f1f5f9">
|
<td style="padding:10px;background:#0f172a;border-radius:10px;color:#e2e8f0">
|
||||||
<div style="font-size:20px;font-weight:700;margin-bottom:4px">Corporate WAN Perimeter</div>
|
<div style="font-size:18px;font-weight:700;margin-bottom:4px">Corporate WAN Perimeter</div>
|
||||||
<div style="font-size:12px;color:#94a3b8">Generated: 2025-10-22 01:45:32</div>
|
<div style="font-size:12px;color:#94a3b8">Generated: 2025-10-22 02:33:35</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 16px 0">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="
|
<td style="padding:10px;border:1px solid #e5e7eb;border-radius:8px">
|
||||||
padding:12px 16px;
|
Total hosts: <strong>3</strong>
|
||||||
border:1px solid #334155;
|
Matching expected: <strong>2</strong>
|
||||||
border-radius:8px;
|
With issues: <strong>1</strong>
|
||||||
background:#1e293b;
|
|
||||||
color:#f1f5f9;
|
|
||||||
font-size:14px;
|
|
||||||
">
|
|
||||||
Total hosts:
|
|
||||||
<strong style="color:#f8fafc">3</strong>
|
|
||||||
Matching expected:
|
|
||||||
<strong style="color:#4ade80">2</strong>
|
|
||||||
With issues:
|
|
||||||
<strong style="color:#f87171">1</strong>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -42,12 +30,11 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
|
||||||
|
|
||||||
10.10.20.12
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||||
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">ISSUES</span>
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||||
|
10.10.20.12 <span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">ISSUES</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
@@ -57,134 +44,116 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
<strong style="color:#e5e7eb">Unexpected TCP open ports:</strong>
|
<strong>Unexpected TCP open ports:</strong> 3128
|
||||||
<span style="color:#cbd5e1">3128</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
<strong style="color:#e5e7eb">Expected TCP ports not seen:</strong>
|
<strong>Expected TCP ports not seen:</strong> none
|
||||||
<span style="color:#cbd5e1">none</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
<strong style="color:#e5e7eb">Unexpected UDP open ports:</strong>
|
<strong>Unexpected UDP open ports:</strong> none
|
||||||
<span style="color:#cbd5e1">none</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:4px 10px 6px 10px;font-size:13px">
|
||||||
<strong style="color:#e5e7eb">Expected UDP ports not seen:</strong>
|
<strong>Expected UDP ports not seen:</strong> none
|
||||||
<span style="color:#cbd5e1">none</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
<td colspan="5" style="padding:8px 10px 6px 10px;font-size:13px">
|
||||||
<div style="font-weight:600;margin:8px 0;color:#e5e7eb">Discovered Ports</div>
|
<div style="font-weight:600;margin:8px 0">Discovered Ports</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Protocol</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Protocol</td>
|
||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Port</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Port</td>
|
||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">State</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">State</td>
|
||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Service</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Service</td>
|
||||||
<td style="padding:8px 10px;border:1px solid #1f2937;background:#111827;font-weight:600;color:#e5e7eb">Expectation</td>
|
<td style="padding:8px 10px;border:1px solid #e5e7eb;background:#f8fafc;font-weight:600">Expectation</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">22</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">22</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">ssh</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">ssh</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">111</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">111</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">rpcbind</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">rpcbind</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">3128</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">3128</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">squid-http</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">squid-http</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Issue</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#ef4444;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Issue</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">tcp</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">8006</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">8006</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">wpl-analytics</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">wpl-analytics</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">udp</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#334155;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">udp</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#e5e7eb">111</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">111</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">open</span></td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020;color:#cbd5e1">rpcbind</td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb">rpcbind</td>
|
||||||
<td style="padding:6px 10px;border:1px solid #1f2937;background:#0b1020"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
<td style="padding:6px 10px;border:1px solid #e5e7eb"><span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">Expected</span></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
|
||||||
<tr>
|
|
||||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
|
||||||
|
|
||||||
10.10.20.4
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||||
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
|
<tr>
|
||||||
|
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||||
|
10.10.20.4 <span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||||
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> <span style="color:#cbd5e1">Matches expected ports.</span>
|
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> Matches expected ports.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0;border-collapse:separate;border-spacing:0">
|
<table role="presentation" cellpadding="0" cellspacing="0" width="100%" style="margin:0 0 18px 0">
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:12px 10px;background:#1e293b;color:#f1f5f9;font-weight:600;font-size:14px;border-radius:8px;border:1px solid #334155">
|
<td colspan="5" style="padding:12px 10px;background:#0f172a;color:#e2e8f0;font-weight:600;font-size:14px;border-radius:8px">
|
||||||
|
10.10.20.5 <span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
|
||||||
10.10.20.5
|
|
||||||
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span>
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<tr>
|
<tr>
|
||||||
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
<td colspan="5" style="padding:10px 10px 8px 10px;font-size:13px">
|
||||||
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> <span style="color:#cbd5e1">Matches expected ports.</span>
|
<span style="display:inline-block;padding:2px 6px;border-radius:12px;font-size:12px;line-height:16px;background:#16a34a;color:#ffffff;font-family:Segoe UI,Arial,sans-serif">OK</span> Matches expected ports.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div style="margin-top:18px;font-size:11px;color:#9ca3af">
|
<div style="margin-top:18px;font-size:11px;color:#94a3b8">
|
||||||
Report generated by mass-scan-v2 • 2025-10-22 01:45:32
|
Report generated by mass-scan-v2 • 2025-10-22 02:33:35
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -8,8 +8,8 @@ scan_options:
|
|||||||
reporting:
|
reporting:
|
||||||
report_name: Corporate WAN Perimeter
|
report_name: Corporate WAN Perimeter
|
||||||
report_filename: corp-wan.html
|
report_filename: corp-wan.html
|
||||||
dark_mode: true
|
|
||||||
full_details: true
|
full_details: true
|
||||||
|
dark_mode: false
|
||||||
email_to: soc@example.com # single string is fine; or a list
|
email_to: soc@example.com # single string is fine; or a list
|
||||||
email_cc: [] # explicitly none
|
email_cc: [] # explicitly none
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user