"""Position awareness: what this instance can see decides what it is. roles = detect(inputs) # inputs: what the operator wired up (flows? http? host listeners? chain rpc?) Roles (several at once are normal): edge-waf HTTP requests arrive (reverse proxy / log tail / inline) -> HTTP tiers, challenge, rate limit host-firewall runs on the host that is protected (can list listeners, can call the firewall) -> posture + block net-sensor sees NetFlow / IPFIX / sFlow or VPC flow logs of a network -> entity stream for every source chain-node sees JSON-RPC and P2P of a blockchain node -> RPC method abuse, peer floods, mempool spam lb-embedded runs inside a Go load balancer (the Go runtime) -> fast path only, decisions from the sidecar The result also says what the layer can *do* here (actions available) so the policy is interpreted correctly. """ from __future__ import annotations import os import shutil import socket from dibaguard.shield.policy import KNOWN_SERVICES CAPABILITIES = { "edge-waf": ["score_http", "challenge", "rate_limit", "block_at_proxy"], "host-firewall": ["posture_audit", "block_at_host", "close_port"], "net-sensor": ["score_flows", "entity_risk", "site_pressure", "report_upstream"], "chain-node": ["score_rpc", "peer_flood", "mempool_spam", "rate_limit_rpc"], "lb-embedded": ["score_http_fast", "block_at_proxy", "rate_limit"], } def _listening_ports() -> list[dict]: """TCP/UDP listeners on this host without psutil: /proc/net on Linux, `netstat -ano` on Windows, `ss` if present.""" out = [] if os.path.exists("/proc/net/tcp"): for proto, path in (("tcp", "/proc/net/tcp"), ("tcp6", "/proc/net/tcp6"), ("udp", "/proc/net/udp")): try: with open(path) as f: next(f) for line in f: parts = line.split() local, state = parts[1], parts[3] if proto.startswith("tcp") and state != "0A": # LISTEN continue addr, port = local.rsplit(":", 1) out.append({"proto": proto[:3], "port": int(port, 16), "addr": addr, "public": addr not in ("0100007F", "00000000000000000000000001000000")}) except OSError: continue elif os.name == "nt" and shutil.which("netstat"): import subprocess try: txt = subprocess.run(["netstat", "-ano"], capture_output=True, text=True, timeout=15).stdout except Exception: # noqa: BLE001 txt = "" for line in txt.splitlines(): p = line.split() if len(p) >= 4 and p[0] in ("TCP", "UDP") and (p[0] == "UDP" or p[3] == "LISTENING"): addr, _, port = p[1].rpartition(":") if port.isdigit(): out.append({"proto": p[0].lower(), "port": int(port), "addr": addr, "public": addr not in ("127.0.0.1", "[::1]")}) seen, uniq = set(), [] for l in out: k = (l["proto"], l["port"], l["public"]) if k not in seen: seen.add(k) uniq.append(dict(l, service=KNOWN_SERVICES.get(l["port"], ""))) return uniq def detect(inputs: dict | None = None) -> dict: """inputs: {"flows": bool, "http": bool, "host": bool, "rpc": bool, "lb": bool} (None = probe what is possible here)""" inputs = dict(inputs or {}) host_ok = inputs.get("host") if host_ok is None: host_ok = os.path.exists("/proc/net/tcp") or (os.name == "nt") roles = [] if inputs.get("http"): roles.append("edge-waf") if host_ok: roles.append("host-firewall") if inputs.get("flows"): roles.append("net-sensor") if inputs.get("rpc"): roles.append("chain-node") if inputs.get("lb"): roles.append("lb-embedded") listeners = _listening_ports() if host_ok else [] if not inputs.get("rpc") and any(l["service"] in ("eth-rpc", "eth-ws", "cosmos-rpc", "bitcoin-rpc") for l in listeners): roles.append("chain-node") # a chain node is running here: its RPC is worth guarding fw = "nftables" if shutil.which("nft") else "iptables" if shutil.which("iptables") else "windows" if os.name == "nt" else "none" caps = sorted({c for r in roles for c in CAPABILITIES[r]}) if fw == "none": caps = [c for c in caps if c not in ("block_at_host", "close_port")] return {"hostname": socket.gethostname(), "roles": roles, "capabilities": caps, "firewall": fw, "listeners": listeners, "inputs": inputs}