# UniFi Firewall Updater Automatically updates UniFi Dream Machine firewall rules with your current public IPv4 and IPv6 addresses via the UniFi Network API. ## Overview This tool: - Fetches your public IPv4 and IPv6 addresses - Reads firewall rule definitions from a YAML config file - Creates or updates policies on your UniFi controller to use those addresses - Optionally sends notifications via NTFY when rules change Designed to run as a one-shot script (e.g., via cron or systemd timer) whenever your public IP changes. ## Quick Start 1. Copy `.env.example` to `.env` and fill in your UniFi credentials 2. Edit `config/rules.yaml` to define your firewall rules 3. Install dependencies and run: ```bash uv sync uv run main.py ``` ## Configuration ### Environment Variables | Variable | Required | Description | | ------------------ | -------- | -------------------------------------------------------- | | `UNIFI_HOST` | Yes | UniFi controller URL (e.g., `https://10.1.0.1`) | | `UNIFI_SITE_ID` | Yes | UniFi site ID | | `UNIFI_API_TOKEN` | Yes | UniFi Network API token | | `UNIFI_VERIFY_SSL` | No | Verify SSL certificates (default: `false`) | | `CONFIG_FILE` | No | Path to rules YAML (default: `config/rules.yaml`) | | `LOG_LEVEL` | No | Log level: DEBUG, INFO, WARNING, ERROR (default: `INFO`) | | `DEBUG` | No | Attach debugpy on port 5678 (default: `false`) | | `NTFY_URL` | No | NTFY server URL for notifications | | `NTFY_TOPIC` | No | NTFY topic to publish to | | `NTFY_API_KEY` | No | NTFY API key for authentication | ### Rules YAML Each rule defines a firewall policy that will use your public IP as the source: ```yaml rules: - name: "Allow External to Gateway HTTP(S)" source_zone: "External" dest_zone: "DMZ" ip_version: "IPV6" action: "ALLOW" allow_return_traffic: true protocol: "tcp" dest_ports: [80, 443] dest_port_ranges: - start: 8000 stop: 8080 logging_enabled: false enabled: true ``` | Field | Required | Description | | ---------------------- | -------- | ------------------------------------------------ | | `name` | Yes | Unique policy name | | `source_zone` | Yes | Source zone name (e.g., `WAN`, `External`) | | `dest_zone` | Yes | Destination zone name (e.g., `LAN`, `DMZ`) | | `ip_version` | No | `IPV4` or `IPV6` (default: `IPV4`) | | `action` | No | `ALLOW`, `BLOCK`, or `REJECT` (default: `ALLOW`) | | `allow_return_traffic` | No | Allow return traffic (default: `true`) | | `protocol` | No | Protocol filter (e.g., `tcp`, `udp`) | | `dest_ports` | No | List of destination ports | | `dest_port_ranges` | No | List of `{start, stop}` port ranges | | `logging_enabled` | No | Enable policy logging (default: `false`) | | `enabled` | No | Enable the policy (default: `true`) | ## Development ```bash uv run pytest -v # Run tests (all mocked, no live API calls) uv run ruff check # Lint uv run pyright # Type check (strict mode) ``` Run a single test: ```bash uv run pytest tests/test_unifi_firewall.py::TestBuildPolicyPayload::test_with_dest_ports -v ``` ## Container Build and run with Podman: ```bash podman build -t unifi-firewall . podman run --env-file .env -v $(pwd)/config:/app/config unifi-firewall ``` Config is mounted at runtime via volume. Environment variables are passed at runtime. ## How It Works 1. Loads rules from `config/rules.yaml` 2. Fetches public IPv4/IPv6 via `curl ifconfig.me` 3. Lists zones from UniFi to resolve zone names to IDs 4. For each rule: - Finds existing policy by name - Skips if IP already matches - Updates if policy exists with different IP - Creates if policy does not exist 5. Sends NTFY notification if rules were created, updated, or failed ## API Details Uses UniFi Network API v1 at: ``` {UNIFI_HOST}/proxy/network/integration/v1/sites/{UNIFI_SITE_ID} ``` Authentication via `X-API-Key` header. See `network_v10.4.57_openapi.json` for the full schema.