netsleuth/config.yaml
Andrew Pimenov 99876103f8 verify TLS certificates and fingerprint them for MITM detection
tls_context() now defaults to real certificate verification instead of the
tool's original always-CERT_NONE posture; measure_tls() only falls back to
unverified on ssl.SSLCertVerificationError, retiming the retry since the
failed attempt's duration isn't representative. This is also the only way
to get subject/issuer/notAfter at all - getpeercert() returns {} for a
chain that never validated.

Every TLS result now carries cert_sha256 (SHA-256 of the full DER cert, not
the SPKI - that would need the cryptography package, a dependency this
doesn't need), cert_issuer, cert_days_remaining, and the long-dead
cert_verified field finally gets written. New tls.pinned_fingerprints
config (host -> expected hash, capped and hex-validated) drives three new
findings: a pin mismatch is crit, an unpinned validation failure is warn,
an expiring cert is warn.

Live-tested with --tls against real Cloudflare/Google endpoints - verified
handshake, correct issuer and expiry both times.

Part of plans/zesty-zooming-wilkinson.md (gitignored).
2026-08-12 21:48:36 +03:00

146 lines
4.9 KiB
YAML

timeouts:
http_seconds: 8.0
module_seconds: 30.0
speedtest_seconds: 90.0
dns_seconds: 4.0
subprocess_seconds: 60.0
probing:
reference_hosts:
- { label: "cloudflare-dns", host: "1.1.1.1" }
- { label: "google-dns", host: "8.8.8.8" }
- { label: "quad9-dns", host: "9.9.9.9" }
service_hosts:
- { label: "cloudflare", host: "cloudflare.com" }
- { label: "google", host: "google.com" }
- { label: "github", host: "github.com" }
ping_count: 20
quick_ping_count: 5
ping_interval_seconds: 0.25
ping_timeout_seconds: 2.0
mtr_cycles: 10
quick_mtr_cycles: 1
max_hops: 30
trace_concurrency: 2
speedtest:
enabled_tiers: ["ookla_bin", "cloudflare", "fastcom"]
download_sizes_bytes: [1000000, 10000000, 25000000]
upload_sizes_bytes: [1000000, 5000000]
cloudflare_base_url: "https://speed.cloudflare.com"
fastcom_api_url: "https://api.fast.com/netflix/speedtest/v2"
ndt7_locate_url: "https://locate.measurementlab.net/v2/nearest/ndt/ndt7"
bufferbloat_probe_interval_seconds: 0.2
providers:
cf_trace_url: "https://www.cloudflare.com/cdn-cgi/trace"
ip_api_url: "http://ip-api.com/json/"
freeipapi_url: "https://freeipapi.com/api/json/"
ipinfo_url: "https://ipinfo.io/"
ipwhois_url: "https://ipwho.is/"
ripestat_base_url: "https://stat.ripe.net/data"
asrank_url: "https://api.asrank.caida.org/v2/graphql"
peeringdb_base_url: "https://www.peeringdb.com/api"
internetdb_url: "https://internetdb.shodan.io/"
abuseipdb_url: "https://api.abuseipdb.com/api/v2/check"
cymru_origin_zone: "origin.asn.cymru.com"
cymru_origin6_zone: "origin6.asn.cymru.com"
cymru_asn_zone: "asn.cymru.com"
ripestat_max_rows: 200
ripestat_timeframe_days: 14
peeringdb_cache_hours: 24
firehol_refresh_hours: 24
firehol_netsets:
- "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level1.netset"
- "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_level2.netset"
- "https://raw.githubusercontent.com/firehol/blocklist-ipsets/master/firehol_abusers_30d.netset"
dnsbl:
zones:
- "zen.spamhaus.org"
- "bl.spamcop.net"
- "b.barracudacentral.org"
- "dnsbl.dronebl.org"
thresholds:
latency_ms: { good: 40.0, warn: 100.0 }
jitter_ms: { good: 5.0, warn: 20.0 }
loss_pct: { good: 0.0, warn: 2.0 }
bufferbloat_ms: { a: 5.0, b: 30.0, c: 60.0, d: 200.0, e: 400.0 }
vpn_confidence: { likely: 0.40, confirmed: 0.75 }
tls_handshake_ms: { good: 100.0, warn: 300.0 }
ttfb_ms: { good: 200.0, warn: 800.0 }
dns_resolve_ms: { good: 30.0, warn: 120.0 }
prefix_spread_ms: { good: 20.0, warn: 80.0 }
tls_cpu_bound_ratio: 2.0
first_hop_ms: { good: 5.0, warn: 25.0 }
tls:
targets:
- { label: "cloudflare", host: "cloudflare.com" }
- { label: "google", host: "google.com" }
port: 443
concurrency: 4
ttfb_path: "/"
verify: true
# host -> expected SHA-256 of the full DER certificate (openssl x509 -outform DER
# | sha256sum). Colons/case are normalized. Capped at 32 entries in code.
pinned_fingerprints: {}
# Opt-in via --prefix-bench. Pings the first host of a handful of your own AS's
# announced prefixes to find the lowest-latency PoP. max_prefixes is capped at 256
# in code: this benchmarks a sample of your own network, not a mass scan of it.
prefix_bench:
max_prefixes: 32
ping_count: 3
concurrency: 8
host_offset: 1
family: 4
# Opt-in via --my-server <host>. Single-host self-diagnostic for provider port
# blocking/TCP RST injection. ports/concurrency are capped in code so this cannot
# become a range scanner: never point it at infrastructure you don't own.
dpi_check:
ports: [80, 443, 8443, 2083, 2096, 53]
connect_timeout_seconds: 3.0
delay_between_ports_seconds: 0.25
concurrency: 2
dns_advanced:
control_names: ["cloudflare.com", "example.com"]
doh_endpoints:
- { name: "cloudflare", url: "https://cloudflare-dns.com/dns-query" }
- { name: "google", url: "https://dns.google/resolve" }
- { name: "quad9", url: "https://dns.quad9.net:5053/dns-query" }
bogus_resolver_ip: "192.0.2.1"
bogus_probe_name: "cloudflare.com"
path_diversity:
targets:
- { label: "cloudflare", host: "cloudflare.com" }
- { label: "discord", host: "discord.com" }
max_targets: 3
# Runs first, before any other probe. Checks a handful of known generate_204-style
# endpoints for a redirect/tampered response, the classic signature of a public-wifi
# login wall. check_urls is capped at 4 in code.
captive_portal:
enabled: true
check_urls:
- "http://cp.cloudflare.com/generate_204"
- "http://connectivitycheck.gstatic.com/generate_204"
- "http://clients3.google.com/generate_204"
expected_status: 204
output:
logs_dir: "./logs"
cache_dir: "./.cache"
emoji: true
# Which report artifacts a run writes by default: md, ru-md, json, or all.
# Overridden per run by --format/--ru/--json.
formats: ["md"]
watch:
interval_seconds: 60
speedtest_every_n_cycles: 10
dashboard_refresh_hz: 4