Skip to content

net/nebula: new plugin — Slack Nebula mesh overlay VPN#5504

Open
hstern wants to merge 17 commits into
opnsense:masterfrom
hstern:net-nebula
Open

net/nebula: new plugin — Slack Nebula mesh overlay VPN#5504
hstern wants to merge 17 commits into
opnsense:masterfrom
hstern:net-nebula

Conversation

@hstern

@hstern hstern commented Jun 16, 2026

Copy link
Copy Markdown

What this is

A new plugin net/nebula that brings Slack Nebula — a scalable certificate-authenticated mesh overlay VPN — into the OPNsense web GUI. Each Nebula tunnel becomes a first-class assignable OPNsense interface (like WireGuard's wgN); PKI, daemon lifecycle, overlay firewall, routing and live monitoring are all managed from the UI.

Coverage

  • Multiple instances. Run several independent Nebula daemons on one box (e.g. a lighthouse and a separate client overlay), each with its own certificate, listen port, tun device and overlay firewall.
  • Full upstream config surface. tools/audit_knobs.py reports deferred=0 against Nebula 1.10.x — every documented knob is either modelled as a typed field or rendered through the per-instance YAML generator. Lighthouse allow-lists (remote/local + remote_allow_ranges/calculated_remotes) and the stats (graphite/prometheus) block are surfaced as hand-edited textareas.
  • PKI. Authorities page (encrypted-CA passphrase, expiry, fingerprint), Host Certificates page (sign / CSR-sign / import / purge), Blocklist page (block-until-purged semantics; bulk import).
  • Overlay firewall. Per-instance inbound/outbound rules with CIDR/group/host selectors.
  • Routing. Lighthouse static host map, unsafe routes (non-Nebula subnets over the overlay), per-route MTU overrides.
  • First-class interfaces. nebula_devices() / nebula_interfaces() plugin hooks register nebulaXXXXXX tun devices for Interfaces → Assignments and a nebula group in Firewall → Rules. Device names are stable (md5(uuid)-derived, never re-used) so freed names can't inherit firewall settings.
  • Live monitoring. Tunnels page (peers / groups / known remotes across all instances, parallelised list-hostmap fan-out), per-instance Status page, dashboard widget.
  • Reload-on-Apply. setup.php apply reloads in place via the debug server (Nebula reload = SIGHUP) and only full-restarts when a structural key (listen.host/port, cipher, tun.dev) changed — so a firewall or route edit doesn't drop live tunnels.

Dependency

PLUGIN_DEPENDS = nebulasecurity/nebula, which is already in opnsense/ports at 1.10.3 (commit 2e30ee5a, 2026-06-11). 1.10.x is required for the encrypted-CA passphrase, pki.blocklist, and pki.initiating_version features the plugin exposes.

Tests

  • tools/gen_knobs_test.py — 111 cases. Drives the config generator against every documented knob and asserts the rendered YAML.
  • src/opnsense/mvc/tests/app/models/OPNsense/Nebula/GenerateConfigTest.php — 42 cases. Round-trips model XML → generated YAML.

Verification

Built and deployed end-to-end on a fresh OPNsense 26.1.6 dev VM (FreeBSD 14.3-RELEASE-p10): instance create + CA + cert sign + Apply → daemon starts, tunnel device comes up and is assignable, dashboard widget populates, Tunnels page shows live state.

License + maintainership

BSD 2-Clause throughout. Maintained by henry@stern.ca.

What this isn't

This is the standalone OPNsense plugin for upstream Nebula. It is not affiliated with Slack; it just uses their open-source nebula / nebula-cert binaries.

hstern added 17 commits June 7, 2026 16:42
Removing the master toggle field from the model and pointing the start /
restart / apply paths at per-instance enabled. Matches WireGuard's model
where each instance is its own daemon and there is no global subsystem
enable.

Fixes a UX dead-end on a fresh install: the model had a general.enabled
field with no UI surface, so the instances page Apply / Start widgets did
nothing (serviceEnabled() returned false, the service-control widget did
not render, setup.php apply silently no-op'd) until the field was poked
via the API or config.xml. The plugin's installed PLUGIN_DEPENDS=nebula
binary alone meant a user could not enable the daemon at all without
already knowing the internal model layout.

Changes:
- src/opnsense/mvc/app/models/OPNsense/Nebula/Nebula.xml: drop the
  <general><enabled> field (the only field under <general>).
- src/opnsense/mvc/app/controllers/OPNsense/Nebula/Api/ServiceController.php:
  drop \$internalServiceEnabled; override serviceEnabled() to return true
  when at least one instance is enabled, so the standard reconfigure /
  status / widget plumbing works.
- src/etc/inc/plugins.inc.d/nebula.inc: nebula_enabled() now iterates
  instances (same semantic for the services / devices / interface hooks).
- src/opnsense/scripts/OPNsense/Nebula/setup.php: strip \$general_enabled
  from nebula_apply(), nebula_restart_instance(), and the CLI dispatcher;
  per-instance enabled is the only gate.
- tools/reseed_demo.php: drop the now-unused general.enabled seed.
hstern added a commit to hstern/plugins that referenced this pull request Jun 16, 2026
… drop PR sequencing gate

Adds docs/README (index), overview.md, clients.md, widget.md, and architecture.md following Diataxis (how-to / reference / explanation). architecture.md covers the multi-controller model, token cache, read-only posture + rationale, the ARP/DHCP enrichment join, out-of-scope (VPN/hosting), and the pagination/v1 API quirks. Drops the net/nebula (opnsense#5504) sequencing gate from AGENTS.md — the two plugins are independent and net/omada's PR need not wait.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant