Files
shorefront/docs/plans/2026-02-28-shorewall-manager-design.md

8.4 KiB

Shorefront — Shorewall Configuration Manager: Design Document

Date: 2026-02-28 Status: Approved


Overview

Shorefront is a production-ready web application for managing Shorewall firewall configurations. Users manage zones, interfaces, policies, rules, and masquerade/NAT entries through a browser UI backed by a PostgreSQL database, and can generate Shorewall config files on demand.


Architecture

Option A (selected): Monorepo with separate Docker services. Frontend and backend are independently containerized. In production, Nginx serves the React bundle and reverse-proxies /api to the FastAPI backend. In development, the Vite dev server proxies /api. Clean separation between frontend, backend API, and the Shorewall generator module.

Deployment targets:

  • Local dev: Docker Compose
  • Production: Kubernetes (Traefik ingress) with Helm charts

Project Structure

shorefront/
├── backend/
│   ├── Dockerfile
│   ├── requirements.txt
│   ├── alembic/
│   │   ├── env.py
│   │   └── versions/
│   │       └── 0001_initial.py       # schema + seed data
│   └── app/
│       ├── main.py                   # FastAPI app, CORS, lifespan
│       ├── database.py               # SQLAlchemy engine + session
│       ├── models.py                 # ORM models
│       ├── schemas.py                # Pydantic schemas
│       ├── auth.py                   # JWT logic, password hashing
│       ├── shorewall_generator.py    # ShorewallGenerator class
│       └── api/
│           ├── auth.py               # /auth/*
│           ├── configs.py            # CRUD + /generate
│           ├── zones.py
│           ├── interfaces.py
│           ├── policies.py
│           ├── rules.py
│           └── masq.py
├── frontend/
│   ├── Dockerfile
│   ├── nginx.conf                    # prod: serve static + proxy /api
│   ├── vite.config.ts                # dev: proxy /api → backend:8000
│   ├── package.json
│   └── src/
│       ├── main.tsx
│       ├── App.tsx
│       ├── api.ts                    # axios wrapper, withCredentials, 401 redirect
│       ├── routes/
│       │   ├── Login.tsx
│       │   ├── ConfigList.tsx
│       │   └── ConfigDetail.tsx      # tabbed: zones/interfaces/policies/rules/masq
│       └── components/
│           ├── Layout.tsx            # dark sidebar + top bar
│           ├── DataTable.tsx         # reusable table w/ edit/delete
│           ├── EntityForm.tsx        # reusable MUI Dialog form
│           └── GenerateModal.tsx     # file preview tabs + ZIP download
├── helm/
│   └── shorefront/
│       ├── Chart.yaml
│       ├── values.yaml
│       ├── values-prod.yaml
│       └── templates/
│           ├── _helpers.tpl
│           ├── namespace.yaml
│           ├── secret.yaml
│           ├── configmap.yaml
│           ├── pv.yaml               # NFS PersistentVolume
│           ├── pvc.yaml
│           ├── postgres-deployment.yaml
│           ├── postgres-service.yaml
│           ├── backend-deployment.yaml
│           ├── backend-service.yaml
│           ├── frontend-deployment.yaml
│           ├── frontend-service.yaml
│           └── ingress.yaml          # Traefik, networking.k8s.io/v1
├── docker-compose.yml
└── README.md

Data Model

Model Key Fields
User id, username, email, hashed_password, is_active
Config id, name, description, is_active, created_at, updated_at, owner_id FK→User
Zone id, config_id FK, name (unique per config), type (ipv4/ipv6/firewall), options
Interface id, config_id FK, name, zone_id FK→Zone, options
Policy id, config_id FK, src_zone_id FK, dst_zone_id FK, policy (ACCEPT/DROP/REJECT/CONTINUE), log_level, comment, position
Rule id, config_id FK, action, src_zone_id FK, dst_zone_id FK, src_ip, dst_ip, proto, dport, sport, comment, position
Masq id, config_id FK, source_network, out_interface, to_address, comment

Constraints: Foreign keys enforced. Zone name unique per config. Position/order fields for policies and rules to preserve Shorewall file ordering semantics.


Seed Data (Initial Alembic Migration)

  • User: admin / admin (must change on first login)
  • Config: homelab
    • Zones: fw (firewall), net (ipv4), loc (ipv4)
    • Interface: eth0 → zone net
    • Policies: loc→net ACCEPT, net→all DROP info, all→all REJECT info
    • Masq: 192.168.1.0/24 via eth0

API Surface

POST   /auth/register
POST   /auth/login          → sets httpOnly JWT cookie
POST   /auth/logout         → clears cookie
GET    /auth/me

GET    /configs
POST   /configs
GET    /configs/{id}
PUT    /configs/{id}
DELETE /configs/{id}
POST   /configs/{id}/generate    # ?format=zip for ZIP, JSON default

GET    /configs/{id}/zones
POST   /configs/{id}/zones
GET    /configs/{id}/zones/{zone_id}
PUT    /configs/{id}/zones/{zone_id}
DELETE /configs/{id}/zones/{zone_id}
# same pattern for interfaces, policies, rules, masq

Auth: JWT in httpOnly cookie. passlib + bcrypt for password hashing. All /configs/* routes require valid JWT.


Shorewall Generator

app/shorewall_generator.pyShorewallGenerator class:

class ShorewallGenerator:
    def __init__(self, config: Config): ...
    def zones(self) -> str: ...
    def interfaces(self) -> str: ...
    def policy(self) -> str: ...
    def rules(self) -> str: ...
    def masq(self) -> str: ...
    def as_json(self) -> dict: ...    # { zones, interfaces, policy, rules, masq }
    def as_zip(self) -> bytes: ...    # in-memory ZIP

Each method produces a Shorewall-formatted string with a comment header:

# zones — generated by shorefront | config: homelab | 2026-02-28T18:00:00Z
#ZONE   TYPE      OPTIONS
fw      firewall
net     ipv4

The /generate endpoint returns JSON by default; appending ?format=zip streams a ZIP archive. The frontend uses JSON for the in-browser preview modal and triggers a separate ?format=zip request for the download button.


Frontend Design

Theme: MUI with custom dark sidebar (#1a1f2e), light main content (#f5f7fa), white cards.

Pages:

  • Login — centered card, username/password
  • Config List — data table with name, description, active badge, created date, edit/delete actions. FAB to create.
  • Config Detail — breadcrumb, 5 tabs (Zones / Interfaces / Policies / Rules / Masq/NAT). Each tab: DataTable + "Add" button → EntityForm dialog. Top-right: "Generate Config" button → GenerateModal.
  • Generate Modal — 5 tabs with monospace code blocks, copy-to-clipboard per file, Download ZIP button.

Key implementation:

  • api.ts: Axios instance, baseURL=/api, withCredentials: true, 401 interceptor → redirect to /login
  • DataTable.tsx: Generic, accepts column defs + row data, reused across all tabs
  • EntityForm.tsx: Generic MUI Dialog, field config passed per entity type
  • React Router v6 with <ProtectedRoute> checking GET /auth/me

Infrastructure

Docker Compose (local dev)

  • Services: postgres, backend, frontend
  • Backend runs alembic upgrade head on startup
  • Local named volume for postgres data
  • Backend on port 8000, frontend (Nginx) on port 80

Kubernetes / Helm

  • Namespace: shorefront
  • Ingress: Traefik (ingressClassName: traefik), standard networking.k8s.io/v1 Ingress
    • shorefront.example.com/api/* → backend service
    • shorefront.example.com/* → frontend service
    • Hostname configurable in values.yaml
  • Storage: Static NFS PersistentVolume at 192.168.17.199:/mnt/user/kubernetesdata/shorefront, ReadWriteOnce, bound via PVC to Postgres deployment
  • Secrets: DB password + JWT secret key in secret.yaml, set via values.yaml (override at deploy time)
  • Migrations: alembic upgrade head runs as an initContainer in the backend Deployment
  • values.yaml configures: image tags, replicas, hostname, resource requests/limits, NFS server/path