fix json encoding
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 11s

This commit is contained in:
2026-02-09 20:36:49 +01:00
parent 7d7008d969
commit 69abb95140

View File

@@ -3,7 +3,7 @@
Unrestricted nftables FastAPI service using textual nft commands only.
Endpoints (high level):
GET /firewall/rules -> return textual ruleset (raw nft output)
GET /firewall/rules -> return textual ruleset (raw nft output or parsed JSON when nft returned JSON)
POST /firewall/rules -> execute a textual nft command (convenience)
DELETE /firewall/rules/{handle} -> delete rule by handle via textual nft command
POST /firewall/raw -> execute arbitrary textual nft command (executes)
@@ -22,6 +22,7 @@ from fastapi import FastAPI, APIRouter, HTTPException, status
from pydantic import BaseModel, Field
import logging
import re
import json
# libnftables (we call textual commands through its .cmd() method)
from nftables import Nftables # type: ignore
@@ -47,7 +48,7 @@ class NftManager:
def __init__(self) -> None:
self.nft = Nftables()
try:
# prefer seeing JSON when python-nftables prints it, but we only use .cmd() below
# Prefer JSON output where python-nftables may produce it, but we still treat stdout textually.
self.nft.set_json_output(True)
except Exception:
logger.debug("set_json_output not available or ignored")
@@ -98,20 +99,67 @@ class RawCmdRequest(BaseModel):
class RulesetOut(BaseModel):
ruleset: str
# ruleset can be either a parsed JSON object (dict/list) or a string
ruleset: Any
# ---------- PREVIEW helpers (unchanged) ----------
def parse_text_cmd_summary(cmd: str) -> Dict[str, Any]:
summary: Dict[str, Any] = {"operation": None, "family": None, "table": None, "chain": None, "remainder": cmd}
s = cmd.strip()
tokens = s.split()
if len(tokens) >= 1:
summary["operation"] = tokens[0].lower()
m = re.match(r'^(add|insert|delete|replace)\s+rule\s+(\S+)\s+(\S+)\s+(\S+)\b', s, flags=re.I)
if m:
summary["operation"] = m.group(1).lower()
summary["family"] = m.group(2)
summary["table"] = m.group(3)
summary["chain"] = m.group(4)
summary["remainder"] = s[m.end():].strip()
return summary
m2 = re.match(r'^(delete)\s+rule\s+(\S+)\s+(\S+)\s+(\S+)\s+handle\s+(\d+)', s, flags=re.I)
if m2:
summary["operation"] = m2.group(1).lower()
summary["family"] = m2.group(2)
summary["table"] = m2.group(3)
summary["chain"] = m2.group(4)
summary["remainder"] = f"handle {m2.group(5)}"
return summary
try:
idx = next(i for i,t in enumerate(tokens) if t.lower() == "rule")
if len(tokens) > idx + 3:
summary["family"] = tokens[idx+1]
summary["table"] = tokens[idx+2]
summary["chain"] = tokens[idx+3]
summary["remainder"] = " ".join(tokens[idx+4:]) if len(tokens) > idx+4 else ""
except StopIteration:
pass
return summary
# ---------- Routes ----------
# GET /firewall/rules -> textual nft ruleset output
# GET /firewall/rules -> textual nft ruleset output or parsed JSON when applicable
@router.get("/rules", response_model=RulesetOut)
def list_rules():
"""
Returns the textual nft ruleset output (as a single string).
Use clients to parse as needed.
Returns the textual nft ruleset output (as a string) or native JSON if nft returned JSON.
Clients should handle both cases.
"""
try:
text = mgr.list_rules_text()
return {"ruleset": text}
if text is None:
return {"ruleset": None}
s = text.strip()
# Try to interpret as JSON. nft will produce JSON when set_json_output(True) is active.
try:
parsed = json.loads(s)
# Return parsed JSON (will be serialized as JSON by FastAPI)
return {"ruleset": parsed}
except json.JSONDecodeError:
# Not JSON — return raw text
return {"ruleset": text}
except NftError as e:
logger.exception("list_rules failed")
raise HTTPException(status_code=500, detail=str(e))