fix position
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
All checks were successful
Build and Deploy MITM Webserver / build (push) Successful in 9s
This commit is contained in:
@@ -255,7 +255,7 @@ class CreateRuleRequest(BaseModel):
|
|||||||
table: str = Field(..., description="Table name (e.g. filter)", example="filter")
|
table: str = Field(..., description="Table name (e.g. filter)", example="filter")
|
||||||
chain: str = Field(..., description="Chain name (e.g. forward)", example="forward")
|
chain: str = Field(..., description="Chain name (e.g. forward)", example="forward")
|
||||||
expr: Any = Field(..., description="nft JSON expression (machine-readable). This field is required for JSON rule creation.")
|
expr: Any = Field(..., description="nft JSON expression (machine-readable). This field is required for JSON rule creation.")
|
||||||
position: Optional[int] = Field(None, description="Optional zero-based insertion position (0 = top). If omitted the rule is appended.")
|
position: Optional[int] = Field(None, description="Optional insertion position (zero-based). If provided endpoint will insert at that position.")
|
||||||
comment: Optional[str] = Field(None, description="Optional comment")
|
comment: Optional[str] = Field(None, description="Optional comment")
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
@@ -264,42 +264,12 @@ class CreateRuleRequest(BaseModel):
|
|||||||
"family": "bridge",
|
"family": "bridge",
|
||||||
"table": "filter",
|
"table": "filter",
|
||||||
"chain": "forward",
|
"chain": "forward",
|
||||||
"position": 1,
|
|
||||||
"expr": [{"match": {"left": {"payload": {"protocol": "ip", "field": "protocol"}}, "op": "==", "right": "icmp"}}, {"drop": None}],
|
"expr": [{"match": {"left": {"payload": {"protocol": "ip", "field": "protocol"}}, "op": "==", "right": "icmp"}}, {"drop": None}],
|
||||||
|
"position": 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
# ---------- Move endpoints: request/response models ----------
|
|
||||||
class MoveRequest(BaseModel):
|
|
||||||
"""
|
|
||||||
Request body for moving a rule. Exactly one of these should typically be provided:
|
|
||||||
- position: numeric index to insert at (0 = first)
|
|
||||||
- before_handle: insert before an existing handle in the same chain
|
|
||||||
- to_top: boolean
|
|
||||||
- to_bottom: boolean
|
|
||||||
family/table/chain are required to identify the chain.
|
|
||||||
"""
|
|
||||||
family: str = Field(..., example="bridge")
|
|
||||||
table: str = Field(..., example="filter")
|
|
||||||
chain: str = Field(..., example="forward")
|
|
||||||
position: Optional[int] = Field(None, description="Zero-based position to insert at (0 = top)")
|
|
||||||
before_handle: Optional[int] = Field(None, description="Insert before this handle (find its index and use that)")
|
|
||||||
to_top: Optional[bool] = Field(False, description="Move to top (equivalent to position=0)")
|
|
||||||
to_bottom: Optional[bool] = Field(False, description="Move to bottom (append)")
|
|
||||||
|
|
||||||
|
|
||||||
class MoveSubResult(BaseModel):
|
|
||||||
cmd: str
|
|
||||||
out: Optional[ExecResult] = None
|
|
||||||
err: Optional[str] = None
|
|
||||||
|
|
||||||
|
|
||||||
class MoveResult(BaseModel):
|
|
||||||
added: MoveSubResult
|
|
||||||
deleted: MoveSubResult
|
|
||||||
|
|
||||||
|
|
||||||
# ruleset may be typed RulesetModel or raw textual string (fallback)
|
# ruleset may be typed RulesetModel or raw textual string (fallback)
|
||||||
RulesetValue = Optional[Union[RulesetModel, str]]
|
RulesetValue = Optional[Union[RulesetModel, str]]
|
||||||
|
|
||||||
@@ -601,7 +571,6 @@ def expr_to_text(expr: Any) -> Optional[str]:
|
|||||||
elif isinstance(q, (int, float)):
|
elif isinstance(q, (int, float)):
|
||||||
token += f" num {int(q)}"
|
token += f" num {int(q)}"
|
||||||
elif isinstance(q, str):
|
elif isinstance(q, str):
|
||||||
# preserve string but ensure spacing: client must supply numeric if nft expects it
|
|
||||||
token += f" num {q}"
|
token += f" num {q}"
|
||||||
parts.append(token)
|
parts.append(token)
|
||||||
continue
|
continue
|
||||||
@@ -707,7 +676,8 @@ def list_rules():
|
|||||||
def create_rule_json(req: CreateRuleRequest):
|
def create_rule_json(req: CreateRuleRequest):
|
||||||
"""
|
"""
|
||||||
Create a rule from JSON (expr required).
|
Create a rule from JSON (expr required).
|
||||||
- Attempts to render expr -> textual fragment and execute: `add rule <family> <table> <chain> [position N] <fragment>`
|
- If req.position is provided, uses: insert rule <family> <table> <chain> position <n> <expr>
|
||||||
|
- Otherwise, uses: add rule <family> <table> <chain> <expr> (append)
|
||||||
- If rendering fails: 400 instructing the client to use POST /firewall/raw
|
- If rendering fails: 400 instructing the client to use POST /firewall/raw
|
||||||
- Returns ExecResult on success (201) or on error (400) with stdout/stderr in body.
|
- Returns ExecResult on success (201) or on error (400) with stdout/stderr in body.
|
||||||
- If nft wrapper returns an invalid rc but the command produced no stderr, we double-check the chain
|
- If nft wrapper returns an invalid rc but the command produced no stderr, we double-check the chain
|
||||||
@@ -730,38 +700,20 @@ def create_rule_json(req: CreateRuleRequest):
|
|||||||
|
|
||||||
expr_text = rendered.strip()
|
expr_text = rendered.strip()
|
||||||
|
|
||||||
# If a position is provided, try to determine chain length to clamp the position.
|
# If a position is explicitly requested, use the 'insert rule ... position <n> ...' form.
|
||||||
position_token = ""
|
# 'add rule ... position ...' is not supported by some nft versions / syntaxes.
|
||||||
if req.position is not None:
|
if req.position is not None:
|
||||||
# ensure numeric and non-negative
|
|
||||||
try:
|
try:
|
||||||
pos_candidate = int(req.position)
|
pos = int(req.position)
|
||||||
|
# clamp pos to >= 0
|
||||||
|
if pos < 0:
|
||||||
|
pos = 0
|
||||||
except Exception:
|
except Exception:
|
||||||
raise NftError("position must be an integer >= 0")
|
pos = 0
|
||||||
if pos_candidate < 0:
|
cmd = f"insert rule {family} {table} {chain} position {pos} {expr_text}"
|
||||||
raise NftError("position must be >= 0")
|
else:
|
||||||
|
cmd = f"add rule {family} {table} {chain} {expr_text}"
|
||||||
|
|
||||||
# attempt to read current ruleset to know chain length
|
|
||||||
try:
|
|
||||||
nft_json = mgr.list_rules_json()
|
|
||||||
custom = build_predictable_ruleset(nft_json)
|
|
||||||
chain_rules: List[Dict[str, Any]] = []
|
|
||||||
for t in custom.get("tables", []):
|
|
||||||
if t.get("family") == family and t.get("name") == table:
|
|
||||||
for ch in t.get("chains", []):
|
|
||||||
if ch.get("name") == chain:
|
|
||||||
chain_rules = ch.get("rules", [])
|
|
||||||
break
|
|
||||||
chain_len = len(chain_rules)
|
|
||||||
# clamp position to [0, chain_len]
|
|
||||||
pos = max(0, min(chain_len, pos_candidate))
|
|
||||||
except Exception:
|
|
||||||
# if we cannot read ruleset, just use provided pos_candidate (server may still accept or fail)
|
|
||||||
pos = pos_candidate
|
|
||||||
|
|
||||||
position_token = f" position {pos}"
|
|
||||||
|
|
||||||
cmd = f"add rule {family} {table} {chain}{position_token} {expr_text}"
|
|
||||||
logger.info("create_rule_json executing command: %s", cmd)
|
logger.info("create_rule_json executing command: %s", cmd)
|
||||||
|
|
||||||
res = mgr.cmd(cmd)
|
res = mgr.cmd(cmd)
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export interface CreateRuleRequest {
|
|||||||
table: string;
|
table: string;
|
||||||
chain: string;
|
chain: string;
|
||||||
expr: Expr;
|
expr: Expr;
|
||||||
position?: any | null;
|
position?: number | null;
|
||||||
comment?: string | null;
|
comment?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user