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→ zonenet - Policies:
loc→net ACCEPT,net→all DROP info,all→all REJECT info - Masq:
192.168.1.0/24viaeth0
- Zones:
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.py — ShorewallGenerator 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 →EntityFormdialog. 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/loginDataTable.tsx: Generic, accepts column defs + row data, reused across all tabsEntityForm.tsx: Generic MUI Dialog, field config passed per entity type- React Router v6 with
<ProtectedRoute>checkingGET /auth/me
Infrastructure
Docker Compose (local dev)
- Services:
postgres,backend,frontend - Backend runs
alembic upgrade headon startup - Local named volume for postgres data
- Backend on port 8000, frontend (Nginx) on port 80
Kubernetes / Helm
- Namespace:
shorefront - Ingress: Traefik (
ingressClassName: traefik), standardnetworking.k8s.io/v1Ingressshorefront.example.com/api/*→ backend serviceshorefront.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 viavalues.yaml(override at deploy time) - Migrations:
alembic upgrade headruns as aninitContainerin the backend Deployment - values.yaml configures: image tags, replicas, hostname, resource requests/limits, NFS server/path