Download runtime/python/dibaguard/shield/policy.py from Dibachain/DibaGuard: direct link, hf CLI and curl.
- Browser
- Download file 8.01 kB
-
https://huggingface.co/Dibachain/DibaGuard/resolve/main/runtime/python/dibaguard/shield/policy.py
- Command line
-
hf download hf://Dibachain/DibaGuard/runtime/python/dibaguard/shield/policy.py
-
curl -L -o policy.py https://huggingface.co/Dibachain/DibaGuard/resolve/main/runtime/python/dibaguard/shield/policy.py
8.01 kB
| """Policy: the operator's contract with the layer. JSON, versioned, validated, with safe defaults (report only). | |
| Example (policy.json): | |
| { | |
| "mode": "report", # report | enforce | |
| "alpha": 0.001, # false-alarm budget per benign (source, minute) | |
| "actions": {"portscan": "block", "bruteforce": "block", "dos": "rate_limit", "ddos": "rate_limit", | |
| "webattack": "block", "lateral": "block", "bot": "challenge", "recon": "monitor", "*": "monitor"}, | |
| "min_action_score": 60, # 0-100 event score below which only 'monitor' is taken | |
| "block_ttl_s": 1800, "rate_limit_ttl_s": 600, "challenge_ttl_s": 900, | |
| "allow": ["10.0.0.0/8", "192.168.0.0/16"], # never acted upon (still reported) | |
| "posture": {"allowed_ports": [22, 80, 443], "close_unexpected": false, "deny_public_services": ["redis", "mongodb", "elasticsearch", "memcached", "docker"]}, | |
| "report": {"every_s": 300, "llm": false} | |
| } | |
| """ | |
| from __future__ import annotations | |
| import ipaddress | |
| import json | |
| import os | |
| from dibaguard.shield.database import DB_PORTS | |
| ACTIONS = ("monitor", "rate_limit", "challenge", "block") | |
| DEFAULT = { | |
| "version": 1, "mode": "report", "alpha": 1e-3, | |
| "actions": {"portscan": "block", "bruteforce": "block", "dos": "rate_limit", "ddos": "rate_limit", "webattack": "block", "lateral": "block", | |
| "sqli": "block", "xss": "block", "cmdi": "block", "rce": "block", "traversal": "block", "lfi": "block", | |
| "nosqli": "block", "ssti": "block", "ssrf": "block", "xxe": "block", | |
| "bot": "challenge", "scan": "challenge", "recon": "monitor", "attack_generic": "monitor", | |
| "db_rce": "block", "db_privilege": "block", "db_destructive": "block", "db_injection": "block", "db_exfil": "rate_limit", | |
| "db_bruteforce": "block", "db_recon": "monitor", "db_new_client": "monitor", "db_unbounded": "monitor", "*": "monitor"}, | |
| "min_action_score": 60, "block_ttl_s": 1800, "rate_limit_ttl_s": 600, "challenge_ttl_s": 900, | |
| "allow": ["127.0.0.0/8", "::1/128"], | |
| "posture": {"allowed_ports": [22, 80, 443], "close_unexpected": False, | |
| "deny_public_services": ["redis", "mongodb", "elasticsearch", "memcached", "docker", "mysql", "postgres", "rdp", "smb", "vnc", | |
| "mssql", "oracle", "cassandra", "cassandra-internode", "cassandra-jmx", "couchdb", "neo4j", "clickhouse", | |
| "cockroach", "influxdb", "kafka", "zookeeper", "etcd", "rethinkdb", "arangodb"]}, | |
| # databases: the layer reads their query / audit logs (shield/database.py). An application server the database | |
| # knows is never blocked at the network level unless block_app_clients is set: that would take the app down - | |
| # its alarm carries the remediation to run on the database, and the web source behind it is acted on instead. | |
| "database": {"block_app_clients": False, "correlate_window_s": 10, "auth_fail_floor": 8, "exempt_shapes": [], "learn_new_clients": False}, | |
| "report": {"every_s": 300, "llm": False}, | |
| # search-engine crawlers recognised by user-agent AND their published address ranges: watched, never acted on as an | |
| # address (each request is still judged - a payload in a crawled link is refused inline). Refresh the ranges from | |
| # the engines' published lists (developers.google.com/search/apis/ipranges/googlebot.json, Bing's bingbot.json) | |
| "verified_crawlers": [{"name": "googlebot", "ua": "googlebot", "cidrs": ["66.249.64.0/19"]}, | |
| {"name": "bingbot", "ua": "bingbot", "cidrs": ["157.55.39.0/24", "207.46.13.0/24", "40.77.167.0/24"]}], | |
| # a client that is not a browser (API integrations, crawlers) cannot pass a JavaScript challenge: a behavioural | |
| # challenge on it becomes this action instead (a challenge would cut it off) | |
| "machine_challenge": "rate_limit", | |
| } | |
| KNOWN_SERVICES = {6379: "redis", 27017: "mongodb", 9200: "elasticsearch", 11211: "memcached", 2375: "docker", 2376: "docker", | |
| 3306: "mysql", 5432: "postgres", 3389: "rdp", 445: "smb", 139: "smb", 5900: "vnc", 23: "telnet", 21: "ftp", | |
| 25: "smtp", 53: "dns", 22: "ssh", 80: "http", 443: "https", 8080: "http-alt", 8443: "https-alt", 9090: "prometheus", | |
| 5601: "kibana", 15672: "rabbitmq", 8545: "eth-rpc", 8546: "eth-ws", 30303: "eth-p2p", 26656: "cosmos-p2p", | |
| 26657: "cosmos-rpc", 8333: "bitcoin-p2p", 8332: "bitcoin-rpc"} | |
| KNOWN_SERVICES.update({p: eng for p, (eng, _) in DB_PORTS.items() if p not in KNOWN_SERVICES}) | |
| class Policy: | |
| def __init__(self, data: dict | None = None): | |
| self.d = json.loads(json.dumps(DEFAULT)) | |
| for k, v in (data or {}).items(): | |
| if isinstance(v, dict) and isinstance(self.d.get(k), dict): | |
| self.d[k].update(v) | |
| else: | |
| self.d[k] = v | |
| self.validate() | |
| self._allow = [ipaddress.ip_network(a, strict=False) for a in self.d["allow"]] | |
| self._crawlers = [(str(c["ua"]).lower(), [ipaddress.ip_network(x, strict=False) for x in c.get("cidrs", [])]) | |
| for c in self.d.get("verified_crawlers", [])] | |
| def load(cls, path: str | None) -> "Policy": | |
| if path and os.path.exists(path): | |
| with open(path, encoding="utf-8") as f: | |
| return cls(json.load(f)) | |
| return cls() | |
| def validate(self) -> None: | |
| d = self.d | |
| if d["mode"] not in ("report", "enforce"): | |
| raise ValueError(f"policy.mode must be report|enforce, got {d['mode']!r}") | |
| if not 0 < float(d["alpha"]) < 0.5: | |
| raise ValueError("policy.alpha must be in (0, 0.5)") | |
| for fam, act in d["actions"].items(): | |
| if act not in ACTIONS: | |
| raise ValueError(f"policy.actions[{fam!r}] must be one of {ACTIONS}, got {act!r}") | |
| for k in ("block_ttl_s", "rate_limit_ttl_s", "challenge_ttl_s", "min_action_score"): | |
| if float(d[k]) < 0: | |
| raise ValueError(f"policy.{k} must be >= 0") | |
| for a in d["allow"]: | |
| ipaddress.ip_network(a, strict=False) | |
| for c in d.get("verified_crawlers", []): | |
| if not c.get("ua") or not c.get("cidrs"): | |
| raise ValueError("policy.verified_crawlers entries need 'ua' and 'cidrs' (both must match)") | |
| for x in c["cidrs"]: | |
| ipaddress.ip_network(x, strict=False) | |
| if d.get("machine_challenge", "rate_limit") not in ACTIONS: | |
| raise ValueError(f"policy.machine_challenge must be one of {ACTIONS}") | |
| # -- decisions | |
| def allowed(self, ip: str) -> bool: | |
| try: | |
| addr = ipaddress.ip_address(ip) | |
| except ValueError: | |
| return False | |
| return any(addr in net for net in self._allow) | |
| def verified_crawler(self, ip: str, ua: str) -> bool: | |
| """the user-agent names a listed crawler AND the address is in that crawler's published ranges""" | |
| ua = ua.lower() | |
| try: | |
| addr = ipaddress.ip_address(str(ip).split("#", 1)[0]) | |
| except ValueError: | |
| return False | |
| return any(tok in ua and any(addr in n for n in nets) for tok, nets in self._crawlers) | |
| def action_for(self, family: str, score: int, ip: str | None = None) -> str: | |
| """what the policy lets the layer do about one verdict (mode 'report' still returns the would-be action)""" | |
| if ip is not None and self.allowed(ip): | |
| return "monitor" | |
| if score < int(self.d["min_action_score"]): | |
| return "monitor" | |
| return self.d["actions"].get(family, self.d["actions"].get("*", "monitor")) | |
| def ttl(self, action: str) -> float: | |
| return float(self.d.get(f"{action}_ttl_s", 0.0)) | |
| def enforcing(self) -> bool: | |
| return self.d["mode"] == "enforce" | |
| def dump(self) -> str: | |
| return json.dumps(self.d, ensure_ascii=False, indent=1) | |