roof a43f9fbf0d fix: full security audit remediation — P0/P1/P2/P3 fixes + 1020 passing tests
P0 — Broken functionality:
- Fix 12+ endpoints with wrong manager method signatures (email/calendar/file/routing)
- Fix email_manager.delete_email_user() missing domain arg
- Fix cell-link DNS forwarding wiped on every peer change (generate_corefile now
  accepts cell_links param; add/remove_cell_dns_forward no longer clobber the file)
- Fix Flask SECRET_KEY regenerating on every restart (persisted to DATA_DIR)
- Fix _next_peer_ip exhaustion returning 500 instead of 409
- Fix ConfigManager Caddyfile path (/app/config-caddy/)
- Fix UI double-add and wrong-key peer bugs in Peers.jsx / WireGuard.jsx
- Remove hardcoded credentials from Dashboard.jsx

P1 — Security:
- CSRF token validation on all POST/PUT/DELETE/PATCH to /api/* (double-submit pattern)
- enforce_auth: 503 only when users file readable but empty; never bypass on IOError
- WireGuard add_cell_peer: validate pubkey, name, endpoint against strict regexes
- DNS add_cell_dns_forward: validate IP and domain; reject injection chars
- DNS zone write: realpath containment + record content validation
- iptables comment /32 suffix prevents substring match deleting wrong peer rules
- is_local_request() trusts only loopback + 172.16.0.0/12 (Docker bridge)
- POST /api/containers: volume allow-list prevents arbitrary host mounts
- file_manager: bcrypt ($2b→$2y) for WebDAV; realpath containment in delete_user
- email/calendar: stop persisting plaintext passwords in user records
- routing_manager: validate IPs, networks, and interface names
- peer_registry: write peers.json at mode 0o600
- vault_manager: Fernet key file at mode 0o600
- CORS: lock down to explicit origin list
- domain/cell_name validation: reject newline, brace, semicolon injection chars

P2 — Architecture:
- Peer add: rollback registry entry if firewall rules fail post-add
- restart_service(): base class now calls _restart_container(); email and calendar
  managers call cell-mail / cell-radicale respectively
- email/calendar managers sync user list (no passwords) to cell_config.json
- Pending-restart flag cleared only after helper subprocess exits with code 0
- docker-compose.yml: add config-caddy volume to API container

P3 — Tests (854 → 1020):
- Fill test_email_endpoints.py, test_calendar_endpoints.py,
  test_network_endpoints.py, test_routing_endpoints.py
- New: test_peer_management_update.py, test_peer_management_edge_cases.py,
  test_input_validation.py, test_enforce_auth_configured.py,
  test_cell_link_dns.py, test_logs_endpoints.py, test_cells_endpoints.py,
  test_is_local_request_per_endpoint.py, test_caddy_routing.py
- E2E conftest: skip WireGuard suite when wg-quick absent
- Update existing tests to match fixed signatures and comment formats

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-27 11:30:21 -04:00
2025-09-14 03:31:14 -05:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00
2025-09-12 23:04:52 +03:00

Personal Internet Cell (PIC)

PIC is a self-hosted digital infrastructure platform. It manages DNS, DHCP, NTP, WireGuard VPN, email, calendar/contacts (CalDAV), file storage (WebDAV), a reverse proxy, and a certificate authority — all controlled from a single REST API and React web UI. No manual config file editing is required for normal operations.


Architecture

Browser
  └── React SPA (cell-webui :8081)
        └── Flask REST API (cell-api :3000, bound to 127.0.0.1)
              └── Docker SDK / config files
                    ├── cell-caddy        :80/:443      reverse proxy
                    ├── cell-dns          :53           CoreDNS
                    ├── cell-dhcp         :67/udp       dnsmasq
                    ├── cell-ntp          :123/udp      chrony
                    ├── cell-wireguard    :51820/udp    WireGuard VPN
                    ├── cell-mail         :25/:587/:993 Postfix + Dovecot
                    ├── cell-radicale     127.0.0.1:5232  CalDAV/CardDAV
                    ├── cell-webdav       127.0.0.1:8080  WebDAV
                    ├── cell-rainloop     :8888         webmail (RainLoop)
                    ├── cell-filegator    :8082         file manager UI
                    └── cell-webui        :8081         React UI (Nginx)

All containers run on a custom Docker bridge network (cell-network, default 172.20.0.0/16). Static IPs per container are set in docker-compose.yml and overridden via .env.

The Flask API (api/app.py, ~2800 lines) contains all REST endpoints, runs a background health-monitoring thread, and manages the entire lifecycle of generated config artefacts: Caddyfile, Corefile, wg0.conf, and cell_config.json (the single source of truth at config/api/cell_config.json).

The React frontend (webui/) is built with Vite + Tailwind CSS. All API calls go through src/services/api.js (Axios). Pages: Dashboard, Peers, Network Services, WireGuard, Email, Calendar, Files, Routing, Vault, Containers, Cell Network, Logs, Settings.


Requirements

  • Linux host with the WireGuard kernel module loaded
  • Docker Engine and Docker Compose (v2 plugin or v1 standalone)
  • Python 3.10+ (for make setup and local dev only; not needed at runtime)
  • 2 GB+ RAM, 10 GB+ disk
  • Ports available: 53, 67/udp, 80, 443, 51820/udp, 25, 587, 993

Quick Start

See QUICKSTART.md for step-by-step setup.


Configuration

Runtime configuration is controlled by .env in the project root. Copy .env.example to .env before first run.

Variable Default Description
CELL_NETWORK 172.20.0.0/16 Docker bridge subnet for all containers
CADDY_IP through FILEGATOR_IP 172.20.0.2.13 Static IP for each container
DNS_PORT 53 DNS (UDP+TCP)
DHCP_PORT 67 DHCP (UDP)
NTP_PORT 123 NTP (UDP)
WG_PORT 51820 WireGuard listen port (UDP)
API_PORT 3000 Flask API (bound to 127.0.0.1)
WEBUI_PORT 8081 React UI
MAIL_SMTP_PORT 25 SMTP
MAIL_SUBMISSION_PORT 587 SMTP submission
MAIL_IMAP_PORT 993 IMAP
RADICALE_PORT 5232 CalDAV (bound to 127.0.0.1)
WEBDAV_PORT 8080 WebDAV (bound to 127.0.0.1)
RAINLOOP_PORT 8888 Webmail
FILEGATOR_PORT 8082 File manager UI
WEBDAV_USER admin WebDAV basic-auth username
WEBDAV_PASS (required) WebDAV basic-auth password — must be set before make start
FLASK_DEBUG (unset) Set to 1 to enable Flask debug mode; do not use in production
PUID / PGID current user UID/GID passed to the WireGuard container

Cell identity (cell name, domain, VPN IP range) is configured via make setup or the Settings → Identity page in the UI after startup. The VPN IP range must be an RFC-1918 CIDR (10.0.0.0/8, 172.16.0.0/12, or 192.168.0.0/16); the API and UI both enforce this.


Security Notes

Ports exposed to the network:

  • 80 / 443 — Caddy (HTTP/HTTPS reverse proxy)
  • 51820/udp — WireGuard
  • 25 / 587 / 993 — Mail (SMTP, submission, IMAP)
  • 53 — DNS (UDP + TCP)
  • 67/udp — DHCP
  • 8081 — Web UI
  • 8888 — Webmail (RainLoop)
  • 8082 — File manager (Filegator)

Ports bound to 127.0.0.1 only (not directly reachable from the network):

  • 3000 — Flask API
  • 5232 — Radicale (CalDAV)
  • 8080 — WebDAV

The API has no authentication layer. It relies on is_local_request() to restrict sensitive endpoints (containers, vault) to requests originating from loopback or the cell's Docker network. The Docker socket is mounted into cell-api; treat access to port 3000 as equivalent to root access on the host.

For internet-facing deployments, place the host behind a firewall or VPN and restrict access to the API and UI ports.


Development

# Start the full stack (builds api and webui images)
make start

# Rebuild a single image after code changes
make build-api
make build-webui

# Run Flask API locally without Docker (port 3000)
pip install -r api/requirements.txt
python api/app.py

# Run React UI dev server locally (port 5173, proxies /api to :3000)
cd webui && npm install && npm run dev

# Follow all container logs
make logs

# Follow logs for one service (e.g. api, dns, caddy, wireguard, mail)
make logs-api

# Open a shell inside a container
make shell-api

Testing

make test            # run the full pytest suite
make test-coverage   # run with coverage; HTML report in htmlcov/

Tests live in tests/ (34 files, 642 test functions). Coverage includes:

  • All service managers (network, WireGuard, email, calendar, file, routing, vault, container)
  • API endpoint tests for each service area
  • Config manager (CRUD, validation, backup/restore)
  • IP utilities and Caddyfile generation
  • Peer registry and WireGuard peer lifecycle
  • Service bus pub/sub
  • Firewall manager
  • Pending-restart logic

Integration tests (tests/integration/) require a running PIC stack:

make test-integration             # full suite (creates peers)
make test-integration-readonly    # read-only checks, safe to run anytime

Management Commands

make setup           # generate WireGuard keys, write configs, create data dirs
make start           # docker compose up -d --build
make stop            # docker compose down
make restart         # docker compose restart
make status          # container status + API health check
make logs            # follow all service logs
make logs-<svc>      # follow logs for one service
make shell-<svc>     # shell inside a container

make update          # git pull + rebuild + restart
make reinstall       # full wipe of config/ and data/, then setup + start
make uninstall       # stop containers; prompts whether to also delete config/ and data/

make backup          # tar config/ + data/ into backups/
make restore         # list available backups

make list-peers      # show WireGuard peers via API
make show-routes     # wg show inside the wireguard container
make add-peer PEER_NAME=foo PEER_IP=10.0.0.5 PEER_KEY=<pubkey>

License

MIT — see LICENSE.

S
Description
No description provided
Readme 1.6 MiB
Languages
Python 79.6%
JavaScript 18.7%
Shell 0.9%
Makefile 0.6%