Skip to content
paepckehhPublic

About

Selfhost OPNSense and UniFI configuration management & backup portal (written in golang, useable via webui, as cli tool, daemon, api or golang-library)

Topics

Resources

Contributing

Security policy

Stars

18 stars

Watchers

1 watching

Forks

Latest commit

Β 

History

442 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Warning

OPNsense 26.7.7 with lighttpd + kernel TLS (kTLS) can send truncated backup XML (more body bytes than its declared Content-Length), so a backup download fails or yields a partial config. Disable kTLS on the appliance (kern.ipc.tls.enable=0 in /boot/loader.conf, then reboot) or set OPN_TLS_MAX_VERSION=1.2. Root cause and permanent fix (Caddy): OPNsense backup framing RCA.

OPNBORG β€” self-hosted OPNsense fleet control plane

Resistance is futile. Your OPNsense will be assimilated. πŸ––

One binary. Your entire firewall fleet. Backed up, watched, audited, and under version control.

A single self-hosted Go daemon that keeps every OPNsense firewall (and optionally every Unifi controller) in a git-tracked, SHA-256 deduplicated archive β€” then lets a local LLM write the security review of every change it makes.

0 agents on the firewalls Β· 0 database servers Β· 0 runtime dependencies Β· 1 static binary.

OPNBORG: established in September 2024, 2 years of continuous public development on GitHub, 290+ tagged releases

Go Reference Go Report Card SemVer License built with nix single binary platforms

πŸš€ Quick Start Β· ✨ Features Β· πŸ“Έ Screenshots Β· 🧠 How It Works Β· πŸ”§ Configuration Β· πŸ” Security Β· ❓ FAQ


Tip

30-second demo β€” point it at one firewall, get a WebUI on :6464, a deduplicated archive, and a git repo with AI-reviewed commit messages. No agents on the firewalls. No database server. No runtime dependencies.

OPN_TARGETS='opn01.lan:8443' OPN_APIKEY='...' OPN_APISECRET='...' \
  go run paepcke.de/opnborg/cmd/opnborg@main
# β–Έ WebUI live at http://localhost:6464

✨ Features

Feature What it actually does
πŸ–₯️ Hive dashboard Live version, reachability and backup state per firewall, rendered as animated SVG by an embedded HTTP server β€” no JS framework, no build step.
πŸ’Ύ Smart backups Every pass is SHA-256 hashed; unchanged configs are skipped, changed ones land in a timestamped .archive/ with CONFIG-CURRENT / CONFIG-LAST rotation.
πŸ” Fleet sync Pick a master host β€” its plugin set and remote-syslog client config are replicated to every other target, so the hive never drifts.
πŸ€– AI security audit A local LLM (Ollama, or any OpenAI-compatible endpoint) reads an enriched diff and writes the commit message: headline, security summary, and a tag: low|medium|high|critical line.
🧟 BorgAUDIT Commit-history review page: full diffs, a threat-level map, the admin who performed each change, and a per-commit approval ledger.
πŸŽ›οΈ Unifi aware .unf backups, autoBackup folder watch, and inventory export (CSV/JSON) from the same binary, on the same dashboard.
πŸ“œ Syslog collector Built-in RFC5424 server with rotated retention β€” point your firewalls at it, read it from the WebUI host.
πŸ” Two-mode WebUI Monitoring is always read-only and open; sensitive actions unlock per browser session behind Argon2id credentials.
☁️ Git native go-git manages the storage repo: commit per backup pass, aggressive repack, SSH upstream push with host-key pinning. No git binary required.
πŸ“¦ One static binary CGO_ENABLED=0, embedded assets, zero runtime deps. Linux, BSD, macOS, Windows on amd64/arm64/armv7.

πŸ“Έ Screenshots

Real captures from a live opnborg daemon, not mockups: a four-member edge hive is backed up, reviewed by the model, committed to git, and displayed in the dark WebUI theme β€” authenticated as an admin so the full diff and approval controls are visible.

πŸ–₯️ The hive β€” fleet status at a glance
OPNBORG hive dashboard showing four OPNsense members in admin mode, live firmware versions, asset tags, last-seen times, archive links and the BorgDASHBOARD store and git summary

Every member tile carries its live firmware version, asset tag, last-seen time and current.xml / .archive links β€” here with one member degraded so the status colors are visible. The BorgDASHBOARD below summarises the backup store, the local git repo and upstream sync health.

🧟 BorgAUDIT β€” the AI-reviewed change history
BorgAUDIT page with the security threat level summary, five severity-tagged commits, expanded unified diffs and approved and pending approval states

Threat-level summary, severity-tagged commits, and a full unified diff on demand. Two changes are approved in the ledger; a critical, needs-review DMZ rule change is still waiting for the operator, and the severity map at the top filters the list on click.

⚑ Live backup console β€” force a pass and watch it stream
OPNBORG forced backup progress view with the radar animation, progress bar, four live server and seven change counters and a streaming per-server log

/force arms a pass; this page streams the daemon log line by line, shows live stats (16 lines Β· 4 servers Β· 7 changes Β· 0 errors in this capture), and redirects back to the hive when the pass completes.

πŸ”§ Config dashboard β€” every parsed env knob, live probes
OPNBORG config dashboard tiles for backup targets, auth mode, git storage, OpenAI model probe, WebUI and monitoring links

Startup configuration, masked secrets, Argon2id auth state, the git store, and live model probes (server reachable, REST API ready, model found) β€” all on one page.

πŸ–¨οΈ Real console output β€” version, commit and colored status lines
OPNBORG terminal output showing the startup version and commit, git repo init, auth, per-server backup lines in green, a yellow retry warning and a red fetch error

Exactly what a pass looks like on a real tty: [OK]/[SUCCESS]/[FINISH] green, [WARN] yellow, [ERROR]/[FAIL] red, [START*] cyan. Color is emitted only for interactive stdout β€” set NO_COLOR or OPN_NOCOLOR to pipe plain text. ./opnborg -v prints the same SemVer + build commit shown at startup.


🧠 How It Works

flowchart LR
    subgraph fleet["🏰 Your fleet"]
        OPN["OPNsense<br/>firewalls"]
        UNI["Unifi<br/>controllers"]
    end

    subgraph borg["βš™οΈ opnborg β€” one binary"]
        PULL["fetch<br/>HTTPS + keypin"]
        STORE["SHA-256 store<br/>archive + rotation"]
        GIT["go-git repo<br/>commit + repack"]
        AI{"local LLM<br/>review"}
        UI["WebUI :6464<br/>hive Β· audit Β· config"]
        SYS["RFC5424<br/>syslog sink"]
    end

    OPN -->|"config XML"| PULL
    UNI -->|".unf"| PULL
    PULL --> STORE
    STORE --> GIT
    GIT --> AI
    AI -->|"commit msg + tag"| GIT
    GIT --> UI
    OPN -->|"logs"| SYS
    STORE --> UI
    GIT -->|"SSH push"| UP[("upstream<br/>git remote")]
Loading
  1. Pull β€” the daemon polls each target over HTTPS (OS trust store intentionally bypassed, OPN_TLSKEYPIN used for MitM-proofing) and fetches the live config.
  2. Diff & dedup β€” a SHA-256 of the payload is compared with the previous CONFIG-CURRENT. No change, no write. Change, and the XML is archived and the symlinks rotate.
  3. Commit β€” the storage directory is a git repo. Each pass stages the changed files, skips the approval ledger via a hardwired path guard, and commits.
  4. Review β€” if a model is configured, an enriched diff (file metadata, widened hunks, detected XML sections) is sent to Ollama or an OpenAI-compatible endpoint. The model's answer is the commit message, including a tag: severity line.
  5. Surface β€” the WebUI renders the hive, the audit history, and the approval ledger.

πŸš€ Quick Start

Run from source (no build step)

OPN_PATH='/tmp/opn' \
OPN_TARGETS='opn01.lan:8443,opn02.lan:8443' \
OPN_APIKEY='...' \
OPN_APISECRET='...' \
go run paepcke.de/opnborg/cmd/opnborg@main

Prebuilt binaries: Releases Β· go install paepcke.de/opnborg/cmd/opnborg@main Add OPN_NODAEMON=1 for a single pass instead of the daemon.

./opnborg -v prints the release SemVer plus the embedded build commit, and every startup logs the same [VERSION:…][COMMIT:…] pair β€” so you always know exactly which artifact is deployed.

🐳 Docker / Docker Compose β€” distroless, linux/amd64 + linux/arm64

Published from every v* tag to ghcr.io/paepckehh/opnborg β€” tags: latest, edge, v0, v0.1, exact release.

services:
  opnborg:
    image: ghcr.io/paepckehh/opnborg:latest
    restart: unless-stopped
    ports: ["6464:6464"]
    volumes: [opnborg-data:/var/opnborg]
    environment:
      OPN_TARGETS: "opn01.lan:443,opn02.lan:443"
      OPN_APIKEY: "+RIb6YWNdcDWMMM7W5ZY..."
      OPN_APISECRET: "8VbjM3HKKqQW2ozO..."

volumes:
  opnborg-data:

[!WARNING] Expose 6464 to trusted networks only β€” and arm OPN_AUTH_HASH / OPN_AUTH_SALT to unlock admin mode.

❄️ NixOS / Nix

Available as a Nix package. Ready-made modules in this repo:

opnborg-docker.nix Β· opnborg-docker-complex.nix Β· opnborg-prometheus-grafana.nix (Prometheus + Grafana; Wazuh/Influx/Graylog WIP)


πŸ”§ Configuration

Everything is environment variables β€” the binary accepts only -v / -h. A local .env file is auto-loaded. Ready-to-use examples live in the repo: example.sh, example-env-config-simple.sh, example-env-config-complex.sh, example-env-config-unifi.sh, example-env-config-dev.sh.

Important

Booleans are presence-based. OPN_DEBUG=0 and OPN_DEBUG=false are both true β€” unset the variable to disable a feature. OPN_GIT_ENABLE is opt-in.

🎯 Basics
Variable Default Description
OPN_APIKEY / OPN_APISECRET β€” OPNsense backup user API credentials (required)
OPN_TARGETS β€” Comma-separated targets, optional #<asset-tag> (opn01.lan:8443#RACK-PROD01)
OPN_TARGETS_<GROUP> β€” Named groups: OPN_TARGETS_INTRANET='opn01.lan:8443,...'
OPN_TARGETS_DESC_<GROUP> β€” WebUI group description
OPN_TARGETS_IMGURL_<GROUP> β€” WebUI group image URL (description becomes the tooltip)
OPN_PATH . Backup store path
OPN_TLSKEYPIN β€” SPKI SHA-256 keypin β€” MitM-proofing for the OPNsense API
OPN_TLS_MIN_VERSION 1.3 Minimum TLS version for OPNsense API fetches (1.2 / 1.3)
OPN_TLS_MAX_VERSION 1.3 Maximum TLS version β€” set to 1.2 when an appliance's FreeBSD kTLS record stream corrupts TLS 1.3 backup downloads
OPN_SLEEP 3600 Poll interval in seconds (min 10)
OPN_MASTER β€” Master host β€” replicate its config across the hive
OPN_SYNC_PKG β€” Sync installed plugins from master to all targets
OPN_EMAIL git@opnborg Git commit author email
OPN_NODAEMON β€” One-shot mode: single pass, then exit
OPN_DEBUG β€” Verbose debug logging
OPN_NOCOLOR β€” Disable ANSI colors on stdout (also honors NO_COLOR)
☁️ Git store β€” native go-git, no external binary

OPN_PATH is managed as a git repo: auto-init, .gitignore upkeep, commit per backup pass, aggressive repack (delta window = 250) after each commit.

Variable Description
OPN_GIT_ENABLE Auto commit each backup pass β€” opt-in
OPN_GIT_UPSTREAM SSH remote to push to; unset = local-only
OPN_GIT_SSH_KEY PEM private key for upstream auth (required with upstream)
OPN_GIT_SSH_HOSTKEY SHA256:<base64> host-key pin; unset skips verification
πŸ€– AI security audit β€” Ollama first, OpenAI-compatible fallback

The LLM reads an enriched diff and writes the commit message: a headline, a short security summary, and a trailing tag: low|medium|high|critical[, needs-review] line that is surfaced on the BorgAUDIT page. Requires OPN_GIT_ENABLE.

export OLLAMA_DESC_URL='http://localhost:11434'
export OLLAMA_DESC_MODEL='llama3'

# optional fallback (used when Ollama is unset or fails):
export OPENAPI_DESC_URL='http://192.168.6.222:11434/v1'   # model defaults to gpt-4o-mini
# export OPENAPI_DESC_MODEL='...'
# export OPENAPI_DESC_TOKEN='...'

.unf-only commits and any model failure fall back to opnborg auto update β€” the backup pipeline never blocks on a model.

πŸ” WebUI auth β€” monitoring by default, admin opt-in

Monitoring mode is always open (green badge): dashboards, audit page, progress. Sensitive actions β€” config downloads, audit diffs, forced backup, approvals β€” require an admin session (red/yellow badge): Argon2id (time=8, 64 MiB) credentials, HttpOnly SameSite=Strict cookies, 12 h sliding TTL, exponential global lockout on failed logins.

Generate OPN_AUTH_HASH + OPN_AUTH_SALT from a password on the /auth-hash page β€” the password is never stored or logged. Without valid credentials, login is impossible and sensitive paths return 403 Forbidden.

πŸ–₯️ WebUI routes & HTTP options
Route Access What
/ open Hive dashboard
/config open Auth, AI, git & store tiles
/audit open BorgAUDIT β€” commit history, threat dashboard
/progress open Live forced-backup progress
/force open Trigger a backup pass now
/files/ admin Config downloads
/approve, /approve-all admin Approval ledger actions (POST)
/auth/*, /auth-hash open Login/logout/state, credential generator
Variable Default Description
OPN_HTTPD_SERVER 127.0.0.1:6464 Listen address
OPN_HTTPD_DISABLE β€” Turn the WebUI off
OPN_HTTPD_CACERT / OPN_HTTPD_CAKEY β€” Enable HTTPS
OPN_HTTPD_CACLIENT β€” Enforce mTLS
OPN_HTTPD_COLOR_FG / OPN_HTTPD_COLOR_BG white / #333333 Theme colors
OPN_RSYSLOG_ENABLE + OPN_RSYSLOG_SERVER β€” Built-in RFC5424 collector (e.g. 192.168.0.1:5140)
πŸŽ›οΈ Unifi controller β€” backups, watch, inventory
export OPN_UNIFI_WEBUI='https://192.168.1.10:8443#RACK-PROD03'
export OPN_UNIFI_BACKUP_USER='backup'
export OPN_UNIFI_BACKUP_SECRET='start'
export OPN_UNIFI_VERSION='8.5.6'              # required
# optional:
export OPN_UNIFI_WATCH_PATH='/var/lib/unifi/data/backup/autobackup'  # watch-only deploy is valid
export OPN_UNIFI_EXPORT='1'; export OPN_UNIFI_FORMAT='csv'           # or 'json'
export OPN_UNIFI_MONGODB_URI='mongodb://127.0.0.1:27117'
export OPN_UNIFI_BACKUP_DESC='Network controller'   # (+_IMGURL) like target groups
πŸ“ˆ Observability links β€” Prometheus, Grafana, Wazuh

OPN_PROMETHEUS_WEBUI Β· OPN_WAZUH_WEBUI Β· OPN_GRAFANA_WEBUI Β· OPN_GRAFANA_DASHBOARD_FREEBSD Β· OPN_GRAFANA_DASHBOARD_HAPROXY Β· OPN_GRAFANA_DASHBOARD_UNIFI β€” deep links into your existing monitoring stack, rendered on the dashboard.

πŸ—„οΈ Storage layout
<OPN_PATH>/<server>/
  current.xml          # latest backup (admin-only download)
  .archive/<Y>/<M>/<timestamp>-<server>.xml
  CONFIG-CURRENT/LAST  # symlink rotation
  sha256.db            # dedup log
.db/
  approval.db          # security-approval ledger (SQLite, always gitignored)
  approval.db-wal
  approval.db-shm
.git/                  # when OPN_GIT_ENABLE is set
Logs/current.log       # rotated by lumberjack (256 MB, 256 backups, 180 d, gzipped)

πŸ” Security Model

opnborg is built for people who treat firewall configs as production state:

  • Least-privilege API user β€” a dedicated OPNsense user with Diagnostics: Configuration History (plus System: Firmware only if you enable plugin sync).
  • Keypin instead of CA trust β€” the OPNsense API is used with an explicit SPKI SHA-256 pin; the OS trust store is deliberately not consulted.
  • Two-mode access β€” unauthenticated sessions are monitoring-only, always. current.xml, current.unf and the archive are never served without a live admin session, and return 403 when no credentials are configured at all. A regression test guards this invariant.
  • Constant-time auth β€” Argon2id-derived keys compared with subtle.ConstantTimeCompare, per-session random tokens, global exponential lockout on failed logins.
  • Ledger isolation β€” the SQLite approval ledger lives in .db/ and stays out of your git history through four independent layers: the store .gitignore (.db/ plus the three file names, reconciled on every startup and every commit), a hardwired staging skip over every approval.db* path, an index filter (gitLedgerScrubIndex) that unstages any ledger path before the commit tree is built β€” even one staged by a manual git add . β€” and a post-commit tree verification that fails the tick loudly if ledger content leaked in anyway. A startup audit flags ledger paths found in existing history so the blob can be purged upstream. The opnborg source checkout carries the same .gitignore guard for itself.

❓ FAQ

How do I create an OPNsense API key?

System β†’ Access β†’ Users β†’ add a backup user (scrambled password) β†’ grant Diagnostics: Configuration History (+ System: Firmware for plugin sync) β†’ API Keys β†’ Add.

How do I generate OPN_TLSKEYPIN?
go run paepcke.de/tlsinfo/cmd/tlsinfo@latest <your-opn-server-name>
# X509 Cert KeyPin [base64] : [FezOCC3qZFzBmD5xRKtDoLgK445Kr0DeJBj2TWVvR9M=]
export OPN_TLSKEYPIN='FezOCC3qZFzBmD5xRKtDoLgK445Kr0DeJBj2TWVvR9M='
How do I enable the AI audit?

Install Ollama, ollama pull llama3, set OLLAMA_DESC_URL + OLLAMA_DESC_MODEL, enable OPN_GIT_ENABLE, restart. The config dashboard then shows a live model probe (server reachable, API ready, model ready).

How do I arm admin mode?

/config β†’ Create Authentication Env Vars β†’ copy both OPN_AUTH_* lines into your env β†’ restart. The generator is always available, even when credentials are already armed.

Operational notes
  • Targets need an explicit port unless :443; plain HTTP is unsupported.
  • OS trust-store verification is intentionally off β€” use OPN_TLSKEYPIN.
  • Backup XML downloads are Connection: close and retried on a fresh TLS stream (3 attempts): a truncated / surplus-framed body (the Unsolicited response received on idle HTTP channel + [FAIL:XML-VALIDATION] pair some OPNsense/FreeBSD kTLS appliances emit) is a per-connection record-stream fault β€” a new connection fixes it. Lower OPN_TLS_MAX_VERSION to 1.2 for an appliance that trips it on every pass.
  • Auth sessions live in process memory only: a restart revokes every admin session.
  • The .db/ ledger directory (database plus WAL sidecars, and anything else inside) is always gitignored and hardwire-excluded from commit staging.

🀝 Contributing

PRs welcome β€” see AGENTS.md for the build, test and release workflow. BSD 3-Clause, see LICENSE.

πŸ’– Sponsors: pvz.digital Β· debitor.de πŸ§‘β€πŸŽ¨ UX Borg design: @Codebase-Torben & @Jones71190

πŸ“„ Citation
@misc{opnborg,
  author = {Michael Paepcke},
  title = {Selfhost-able OPNSense Appliance Configuration Management & Backup Portal},
  year = {2024},
  publisher = {GitHub},
  journal = {GitHub repository},
  howpublished = {\url{https://paepcke.de/opnborg}}
}

Made with βš™οΈ by Michael Paepcke β€” the hive thanks you for your compliance.

About

Selfhost OPNSense and UniFI configuration management & backup portal (written in golang, useable via webui, as cli tool, daemon, api or golang-library)

Topics

Resources

Contributing

Security policy

Stars

18 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages