diff --git a/README.md b/README.md index e1dcc3a..70c4801 100644 --- a/README.md +++ b/README.md @@ -1 +1,35 @@ -# pfsense-netbird \ No newline at end of file +# NetBird Package for pfSense + +![My Logo](assets/netbird.png) + +## Build Instructions + +Use **FreeBSD 15.0-CURRENT** as your base system for building the package. + +### Steps: + +``` +git clone git@github.com:netbirdio/pfsense-netbird.git +cd pfSense-pkg-NetBird/ +make +make package +``` + +The `make package` command will generate a file like: + +``` +/work/pkg/pfSense-pkg-NetBird-.pkg +``` + +You can then transfer this file to your pfSense system and install it manually. + +## Installation on pfSense + +To install the package manually on pfSense: + +``` +pkg remove pfSense-pkg-NetBird- +pkg add pfSense-pkg-NetBird-.pkg +``` + +Once installed, **NetBird** will appear under the **VPN** menu in the pfSense Web UI. \ No newline at end of file diff --git a/assets/netbird.png b/assets/netbird.png new file mode 100644 index 0000000..a55f8c7 Binary files /dev/null and b/assets/netbird.png differ diff --git a/netbird/Makefile b/netbird/Makefile new file mode 100644 index 0000000..3e63931 --- /dev/null +++ b/netbird/Makefile @@ -0,0 +1,30 @@ +PORTNAME= netbird +DISTVERSIONPREFIX= v +DISTVERSION= 0.47.0 +CATEGORIES= security net net-vpn + +MAINTAINER= hakan.external@netbird.io +COMMENT= Peer-to-peer VPN that seamlessly connects your devices +WWW= https://netbird.io/ + +LICENSE= BSD3CLAUSE +LICENSE_FILE= ${WRKSRC}/LICENSE + +NOT_FOR_ARCHS= i386 +NOT_FOR_ARCHS_REASON= "no 32-bit builds supported" + +RUN_DEPENDS= ca_root_nss>0:security/ca_root_nss + +USES= go:1.23,modules +USE_RC_SUBR= netbird + +GO_MODULE= github.com/netbirdio/netbird +GO_TARGET= ./client:netbird +GO_BUILDFLAGS= -tags freebsd -o ${PORTNAME} -ldflags "\ + -s -w -X github.com/netbirdio/netbird/version.version=${PORTVERSION}" + +WRKSRC= ${WRKDIR}/netbird-${PORTVERSION} + +PLIST_FILES= bin/netbird + +.include diff --git a/netbird/files/netbird.in b/netbird/files/netbird.in new file mode 100644 index 0000000..191491e --- /dev/null +++ b/netbird/files/netbird.in @@ -0,0 +1,18 @@ +#!/bin/sh +# +# PROVIDE: netbird +# REQUIRE: SERVERS +# KEYWORD: shutdown +# + +. /etc/rc.subr + +name="netbird" +netbird_env="IS_DAEMON=1" +pidfile="/var/run/${name}.pid" +command="/usr/sbin/daemon" +daemon_args="-P ${pidfile} -r -t \"${name}: daemon\"" +command_args="${daemon_args} /usr/local/bin/netbird service run --config /var/db/netbird/config.json --log-level info --daemon-addr unix:///var/run/netbird.sock --log-file /var/log/netbird/client.log" + +run_rc_command "$1" + diff --git a/netbird/pkg-descr b/netbird/pkg-descr new file mode 100644 index 0000000..e3c155b --- /dev/null +++ b/netbird/pkg-descr @@ -0,0 +1,18 @@ +NetBird is an open-source WireGuard-based overlay network combined with +Zero Trust Network Access, providing secure and reliable connectivity +to internal resources. + +Key features: +- Zero-config VPN: Easily create secure connections between devices without +manual network setup. +- Built on WireGuard: Leverages WireGuard's high-performance encryption for +fast and secure communication. +- Self-hosted or Cloud-managed: Users can deploy their own NetBird management +server or use NetBird Cloud for centralized control. +- Access Control & Routing: Fine-grained access control policies and automatic +network routing simplify connectivity. +- This FreeBSD port provides the NetBird client daemon and CLI tools, allowing +FreeBSD systems to join a NetBird mesh network and securely communicate with +other peers. + +For more details, visit: https://netbird.io diff --git a/netbird/pkg-message b/netbird/pkg-message new file mode 100644 index 0000000..c8b893f --- /dev/null +++ b/netbird/pkg-message @@ -0,0 +1,11 @@ +[ +{ type: install + message: < + + + + + + + netbird + %%PKGVERSION%% + VPN/NetBird + /usr/local/pkg/netbird/netbird.inc + /pkg_edit.php?xml=netbird.xml + + NetBird +
VPN
+ netbird.xml + /pkg_edit.php?xml=netbird/netbird_auth.xml +
+ + NetBird +
Status
+ netbird.xml + /netbird_status.php +
+ + + Authentication + pkg_edit.php?xml=netbird/netbird_auth.xml + + + Settings + pkg_edit.php?xml=netbird.xml + + + + Status + netbird_status.php + + + + + General + listtopic + + + WireGuard Port + wireguardport + + + + input + 51820 + + + + + Firewall + listtopic + + + Allow Configuration + allowfirewallconfig + + + + + + + checkbox + on + + + Block Inbound Connection + blockinboundconn + + + + + + + checkbox + + + + SSH + listtopic + + + Enable + enablessh + + + + checkbox + + + + DNS + listtopic + + + Enable + enabledns + + + + checkbox + on + + + + Routing + listtopic + + + Access LAN + accesslan + + + + checkbox + on + + + Accept Client Routes + allowclientroutes + + + + checkbox + on + + + Allow Server Routes + allowserverroutes + + + + checkbox + on + + + + Post-Quantum + listtopic + + + Enable Rosenpass + enablerosenpass + + + + + + + checkbox + + + Rosenpass Permissive Mode + rosenpasspermissive + + + + + + + checkbox + + + + Logging + listtopic + + + Syslog Logging + syslogenable + + + + checkbox + on + + + Syslog Settings + loglevel + Set the syslog logging level + select + info + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird.inc b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird.inc new file mode 100644 index 0000000..9fceac3 --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird.inc @@ -0,0 +1,128 @@ + ['--allow-server-ssh', true], + 'blockinboundconn' => ['--block-inbound', true], + 'allowfirewallconfig' => ['--disable-firewall', false], + 'enabledns' => ['--disable-dns', false], + 'accesslan' => ['--block-lan-access', false], + 'allowclientroutes' => ['--disable-client-routes', false], + 'allowserverroutes' => ['--disable-server-routes', false], + 'enablerosenpass' => ['--enable-rosenpass', true], + 'rosenpasspermissive' => ['--rosenpass-permissive', true], + ]; + + foreach ($options as $key => [$flag, $enabled_value]) { + $is_checked = ($_POST[$key] ?? '') === 'on'; + $cmd[] = $flag . '=' . ($is_checked === $enabled_value ? 'true' : 'false'); + } + + exec(implode(' ', $cmd)); +} + + +function netbird_validate_input($post, &$input_errors) +{ + $wireguard_port = $post['wireguard_port']; + if (!empty($wireguard_port) && (!ctype_digit($wireguard_port) || !is_port($wireguard_port))){ + $input_errors[] = sprintf(gettext('WireGuard port (%s) is not a valid port.'), $wireguard_port); + } +} + +function netbird_is_connected(): bool +{ + $status = netbird_get_status(); + return !empty($status['management']['connected']); +} + +function netbird_is_running(): bool +{ + return is_service_running('netbird'); +} + +function netbird_disconnect(): bool +{ + $cmd = implode(' ', [NETBIRD_BIN, 'down']); + exec($cmd, $out, $result_code); + + return $result_code === 0 && !empty($out); +} + +function netbird_get_status() +{ + $cmd = implode(' ', [NETBIRD_BIN, 'status', '--json']); + exec($cmd, $out, $rc); + + if ($rc !==0 || empty($out)) { + return false; + } + + $status = json_decode(implode('', $out), true); + if (json_last_error() !== JSON_ERROR_NONE || !is_array($status)) { + return false; + } + return $status; +} + +function netbird_display_connection_info(): void +{ + if (!netbird_is_running()) { + $message = gettext('NetBird is not running.'); + $type = 'danger'; + $closable = false; + } elseif (!netbird_is_connected()) { + $message = gettext('NetBird is not connected. Refresh or check the NetBird status page.'); + $type = 'warning'; + $closable = false; + } else { + $message = gettext('NetBird is connected.'); + $type = 'info'; + $closable = true; + } + + print_info_box($message, $type, $closable ? 'close' : false); +} +?> \ No newline at end of file diff --git a/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.inc b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.inc new file mode 100644 index 0000000..a26b95d --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.inc @@ -0,0 +1,65 @@ + + diff --git a/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.xml b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.xml new file mode 100644 index 0000000..0c80a75 --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_auth.xml @@ -0,0 +1,95 @@ + + + + + + + + netbird_auth + %%PKGVERSION%% + VPN/NetBird + /usr/local/pkg/netbird/netbird_auth.inc + /pkg_edit.php?xml=netbird/netbird_auth.xml + + + Authentication + pkg_edit.php?xml=netbird/netbird_auth.xml + + + + Settings + pkg_edit.php?xml=netbird.xml + + + Status + netbird_status.php + + + + + Authentication + listtopic + + + Management URL + managementurl + + + + input + https://api.netbird.io:443 + + + + Setup Key + setupkey + + + + input + + + + Disconnect + disconnect + button + fa-solid fa-right-from-bracket + btn-danger + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_status.inc b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_status.inc new file mode 100644 index 0000000..ec0f770 --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/pkg/netbird/netbird_status.inc @@ -0,0 +1,325 @@ + $row_string){ + foreach (explode($delimiter, $row_string) as $field_idx => $field_string){ + $result[$row_idx][$return_keys[$field_idx]] = $field_string; + } + } + + return $result; +} + +function netbird_display_peer_connection_status() +{ + $summary = netbird_get_peer_connection_status(); + echo netbird_render_status_html_table('Connection Status', $summary); +} + +function netbird_display_peers_details_status(): void +{ + $status = netbird_get_status(); + $peers = $status['peers'] ?? []; + + $summary = netbird_get_peers_detail( + $peers, + $status['quantumResistance'] ?? false, + $status['quantumResistancePermissive'] ?? false + ); + echo netbird_render_status_html_table('Peers Detail', $summary); +} + +function netbird_render_status_html_table(string $name, string $summary): string +{ + $table = Html::el('table') + ->class('table table-hover table-condensed')->addHtml( + Html::el('tbody')->addHtml( + Html::el('tr')->addHtml( + Html::el('td')->addHtml( + Html::el('h5')->addHtml( + Html::el('a')->setText($name) + ) + ) + ) + )->addHtml(Html::el('tr')->addHtml( + Html::el('td')->addHtml( + Html::el('pre')->addAttributes([ + 'style' => 'max-height: 500px; overflow-y: auto;' + ])->setText($summary) + ) + )) + ); + + return $table->toHtml(); +} + +function netbird_get_peer_connection_status(): string +{ + $status = netbird_get_status(); + if (!$status) { + return 'No status available.'; + } + + // Management + $management = $status['management'] ?? []; + if (!empty($management['connected'])) { + $management_str = "Connected"; + if (!empty($management['url'])) { + $management_str .= " to {$management['url']}"; + } + } else { + $management_str = "Disconnected"; + if (!empty($management['error'])) { + $management_str .= ", reason: {$management['error']}"; + } + } + + // Signal + $signal = $status['signal'] ?? []; + if (!empty($signal['connected'])) { + $signal_str = "Connected"; + if (!empty($signal['url'])) { + $signal_str .= " to {$signal['url']}"; + } + } else { + $signal_str = "Disconnected"; + if (!empty($signal['error'])) { + $signal_str .= ", reason: {$signal['error']}"; + } + } + + + // Interface + $interface_type = !empty($status['kernelInterface']) ? "Kernel" : "Userspace"; + $interface_ip = $status['netbirdIp'] ?? 'N/A'; + if (empty($interface_ip)) { + $interface_type = "N/A"; + $interface_ip = "N/A"; + } + + // Relays + $relays_str = ''; + if (!empty($status['relays']['details'])) { + foreach ($status['relays']['details'] as $relay) { + $available = !empty($relay['available']) ? "Available" : "Unavailable"; + $reason = !empty($relay['error']) ? ", reason: {$relay['error']}" : ""; + $relays_str .= "\n [{$relay['uri']}] is {$available}{$reason}"; + } + } else { + $relays_str = sprintf("%d/%d Available", $status['relays']['available'] ?? 0, $status['relays']['total'] ?? 0); + } + + // DNS/Nameservers + $dnsStr = ''; + if (!empty($status['dnsServers'])) { + foreach ($status['dnsServers'] as $group) { + $enabled = !empty($group['enabled']) ? "Available" : "Unavailable"; + $reason = !empty($group['error']) ? ", reason: {$group['error']}" : ""; + $domains = !empty($group['domains']) ? implode(", ", $group['domains']) : "."; + $servers = implode(", ", $group['servers']); + $dnsStr .= "\n [{$servers}] for [{$domains}] is {$enabled}{$reason}"; + } + } else { + $enabled_dns_count = 0; + foreach ($status['dnsServers'] ?? [] as $group) { + if (!empty($group['enabled'])) { + $enabled_dns_count++; + } + } + $dnsStr = sprintf("%d/%d Available", $enabled_dns_count, count($status['dnsServers'] ?? [])); + } + + // Quantum resistance + $quantum = "false"; + if (!empty($status['rosenpassEnabled'])) { + $quantum = "true" . (!empty($status['rosenpassPermissive']) ? " (permissive)" : ""); + } + + // Lazy connection + $lazy = !empty($status['lazyConnectionEnabled']) ? "true" : "false"; + + // Networks + $networks = !empty($status['networks']) ? implode(", ", $status['networks']) : "-"; + + // Peers + $peers_count = sprintf("%d/%d Connected", $status['peers']['connected'] ?? 0, $status['peers']['total'] ?? 0); + + + $info_lines = [ + 'Daemon version' => $status['daemonVersion'], + 'CLI version' => $status['cliVersion'], + 'Management' => $management_str, + 'Signal' => $signal_str, + 'Relays' => $relays_str, + 'Nameservers' => $dnsStr, + 'FQDN' => $status['fqdn'], + 'NetBird IP' => $interface_ip, + 'Interface type' => $interface_type, + 'Quantum resistance' => $quantum, + 'Lazy connection' => $lazy, + 'Networks' => $networks, + 'Forwarding rules' => $status['forwardingRules'], + 'Peers count' => $peers_count, + ]; + + $lines = []; + foreach ($info_lines as $key => $value) { + $lines[] = "{$key}: {$value}"; + } + + return implode("\n", $lines); +} + +function netbird_get_peers_detail(array $peers, bool $rosenpass_enabled, bool $rosenpass_permissive): string { + $peers_str = ''; + + $details = !empty($peers['details']) ? $peers['details'] : []; + foreach ($details as $peer_state) { + $local_ice = !empty($peer_state['iceCandidateType']['local']) ? $peer_state['iceCandidateType']['local'] : '-'; + $remote_ice = !empty($peer_state['iceCandidateType']['remote']) ? $peer_state['iceCandidateType']['remote'] : '-'; + + $local_ice_endpoint = !empty($peer_state['iceCandidateType']['local']) ? $peer_state['iceCandidateType']['local'] : '-'; + $remote_ice_endpoint = !empty($peer_state['iceCandidateType']['remote']) ? $peer_state['iceCandidateType']['remote'] : '-'; + + $rosenpass_enabled_status = 'false'; + if ($rosenpass_enabled) { + if (!empty($peer_state['quantumResistance'])) { + $rosenpass_enabled_status = 'true'; + } else { + if ($rosenpass_permissive) { + $rosenpass_enabled_status = "false (remote didn't enable quantum resistance)"; + } else { + $rosenpass_enabled_status = "false (connection won't work without a permissive mode)"; + } + } + } else { + if (!empty($peer_state['quantumResistance'])) { + $rosenpass_enabled_status = "false (connection might not work without a remote permissive mode)"; + } + } + + $networks = '-'; + if (!empty($peer_state['networks']) && is_array($peer_state['networks'])) { + sort($peer_state['networks']); + $networks = implode(', ', $peer_state['networks']); + } + + $lastStatus_update = (!empty($peer_state['lastStatus_update'])) ? new DateTimeImmutable($peer_state['lastStatus_update']) : new DateTimeImmutable('@0'); + $last_wireguard_handshake = (!empty($peer_state['lastStatus_update'])) ? new DateTimeImmutable($peer_state['lastStatus_update']) : new DateTimeImmutable('@0'); + + $latency = isset($peer_state['latency']) ? floatval($peer_state['latency']) / 1_000_000 . ' ms' : '-'; + + $peer_str = sprintf( + "\n %s:\n". + " NetBird IP: %s\n". + " Public key: %s\n". + " Status: %s\n". + " -- detail --\n". + " Connection type: %s\n". + " ICE candidate (Local/Remote): %s/%s\n". + " ICE candidate endpoints (Local/Remote): %s/%s\n". + " Relay server address: %s\n". + " Last connection update: %s\n". + " Last WireGuard handshake: %s\n". + " Transfer status (received/sent) %s/%s\n". + " Quantum resistance: %s\n". + " Networks: %s\n". + " Latency: %s\n", + // FQDN safe string fallback + $peer_state['fqdn'], + $peer_state['netbirdIp'], + $peer_state['publicKey'] , + $peer_state['status'], + $peer_state['connectionType'] ?? '-', + $local_ice, + $remote_ice, + $local_ice_endpoint, + $remote_ice_endpoint, + $peer_state['relayAddress'] ?? '-', + netbird_get_elapsed_time($lastStatus_update), + netbird_get_elapsed_time($last_wireguard_handshake), + netbird_format_bytes((int)($peer_state['transferReceived'] ?? 0)), + netbird_format_bytes((int)($peer_state['transferSent'] ?? 0)), + $rosenpass_enabled_status, + $networks, + $latency + ); + + $peers_str .= $peer_str; + } + + return $peers_str; +} + +function netbird_get_elapsed_time(DateTimeInterface $dt): string { + if ($dt->getTimestamp() === 0) { + return '-'; + } + + $units = ['y' => 'year', 'm' => 'month', 'd' => 'day', 'h' => 'hour', 'i' => 'minute', 's' => 'second']; + $diff = (new DateTimeImmutable())->diff($dt); + + foreach ($units as $key => $label) { + $value = $diff->$key; + if ($value > 0) { + return "$value $label" . ($value > 1 ? 's' : '') . " ago"; + } + } + + return 'Now'; +} + +function netbird_format_bytes(int $bytes): string { + $unit = 1024; + + if ($bytes < $unit) { + return $bytes . ' B'; + } + + $units = ['Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei']; + $exp = (int) (log($bytes) / log($unit)); + $prefix = $units[$exp - 1]; + $value = $bytes / ($unit ** $exp); + + return sprintf('%.1f %sB', $value, $prefix); +} + +?> \ No newline at end of file diff --git a/pfSense-pkg-NetBird/files/usr/local/share/pfSense-pkg-NetBird/info.xml b/pfSense-pkg-NetBird/files/usr/local/share/pfSense-pkg-NetBird/info.xml new file mode 100644 index 0000000..490c6c4 --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/share/pfSense-pkg-NetBird/info.xml @@ -0,0 +1,12 @@ + + + + NetBird + + https://netbird.io/ + https://github.com/netbirdio/netbird + %%PKGVERSION%% + netbird.xml + + \ No newline at end of file diff --git a/pfSense-pkg-NetBird/files/usr/local/www/netbird_status.php b/pfSense-pkg-NetBird/files/usr/local/www/netbird_status.php new file mode 100644 index 0000000..090ac8a --- /dev/null +++ b/pfSense-pkg-NetBird/files/usr/local/www/netbird_status.php @@ -0,0 +1,88 @@ + '%n', 'version' => '%v', 'comment' => '%c']; +$packages = ['pfSense-pkg-netBird', 'netbird']; + +include('head.inc'); + +netbird_display_connection_info(); + +display_top_tabs($tabs); + +if (netbird_is_running()): +?> +
+
+

+

+
+
+ + +
+
+ + +
+
+

+
+
+ + + + + + + + + + + + + + + + + +
+
+
+0:security/netbird + +NO_ARCH= yes +NO_BUILD= yes +NO_MTREE= yes + +SUB_FILES= pkg-install pkg-deinstall +SUB_LIST= PORTNAME=${PORTNAME} + +do-extract: + ${MKDIR} ${WRKSRC} + +do-install: + ${MKDIR} ${STAGEDIR}${DATADIR} + ${MKDIR} ${STAGEDIR}${PREFIX}/pkg/netbird + ${MKDIR} ${STAGEDIR}${PREFIX}/www + + ${INSTALL_DATA} ${FILESDIR}${DATADIR}/info.xml \ + ${STAGEDIR}${DATADIR} + + ${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/*.xml \ + ${STAGEDIR}${PREFIX}/pkg + + ${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/netbird/*.inc \ + ${STAGEDIR}${PREFIX}/pkg/netbird + + ${INSTALL_DATA} ${FILESDIR}${PREFIX}/pkg/netbird/*.xml \ + ${STAGEDIR}${PREFIX}/pkg/netbird + + ${INSTALL_DATA} ${FILESDIR}${PREFIX}/www/*.php \ + ${STAGEDIR}${PREFIX}/www + + @${REINPLACE_CMD} -i '' -e "s|%%PKGVERSION%%|${PKGVERSION}|" \ + ${STAGEDIR}${DATADIR}/info.xml \ + ${STAGEDIR}${PREFIX}/pkg/netbird.xml \ + ${STAGEDIR}${PREFIX}/pkg/netbird/netbird_auth.xml + +.include \ No newline at end of file diff --git a/pfSense-pkg-NetBird/pkg-descr b/pfSense-pkg-NetBird/pkg-descr new file mode 100644 index 0000000..a89b48d --- /dev/null +++ b/pfSense-pkg-NetBird/pkg-descr @@ -0,0 +1,5 @@ +NetBird is an open-source WireGuard-based overlay network combined with +Zero Trust Network Access, providing secure and reliable connectivity +to internal resources. + +WWW: https://netbird.io \ No newline at end of file diff --git a/pfSense-pkg-NetBird/pkg-plist b/pfSense-pkg-NetBird/pkg-plist new file mode 100644 index 0000000..30edb8c --- /dev/null +++ b/pfSense-pkg-NetBird/pkg-plist @@ -0,0 +1,7 @@ +pkg/netbird.xml +pkg/netbird/netbird.inc +pkg/netbird/netbird_status.inc +pkg/netbird/netbird_auth.inc +pkg/netbird/netbird_auth.xml +%%DATADIR%%/info.xml +www/netbird_status.php