Files
firewall/README.md
T
ducoterra e360546de9
Build and Push Container / build-and-push (push) Successful in 19s
init
2026-08-01 19:07:05 -04:00

4.6 KiB

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:
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:

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

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:

uv run pytest tests/test_unifi_firewall.py::TestBuildPolicyPayload::test_with_dest_ports -v

Container

Build and run with Podman:

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.