From e05e9d59758adf90e765af7d0819e05653ae581d Mon Sep 17 00:00:00 2001 From: "Adrian A. Baumann" Date: Sun, 1 Mar 2026 11:18:26 +0100 Subject: [PATCH] feat: add limit:burst and connlimit:mask fields to policies --- .../0008_policy_add_limit_connlimit.py | 23 +++++++++++++++++++ backend/app/models.py | 2 ++ backend/app/schemas.py | 6 +++++ backend/app/shorewall_generator.py | 14 +++++++++-- frontend/src/routes/ConfigDetail.tsx | 4 ++++ 5 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 backend/alembic/versions/0008_policy_add_limit_connlimit.py diff --git a/backend/alembic/versions/0008_policy_add_limit_connlimit.py b/backend/alembic/versions/0008_policy_add_limit_connlimit.py new file mode 100644 index 0000000..06568f4 --- /dev/null +++ b/backend/alembic/versions/0008_policy_add_limit_connlimit.py @@ -0,0 +1,23 @@ +"""add limit_burst and connlimit_mask to policies + +Revision ID: 0008 +Revises: 0007 +Create Date: 2026-03-01 +""" +from alembic import op +import sqlalchemy as sa + +revision = "0008" +down_revision = "0007" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("policies", sa.Column("limit_burst", sa.String(64), server_default="''", nullable=False)) + op.add_column("policies", sa.Column("connlimit_mask", sa.String(32), server_default="''", nullable=False)) + + +def downgrade() -> None: + op.drop_column("policies", "connlimit_mask") + op.drop_column("policies", "limit_burst") diff --git a/backend/app/models.py b/backend/app/models.py index 06303a3..f921eb2 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -76,6 +76,8 @@ class Policy(Base): dst_zone_id: Mapped[int | None] = mapped_column(Integer, ForeignKey("zones.id"), nullable=True) policy: Mapped[str] = mapped_column(String(16), nullable=False) log_level: Mapped[str] = mapped_column(String(16), default="") + limit_burst: Mapped[str] = mapped_column(String(64), default="") + connlimit_mask: Mapped[str] = mapped_column(String(32), default="") comment: Mapped[str] = mapped_column(Text, default="") position: Mapped[int] = mapped_column(Integer, default=0) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index 9d9b788..2c3f9b8 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -93,6 +93,8 @@ class PolicyCreate(BaseModel): dst_zone_id: Optional[int] = None policy: str log_level: str = "" + limit_burst: str = "" + connlimit_mask: str = "" comment: str = "" position: int = 0 @@ -102,6 +104,8 @@ class PolicyUpdate(BaseModel): dst_zone_id: Optional[int] = None policy: Optional[str] = None log_level: Optional[str] = None + limit_burst: Optional[str] = None + connlimit_mask: Optional[str] = None comment: Optional[str] = None position: Optional[int] = None @@ -113,6 +117,8 @@ class PolicyOut(BaseModel): dst_zone_id: Optional[int] policy: str log_level: str + limit_burst: str + connlimit_mask: str comment: str position: int diff --git a/backend/app/shorewall_generator.py b/backend/app/shorewall_generator.py index 84dea08..2fe3991 100644 --- a/backend/app/shorewall_generator.py +++ b/backend/app/shorewall_generator.py @@ -33,11 +33,21 @@ class ShorewallGenerator: return "".join(lines) def policy(self) -> str: - lines = [self._header("policy"), "#SOURCE".ljust(16) + "DEST".ljust(16) + "POLICY".ljust(16) + "LOG LEVEL\n"] + lines = [ + self._header("policy"), + "#SOURCE".ljust(16) + "DEST".ljust(16) + "POLICY".ljust(16) + + "LOG LEVEL".ljust(16) + "LIMIT:BURST".ljust(20) + "CONNLIMIT:MASK\n", + ] for p in sorted(self._config.policies, key=lambda x: x.position): src = p.src_zone.name if p.src_zone else "all" dst = p.dst_zone.name if p.dst_zone else "all" - lines.append(self._col(src, dst, p.policy, p.log_level or "-")) + lines.append(self._col( + src, dst, p.policy, + p.log_level or "-", + p.limit_burst or "-", + p.connlimit_mask or "-", + width=16, + )) return "".join(lines) def rules(self) -> str: diff --git a/frontend/src/routes/ConfigDetail.tsx b/frontend/src/routes/ConfigDetail.tsx index e731e53..f3eca2f 100644 --- a/frontend/src/routes/ConfigDetail.tsx +++ b/frontend/src/routes/ConfigDetail.tsx @@ -113,6 +113,8 @@ export default function ConfigDetail() { }, { key: 'policy' as const, label: 'Policy' }, { key: 'log_level' as const, label: 'Log Level' }, + { key: 'limit_burst' as const, label: 'Limit:Burst' }, + { key: 'connlimit_mask' as const, label: 'ConnLimit:Mask' }, { key: 'position' as const, label: 'Position' }, ] as Column[], fields: [ @@ -120,6 +122,8 @@ export default function ConfigDetail() { { name: 'dst_zone_id', label: 'Destination Zone', type: 'select' as const, options: [{ value: '', label: 'all' }, ...zoneOptions] }, { name: 'policy', label: 'Policy', required: true, type: 'select' as const, options: [{ value: 'ACCEPT', label: 'ACCEPT' }, { value: 'DROP', label: 'DROP' }, { value: 'REJECT', label: 'REJECT' }, { value: 'CONTINUE', label: 'CONTINUE' }] }, { name: 'log_level', label: 'Log Level' }, + { name: 'limit_burst', label: 'Limit:Burst', placeholder: 'e.g. 10/sec:20' }, + { name: 'connlimit_mask', label: 'ConnLimit:Mask', placeholder: 'e.g. 10:24' }, { name: 'comment', label: 'Comment' }, { name: 'position', label: 'Position', type: 'number' as const }, ] as FieldDef[],