48df10b17e
- Harden DB-backed handlers and seed profiles; align client wire shapes for cluster resources, QEMU config, and node SSL fields - Serve plain HTTP on Compose :8006; keep TLS optional (--profile tls) and terminate HTTPS at Kubernetes Ingress - Add pulumi-tests (full contract surface majors 6–9 + BPG lifecycle) and make pulumi-tests - Ship bilingual docs, CHANGELOG, SECURITY, CONTRIBUTING, and GitHub Actions (make ci + Compose/Helm validation)
560 lines
26 KiB
Python
560 lines
26 KiB
Python
"""Firewall handlers backed by cluster metadata."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from collections.abc import Callable
|
|
from typing import Any, cast
|
|
|
|
from fastapi import Request
|
|
|
|
from app.api.errors import ApiError
|
|
from app.api.registry import HandlerRegistry
|
|
from app.handlers.common import database, require_node, state, subdirs, values
|
|
from app.simulation.seed import CLUSTER_ID
|
|
|
|
ScopeFn = Callable[[dict[str, Any]], str]
|
|
|
|
|
|
async def _load_firewall(request: Request) -> dict[str, Any]:
|
|
row = await database(request).pool.fetchrow(
|
|
"SELECT metadata FROM clusters WHERE id=$1",
|
|
CLUSTER_ID,
|
|
)
|
|
metadata = state(row["metadata"]) if row is not None else {}
|
|
firewall = metadata.get("firewall")
|
|
return dict(firewall) if isinstance(firewall, dict) else {}
|
|
|
|
|
|
async def _save_firewall(request: Request, firewall: dict[str, Any]) -> None:
|
|
await database(request).pool.execute(
|
|
"""UPDATE clusters SET metadata = jsonb_set(
|
|
COALESCE(metadata, '{}'::jsonb), '{firewall}', $2::jsonb, true
|
|
), updated_at=now() WHERE id=$1""",
|
|
CLUSTER_ID,
|
|
json.dumps(firewall, sort_keys=True),
|
|
)
|
|
|
|
|
|
def _empty_scope() -> dict[str, Any]:
|
|
return {
|
|
"options": {},
|
|
"rules": [],
|
|
"aliases": {},
|
|
"ipset": {},
|
|
"groups": {},
|
|
"log": [],
|
|
}
|
|
|
|
|
|
def _get_scope(firewall: dict[str, Any], scope: str) -> dict[str, Any] | None:
|
|
scopes = firewall.get("scopes")
|
|
if not isinstance(scopes, dict):
|
|
return None
|
|
section = scopes.get(scope)
|
|
return cast(dict[str, Any], section) if isinstance(section, dict) else None
|
|
|
|
|
|
def _ensure_scope(firewall: dict[str, Any], scope: str) -> dict[str, Any]:
|
|
"""Create an empty durable scope on mutation; never injects catalog defaults."""
|
|
scopes = firewall.setdefault("scopes", {})
|
|
if not isinstance(scopes, dict):
|
|
scopes = {}
|
|
firewall["scopes"] = scopes
|
|
section = scopes.get(scope)
|
|
if not isinstance(section, dict):
|
|
section = _empty_scope()
|
|
scopes[scope] = section
|
|
section.setdefault("options", {})
|
|
section.setdefault("rules", [])
|
|
section.setdefault("aliases", {})
|
|
section.setdefault("ipset", {})
|
|
section.setdefault("groups", {})
|
|
section.setdefault("log", [])
|
|
return cast(dict[str, Any], section)
|
|
|
|
|
|
def _scope_data(firewall: dict[str, Any], scope: str) -> dict[str, Any]:
|
|
"""Mutation helper — ensure scope exists without template defaults."""
|
|
return _ensure_scope(firewall, scope)
|
|
|
|
|
|
def register_firewall_handlers(registry: HandlerRegistry) -> None:
|
|
def register_scope(
|
|
base: str,
|
|
scope_fn: ScopeFn,
|
|
*,
|
|
require_node_name: bool = False,
|
|
include_macros: bool = False,
|
|
include_groups: bool = False,
|
|
) -> None:
|
|
async def _ready(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
payload = values(inputs)
|
|
if require_node_name:
|
|
await require_node(request, str(payload["node"]))
|
|
return payload
|
|
|
|
async def index(request: Request, inputs: dict[str, Any]) -> list[dict[str, str]]:
|
|
await _ready(request, inputs)
|
|
names = ["aliases", "ipset", "log", "options", "refs", "rules"]
|
|
if include_groups:
|
|
names.insert(2, "groups")
|
|
if include_macros:
|
|
names.append("macros")
|
|
return subdirs(*names)
|
|
|
|
async def options_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
if section is None:
|
|
return {}
|
|
options = section.get("options", {})
|
|
return dict(options) if isinstance(options, dict) else {}
|
|
|
|
async def options_put(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _ensure_scope(firewall, scope_fn(payload))
|
|
current = dict(section.get("options") or {})
|
|
for key, value in payload.items():
|
|
if key in {"node", "vmid", "delete", "digest"}:
|
|
continue
|
|
current[key] = value
|
|
section["options"] = current
|
|
await _save_firewall(request, firewall)
|
|
return current
|
|
|
|
async def rules_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
if section is None:
|
|
return []
|
|
rules = section.get("rules", [])
|
|
return list(rules) if isinstance(rules, list) else []
|
|
|
|
async def rules_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _scope_data(firewall, scope_fn(payload))
|
|
rules = section.setdefault("rules", [])
|
|
if not isinstance(rules, list):
|
|
rules = section["rules"] = []
|
|
rule = {
|
|
key: value for key, value in payload.items() if key not in {"node", "vmid", "pos"}
|
|
}
|
|
rule["pos"] = len(rules)
|
|
rules.append(rule)
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def rule_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
pos = int(values(inputs)["pos"])
|
|
rules = await rules_list(request, inputs)
|
|
if pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
return dict(rules[pos])
|
|
|
|
async def rule_update(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
pos = int(payload["pos"])
|
|
firewall = await _load_firewall(request)
|
|
rules = _scope_data(firewall, scope_fn(payload)).setdefault("rules", [])
|
|
if not isinstance(rules, list) or pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
rules[pos] = {
|
|
**rules[pos],
|
|
**{k: v for k, v in payload.items() if k not in {"node", "vmid"}},
|
|
}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def rule_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
pos = int(payload["pos"])
|
|
firewall = await _load_firewall(request)
|
|
rules = _scope_data(firewall, scope_fn(payload)).setdefault("rules", [])
|
|
if not isinstance(rules, list) or pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
del rules[pos]
|
|
for index, rule in enumerate(rules):
|
|
if isinstance(rule, dict):
|
|
rule["pos"] = index
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def aliases_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
aliases = section.get("aliases", {}) if section else {}
|
|
if not isinstance(aliases, dict):
|
|
return []
|
|
return [
|
|
{"name": name, **{k: v for k, v in data.items() if k != "name"}}
|
|
for name, data in sorted(aliases.items())
|
|
if isinstance(data, dict)
|
|
]
|
|
|
|
async def aliases_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
section = _scope_data(firewall, scope_fn(payload))
|
|
aliases = section.setdefault("aliases", {})
|
|
if name in aliases:
|
|
raise ApiError(400, f"alias '{name}' already exists")
|
|
aliases[name] = {
|
|
"name": name,
|
|
"cidr": str(payload.get("cidr") or ""),
|
|
"comment": str(payload.get("comment") or ""),
|
|
}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def aliases_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
alias = _scope_data(firewall, scope_fn(payload)).get("aliases", {}).get(name)
|
|
if not isinstance(alias, dict):
|
|
raise ApiError(404, "alias does not exist")
|
|
return dict(alias)
|
|
|
|
async def aliases_update(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
aliases = _scope_data(firewall, scope_fn(payload)).setdefault("aliases", {})
|
|
if name not in aliases or not isinstance(aliases[name], dict):
|
|
raise ApiError(404, "alias does not exist")
|
|
current = dict(aliases[name])
|
|
if payload.get("rename"):
|
|
new_name = str(payload["rename"])
|
|
if new_name in aliases and new_name != name:
|
|
raise ApiError(400, f"alias '{new_name}' already exists")
|
|
del aliases[name]
|
|
name = new_name
|
|
current["name"] = new_name
|
|
for key in ("cidr", "comment"):
|
|
if key in payload:
|
|
current[key] = payload[key]
|
|
aliases[name] = current
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def aliases_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
aliases = _scope_data(firewall, scope_fn(payload)).setdefault("aliases", {})
|
|
if name not in aliases:
|
|
raise ApiError(404, "alias does not exist")
|
|
del aliases[name]
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def ipset_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
ipsets = section.get("ipset", {}) if section else {}
|
|
if not isinstance(ipsets, dict):
|
|
return []
|
|
return [
|
|
{"name": name, "comment": data.get("comment", "")}
|
|
for name, data in sorted(ipsets.items())
|
|
if isinstance(data, dict)
|
|
]
|
|
|
|
async def ipset_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
ipsets = _scope_data(firewall, scope_fn(payload)).setdefault("ipset", {})
|
|
if name in ipsets:
|
|
raise ApiError(400, f"ipset '{name}' already exists")
|
|
ipsets[name] = {
|
|
"name": name,
|
|
"comment": str(payload.get("comment") or ""),
|
|
"entries": {},
|
|
}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def ipset_get(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
ipset = _scope_data(firewall, scope_fn(payload)).get("ipset", {}).get(name)
|
|
if not isinstance(ipset, dict):
|
|
raise ApiError(404, "ipset does not exist")
|
|
entries = ipset.get("entries", {})
|
|
if not isinstance(entries, dict):
|
|
return []
|
|
return [
|
|
{"cidr": cidr, **{k: v for k, v in data.items() if k != "cidr"}}
|
|
for cidr, data in sorted(entries.items())
|
|
if isinstance(data, dict)
|
|
]
|
|
|
|
async def ipset_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
firewall = await _load_firewall(request)
|
|
ipsets = _scope_data(firewall, scope_fn(payload)).setdefault("ipset", {})
|
|
if name not in ipsets:
|
|
raise ApiError(404, "ipset does not exist")
|
|
del ipsets[name]
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def ipset_entry_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
cidr = str(payload["cidr"])
|
|
firewall = await _load_firewall(request)
|
|
ipsets = _scope_data(firewall, scope_fn(payload)).setdefault("ipset", {})
|
|
if name not in ipsets or not isinstance(ipsets[name], dict):
|
|
raise ApiError(404, "ipset does not exist")
|
|
entries = ipsets[name].setdefault("entries", {})
|
|
if cidr in entries:
|
|
raise ApiError(400, f"ip '{cidr}' already exists in ipset")
|
|
entries[cidr] = {
|
|
"cidr": cidr,
|
|
"comment": str(payload.get("comment") or ""),
|
|
"nomatch": int(bool(payload.get("nomatch"))),
|
|
}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def ipset_entry_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
cidr = str(payload["cidr"])
|
|
firewall = await _load_firewall(request)
|
|
ipset = _scope_data(firewall, scope_fn(payload)).get("ipset", {}).get(name)
|
|
if not isinstance(ipset, dict):
|
|
raise ApiError(404, "ipset does not exist")
|
|
entry = ipset.get("entries", {}).get(cidr)
|
|
if not isinstance(entry, dict):
|
|
raise ApiError(404, "ipset entry does not exist")
|
|
return dict(entry)
|
|
|
|
async def ipset_entry_update(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
cidr = str(payload["cidr"])
|
|
firewall = await _load_firewall(request)
|
|
ipsets = _scope_data(firewall, scope_fn(payload)).setdefault("ipset", {})
|
|
if name not in ipsets or not isinstance(ipsets[name], dict):
|
|
raise ApiError(404, "ipset does not exist")
|
|
entries = ipsets[name].setdefault("entries", {})
|
|
if cidr not in entries:
|
|
raise ApiError(404, "ipset entry does not exist")
|
|
current = dict(entries[cidr])
|
|
if "comment" in payload:
|
|
current["comment"] = payload["comment"]
|
|
if "nomatch" in payload:
|
|
current["nomatch"] = int(bool(payload.get("nomatch")))
|
|
entries[cidr] = current
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def ipset_entry_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
name = str(payload["name"])
|
|
cidr = str(payload["cidr"])
|
|
firewall = await _load_firewall(request)
|
|
ipsets = _scope_data(firewall, scope_fn(payload)).setdefault("ipset", {})
|
|
if name not in ipsets or not isinstance(ipsets[name], dict):
|
|
raise ApiError(404, "ipset does not exist")
|
|
entries = ipsets[name].setdefault("entries", {})
|
|
if cidr not in entries:
|
|
raise ApiError(404, "ipset entry does not exist")
|
|
del entries[cidr]
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def refs_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
if section is None:
|
|
return []
|
|
refs: list[dict[str, Any]] = []
|
|
for name in section.get("aliases", {}) or {}:
|
|
refs.append({"type": "alias", "name": name})
|
|
for name in section.get("ipset", {}) or {}:
|
|
refs.append({"type": "ipset", "name": name})
|
|
return refs
|
|
|
|
async def log_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
if section is None:
|
|
return []
|
|
log = section.get("log", [])
|
|
return list(log) if isinstance(log, list) else []
|
|
|
|
async def macros_list(request: Request, _inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
from app.handlers.common import cluster_metadata
|
|
|
|
metadata = await cluster_metadata(request)
|
|
macros = metadata.get("firewall_macros")
|
|
if isinstance(macros, list):
|
|
return [dict(item) for item in macros if isinstance(item, dict)]
|
|
firewall = await _load_firewall(request)
|
|
nested = firewall.get("macros")
|
|
if isinstance(nested, list):
|
|
return [dict(item) for item in nested if isinstance(item, dict)]
|
|
return []
|
|
|
|
async def groups_list(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
firewall = await _load_firewall(request)
|
|
section = _get_scope(firewall, scope_fn(payload))
|
|
groups = section.get("groups", {}) if section else {}
|
|
if not isinstance(groups, dict):
|
|
return []
|
|
return [
|
|
{"group": name, "comment": data.get("comment", "")}
|
|
for name, data in sorted(groups.items())
|
|
if isinstance(data, dict)
|
|
]
|
|
|
|
async def groups_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).setdefault("groups", {})
|
|
if group in groups:
|
|
raise ApiError(400, f"security group '{group}' already exists")
|
|
groups[group] = {"comment": str(payload.get("comment") or ""), "rules": []}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def group_rules(request: Request, inputs: dict[str, Any]) -> list[dict[str, Any]]:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).get("groups", {})
|
|
if group not in groups or not isinstance(groups[group], dict):
|
|
raise ApiError(404, "security group does not exist")
|
|
rules = groups[group].get("rules", [])
|
|
return list(rules) if isinstance(rules, list) else []
|
|
|
|
async def group_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).setdefault("groups", {})
|
|
if group not in groups:
|
|
raise ApiError(404, "security group does not exist")
|
|
del groups[group]
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def group_rule_create(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).setdefault("groups", {})
|
|
if group not in groups or not isinstance(groups[group], dict):
|
|
raise ApiError(404, "security group does not exist")
|
|
rules = groups[group].setdefault("rules", [])
|
|
if not isinstance(rules, list):
|
|
rules = groups[group]["rules"] = []
|
|
rule = {
|
|
key: value
|
|
for key, value in payload.items()
|
|
if key not in {"node", "vmid", "group", "pos"}
|
|
}
|
|
rule["pos"] = len(rules)
|
|
rules.append(rule)
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def group_rule_get(request: Request, inputs: dict[str, Any]) -> dict[str, Any]:
|
|
rules = await group_rules(request, inputs)
|
|
pos = int(values(inputs)["pos"])
|
|
if pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
return dict(rules[pos])
|
|
|
|
async def group_rule_update(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
pos = int(payload["pos"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).setdefault("groups", {})
|
|
if group not in groups or not isinstance(groups[group], dict):
|
|
raise ApiError(404, "security group does not exist")
|
|
rules = groups[group].setdefault("rules", [])
|
|
if not isinstance(rules, list) or pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
rules[pos] = {
|
|
**rules[pos],
|
|
**{k: v for k, v in payload.items() if k not in {"node", "vmid", "group"}},
|
|
}
|
|
await _save_firewall(request, firewall)
|
|
|
|
async def group_rule_delete(request: Request, inputs: dict[str, Any]) -> None:
|
|
payload = await _ready(request, inputs)
|
|
group = str(payload["group"])
|
|
pos = int(payload["pos"])
|
|
firewall = await _load_firewall(request)
|
|
groups = _scope_data(firewall, scope_fn(payload)).setdefault("groups", {})
|
|
if group not in groups or not isinstance(groups[group], dict):
|
|
raise ApiError(404, "security group does not exist")
|
|
rules = groups[group].setdefault("rules", [])
|
|
if not isinstance(rules, list) or pos < 0 or pos >= len(rules):
|
|
raise ApiError(404, "firewall rule does not exist")
|
|
del rules[pos]
|
|
await _save_firewall(request, firewall)
|
|
|
|
registry.register(base, "GET", index)
|
|
registry.register(f"{base}/options", "GET", options_get)
|
|
registry.register(f"{base}/options", "PUT", options_put)
|
|
registry.register(f"{base}/rules", "GET", rules_list)
|
|
registry.register(f"{base}/rules", "POST", rules_create)
|
|
registry.register(f"{base}/rules/{{pos}}", "GET", rule_get)
|
|
registry.register(f"{base}/rules/{{pos}}", "PUT", rule_update)
|
|
registry.register(f"{base}/rules/{{pos}}", "DELETE", rule_delete)
|
|
registry.register(f"{base}/aliases", "GET", aliases_list)
|
|
registry.register(f"{base}/aliases", "POST", aliases_create)
|
|
registry.register(f"{base}/aliases/{{name}}", "GET", aliases_get)
|
|
registry.register(f"{base}/aliases/{{name}}", "PUT", aliases_update)
|
|
registry.register(f"{base}/aliases/{{name}}", "DELETE", aliases_delete)
|
|
registry.register(f"{base}/ipset", "GET", ipset_list)
|
|
registry.register(f"{base}/ipset", "POST", ipset_create)
|
|
registry.register(f"{base}/ipset/{{name}}", "GET", ipset_get)
|
|
registry.register(f"{base}/ipset/{{name}}", "DELETE", ipset_delete)
|
|
registry.register(f"{base}/ipset/{{name}}", "POST", ipset_entry_create)
|
|
registry.register(f"{base}/ipset/{{name}}/{{cidr}}", "GET", ipset_entry_get)
|
|
registry.register(f"{base}/ipset/{{name}}/{{cidr}}", "PUT", ipset_entry_update)
|
|
registry.register(f"{base}/ipset/{{name}}/{{cidr}}", "DELETE", ipset_entry_delete)
|
|
registry.register(f"{base}/refs", "GET", refs_list)
|
|
registry.register(f"{base}/log", "GET", log_list)
|
|
if include_macros:
|
|
registry.register(f"{base}/macros", "GET", macros_list)
|
|
if include_groups:
|
|
registry.register(f"{base}/groups", "GET", groups_list)
|
|
registry.register(f"{base}/groups", "POST", groups_create)
|
|
registry.register(f"{base}/groups/{{group}}", "GET", group_rules)
|
|
registry.register(f"{base}/groups/{{group}}", "POST", group_rule_create)
|
|
registry.register(f"{base}/groups/{{group}}", "DELETE", group_delete)
|
|
registry.register(f"{base}/groups/{{group}}/{{pos}}", "GET", group_rule_get)
|
|
registry.register(f"{base}/groups/{{group}}/{{pos}}", "PUT", group_rule_update)
|
|
registry.register(f"{base}/groups/{{group}}/{{pos}}", "DELETE", group_rule_delete)
|
|
|
|
register_scope(
|
|
"/cluster/firewall",
|
|
lambda _payload: "cluster",
|
|
include_macros=True,
|
|
include_groups=True,
|
|
)
|
|
register_scope(
|
|
"/nodes/{node}/firewall",
|
|
lambda payload: f"node:{payload['node']}",
|
|
require_node_name=True,
|
|
)
|
|
register_scope(
|
|
"/nodes/{node}/qemu/{vmid}/firewall",
|
|
lambda payload: f"qemu:{payload['node']}:{payload['vmid']}",
|
|
require_node_name=True,
|
|
)
|
|
register_scope(
|
|
"/nodes/{node}/lxc/{vmid}/firewall",
|
|
lambda payload: f"lxc:{payload['node']}:{payload['vmid']}",
|
|
require_node_name=True,
|
|
)
|