diff --git a/README.md b/README.md index c6bbb5e5..83684bf0 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,30 @@ This repository contains assets required to build the [documentation website for We're glad that you want to contribute! +- [Reporting bugs and requesting features](#reporting-bugs-and-requesting-features) - [Contributing to the docs](#contributing-to-the-docs) +## Reporting bugs and requesting features + +NetBird uses a discussion-first workflow across all its repositories. Bug reports and +feature requests for the docs start in +[netbird Discussions](https://github.com/netbirdio/netbird/discussions), not as issues here. + +| What you want to do | Where to go | +| --- | --- | +| Report a bug, regression, or unexpected behavior | [Issue Triage](https://github.com/netbirdio/netbird/discussions/new?category=issue-triage) | +| Request a feature or share an idea | [Ideas & Feature Requests](https://github.com/netbirdio/netbird/discussions/new?category=ideas-feature-requests) | +| Ask about setup, configuration, or self-hosting | [Q&A / Support](https://github.com/netbirdio/netbird/discussions/new?category=q-a-support) | +| Report a security vulnerability | [Security policy](https://github.com/netbirdio/netbird/security/policy), never a public thread | + +Our team triages each discussion. Validated reports become issues in this repository, +linked back to the discussion. See +[How to use Discussions, Issues, and Pull Requests](https://github.com/netbirdio/netbird/discussions/6075) +for the full workflow. + +Small fixes like typos, broken links, or documentation corrections can go straight to a +[pull request](#contributing-to-the-docs). + ### Requirements * node 16 * npm 8+ diff --git a/mdx/rehype.mjs b/mdx/rehype.mjs index 0ddd7c78..4dfa606c 100644 --- a/mdx/rehype.mjs +++ b/mdx/rehype.mjs @@ -54,7 +54,7 @@ function rehypeInsertLastUpdated() { type: 'element', tagName: 'p', properties: { - className: ['not-prose', 'text-sm', 'text-slate-400', 'dark:text-zinc-500', 'mt-0', 'mb-8', 'ml-2.5'], + className: ['not-prose', 'text-sm', 'text-slate-400', 'dark:text-zinc-500', 'mt-0', 'mb-8', 'md:ml-2.5'], }, children: [ { type: 'text', value: 'Updated ' }, @@ -165,11 +165,30 @@ function rehypeAddMDXExports(getExports) { } } +// Components that render their own `h2` from `title` and `id` props. The +// heading is not in the MDX tree, so it is added to the table of contents here. +const sectionComponents = ['TroubleshootingStart', 'TroubleshootingTiles', 'StillStuck'] + +function jsxStringAttribute(node, name) { + let attribute = node.attributes?.find((a) => a.name === name) + return typeof attribute?.value === 'string' ? attribute.value : undefined +} + function getSections(node) { let sections = [] for (let child of node.children ?? []) { - if (child.type === 'element' && ['h2', 'h3', 'h4', 'h5', 'h6'].includes(child.tagName)) { + if (child.type === 'mdxJsxFlowElement' && sectionComponents.includes(child.name)) { + let title = jsxStringAttribute(child, 'title') + let id = jsxStringAttribute(child, 'id') + if (title && id) { + sections.push(`{ + title: ${JSON.stringify(title)}, + id: ${JSON.stringify(id)}, + tagName: "h2" + }`) + } + } else if (child.type === 'element' && ['h2', 'h3', 'h4', 'h5', 'h6'].includes(child.tagName)) { sections.push(`{ title: ${JSON.stringify(toString(child))}, id: ${JSON.stringify(child.properties.id)}, diff --git a/next.config.mjs b/next.config.mjs index e258e374..893f55f2 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -27,7 +27,7 @@ const nextConfig = { return [ { source: '/slack-url', - destination: 'https://join.slack.com/t/netbirdio/shared_invite/zt-43j76787p-otf3d0nMcJHAQYx46H3jsg', + destination: 'https://join.slack.com/t/netbirdio/shared_invite/zt-4bkyz66cn-Bs~A8lAgIPii5bDl6QUXyA', permanent: false, }, { @@ -910,7 +910,12 @@ const nextConfig = { }, { source: '/selfhosted/activity-mysql-store', - destination: '/selfhosted/maintenance/activity-mysql-store', + destination: '/selfhosted/maintenance/activity-postgres-store', + permanent: true, + }, + { + source: '/selfhosted/maintenance/activity-mysql-store', + destination: '/selfhosted/maintenance/activity-postgres-store', permanent: true, }, { diff --git a/public/docs-static/files/io.netbird.client.plist b/public/docs-static/files/io.netbird.client.plist index ed87c71d..c9d6c56b 100644 --- a/public/docs-static/files/io.netbird.client.plist +++ b/public/docs-static/files/io.netbird.client.plist @@ -3,9 +3,8 @@ + + + + + + + + + + disableAdvancedView : hide the advanced section of the UI. + UI-only: nothing is rejected on its + account. + disableMetricsCollection: reserved; recognized but no effect yet. --> diff --git a/public/docs-static/files/netbird-ios-appconfig.plist b/public/docs-static/files/netbird-ios-appconfig.plist new file mode 100644 index 00000000..b74785f1 --- /dev/null +++ b/public/docs-static/files/netbird-ios-appconfig.plist @@ -0,0 +1,133 @@ + + + + + + + + managementURL + https://api.netbird.io:443 + + + + + + + + + + + + + + + + + + diff --git a/public/docs-static/files/netbird-macos.mobileconfig b/public/docs-static/files/netbird-macos.mobileconfig index d5b54e73..71bd82cc 100644 --- a/public/docs-static/files/netbird-macos.mobileconfig +++ b/public/docs-static/files/netbird-macos.mobileconfig @@ -16,11 +16,22 @@ (confirm against the signed pkg before fleet roll-out) Distribution: - - sign with `productsign --sign "Developer ID Installer: ..." ...` - before fleet roll-out (Apple-Configurator-2 won't install an - unsigned profile on Sonoma+ without user override). - - For local dev install: `sudo profiles install -path netbird-macos.mobileconfig`. - - For MDM (Jamf/Kandji/Mosyle/Intune): upload as a Custom Profile. + - delivered through an MDM, the profile is signed by the MDM + channel itself; nothing to sign by hand, + - handed out any other way (Apple Configurator, double-click), it + installs unsigned and asks the user to confirm on recent macOS. + To avoid that, sign it as a CMS message with an identity the + target Macs trust: + security cms -S -N "Your Signing Identity" \ + -i netbird-macos.mobileconfig -o signed.mobileconfig + Configuration profiles are not signed with productsign, which + handles installer packages. + - For a local test: double-click the file, then install it from + System Settings > General > Device Management. macOS 11 and later + cannot install configuration profiles with the `profiles` command. + - For MDM (Kandji/Mosyle/Intune/Workspace ONE/JumpCloud): upload as + a custom profile. Jamf Pro: prefer io.netbird.client.plist in + Application & Custom Settings, or sign this profile before upload. Editing: - Replace UUID placeholders below with fresh UUIDs (`uuidgen` on @@ -121,6 +132,15 @@ --> + + + + + + + + + @@ -138,7 +179,9 @@ com.acme.app1,com.acme.app2 --> - + diff --git a/public/docs-static/files/netbird-macos.sh b/public/docs-static/files/netbird-macos.sh index d22ea45f..aca8d51b 100644 --- a/public/docs-static/files/netbird-macos.sh +++ b/public/docs-static/files/netbird-macos.sh @@ -53,9 +53,9 @@ set -euo pipefail # docs/netbird.admx + .adml (Windows ADMX schema) # NULL='__UNSET__' -managementURL='https://api.netbird.io:443' +managementURL="$NULL" preSharedKey="$NULL" # secret; redacted in log -allowServerSSH='true' +allowServerSSH="$NULL" blockInbound="$NULL" disableAutoConnect="$NULL" disableAutostart="$NULL" @@ -65,9 +65,15 @@ disableMetricsCollection="$NULL" disableUpdateSettings="$NULL" disableProfiles="$NULL" disableNetworks="$NULL" +disableAdvancedView="$NULL" # UI-only: hides the advanced section, rejects nothing rosenpassEnabled="$NULL" rosenpassPermissive="$NULL" -wireguardPort='51820' +lazyConnection="$NULL" # "true"/"false"; absent defers to the Management setting +wireguardPort="$NULL" +allowRemoteJobs="$NULL" # present at any value locks the client toggle +debugBundleUploadURL="$NULL" # https URL with a host +enableLocalMetrics="$NULL" # local Prometheus endpoint; unrelated to disableMetricsCollection +localMetricsAddress="$NULL" # default 127.0.0.1:9191 splitTunnelMode="$NULL" # "allow" or "disallow", Android-only at the daemon level splitTunnelApps="$NULL" # comma-separated app IDs, Android-only ############################################################################## @@ -175,9 +181,15 @@ main() { is_set "$disableUpdateSettings" && emit_bool disableUpdateSettings "$disableUpdateSettings" is_set "$disableProfiles" && emit_bool disableProfiles "$disableProfiles" is_set "$disableNetworks" && emit_bool disableNetworks "$disableNetworks" + is_set "$disableAdvancedView" && emit_bool disableAdvancedView "$disableAdvancedView" is_set "$rosenpassEnabled" && emit_bool rosenpassEnabled "$rosenpassEnabled" is_set "$rosenpassPermissive" && emit_bool rosenpassPermissive "$rosenpassPermissive" + is_set "$lazyConnection" && emit_bool lazyConnection "$lazyConnection" is_set "$wireguardPort" && emit_int wireguardPort "$wireguardPort" + is_set "$allowRemoteJobs" && emit_bool allowRemoteJobs "$allowRemoteJobs" + is_set "$debugBundleUploadURL" && emit_string debugBundleUploadURL "$debugBundleUploadURL" + is_set "$enableLocalMetrics" && emit_bool enableLocalMetrics "$enableLocalMetrics" + is_set "$localMetricsAddress" && emit_string localMetricsAddress "$localMetricsAddress" is_set "$splitTunnelMode" && emit_split_tunnel_mode "$splitTunnelMode" is_set "$splitTunnelApps" && emit_string splitTunnelApps "$splitTunnelApps" diff --git a/public/docs-static/files/netbird-policy.reg b/public/docs-static/files/netbird-policy.reg index 074e42d4..bd8bd52c 100644 Binary files a/public/docs-static/files/netbird-policy.reg and b/public/docs-static/files/netbird-policy.reg differ diff --git a/public/docs-static/files/netbird.adml b/public/docs-static/files/netbird.adml index 8a62ff04..dd9a85ad 100644 --- a/public/docs-static/files/netbird.adml +++ b/public/docs-static/files/netbird.adml @@ -64,7 +64,25 @@ When enabled, the client UI/CLI cannot list, select or deselect NetBird networks (the corresponding daemon RPCs return Unavailable). Equivalent to --disable-networks. Disable metrics collection - When enabled, the client does not collect or report local usage metrics. + Reserved for a future client telemetry feature. The client recognizes this setting but it has no effect in current releases. + + Disable advanced view + When enabled, the client hides the advanced section of its interface. This policy only changes what the interface offers: it is not part of the daemon configuration and never causes a configuration change to be rejected. Setting it to Disabled explicitly re-enables the section. + + Lazy connections + Local override for lazy connections. Enabled forces lazy connections on, Disabled forces them off, and leaving the policy Not Configured defers to the Management setting. The NB_LAZY_CONN environment variable takes precedence over this policy. + + Allow remote jobs + Allows management-requested remote jobs, such as debug bundle requests, to run on this peer. Off by default; equivalent to --allow-remote-jobs. Configuring this policy at either value locks the corresponding client toggle, so Disabled pins remote jobs off. + + Debug bundle upload URL + Overrides the upload service used for debug bundles produced by remote jobs, taking precedence over the value requested by Management. Must be an https URL including a host. + + Enable local metrics endpoint + Exposes the client's local Prometheus /metrics endpoint. Unrelated to "Disable metrics collection". + + Local metrics listen address + Listen address of the local Prometheus /metrics endpoint. Defaults to 127.0.0.1:9191 when the endpoint is enabled and this policy is Not Configured. @@ -93,6 +111,19 @@ + + + + + + + + + + 127.0.0.1:9191 + + + diff --git a/public/docs-static/files/netbird.admx b/public/docs-static/files/netbird.admx index ddeacc17..71c9ec1e 100644 --- a/public/docs-static/files/netbird.admx +++ b/public/docs-static/files/netbird.admx @@ -231,5 +231,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/docs-static/img/agent-network/control-center/agent-network-control-center-components-panel.png b/public/docs-static/img/agent-network/control-center/agent-network-control-center-components-panel.png new file mode 100644 index 00000000..3d4db539 Binary files /dev/null and b/public/docs-static/img/agent-network/control-center/agent-network-control-center-components-panel.png differ diff --git a/public/docs-static/img/agent-network/control-center/agent-network-control-center-draft-connected.png b/public/docs-static/img/agent-network/control-center/agent-network-control-center-draft-connected.png new file mode 100644 index 00000000..1f065afa Binary files /dev/null and b/public/docs-static/img/agent-network/control-center/agent-network-control-center-draft-connected.png differ diff --git a/public/docs-static/img/agent-network/control-center/agent-network-control-center-edit-provider.png b/public/docs-static/img/agent-network/control-center/agent-network-control-center-edit-provider.png new file mode 100644 index 00000000..5f5ac098 Binary files /dev/null and b/public/docs-static/img/agent-network/control-center/agent-network-control-center-edit-provider.png differ diff --git a/public/docs-static/img/agent-network/control-center/agent-network-control-center-focus-policy.png b/public/docs-static/img/agent-network/control-center/agent-network-control-center-focus-policy.png new file mode 100644 index 00000000..719a1c1f Binary files /dev/null and b/public/docs-static/img/agent-network/control-center/agent-network-control-center-focus-policy.png differ diff --git a/public/docs-static/img/get-started/unraid/netbird-connected.png b/public/docs-static/img/get-started/unraid/netbird-connected.png new file mode 100644 index 00000000..18edc480 Binary files /dev/null and b/public/docs-static/img/get-started/unraid/netbird-connected.png differ diff --git a/public/docs-static/img/manage/access-control/posture-checks/posture-checks-02.png b/public/docs-static/img/manage/access-control/posture-checks/posture-checks-02.png index 3859bcfe..d79fb761 100644 Binary files a/public/docs-static/img/manage/access-control/posture-checks/posture-checks-02.png and b/public/docs-static/img/manage/access-control/posture-checks/posture-checks-02.png differ diff --git a/public/docs-static/img/manage/access-control/posture-checks/posture-checks-certificate.png b/public/docs-static/img/manage/access-control/posture-checks/posture-checks-certificate.png new file mode 100644 index 00000000..cdf3f12b Binary files /dev/null and b/public/docs-static/img/manage/access-control/posture-checks/posture-checks-certificate.png differ diff --git a/public/docs-static/img/manage/networks/netbird-network-routes.png b/public/docs-static/img/manage/networks/netbird-network-routes.png deleted file mode 100644 index 1997d620..00000000 Binary files a/public/docs-static/img/manage/networks/netbird-network-routes.png and /dev/null differ diff --git a/public/docs-static/img/manage/networks/networks-overview.png b/public/docs-static/img/manage/networks/networks-overview.png new file mode 100644 index 00000000..af40b580 Binary files /dev/null and b/public/docs-static/img/manage/networks/networks-overview.png differ diff --git a/public/docs-static/img/manage/networks/through-or-to-the-routing-peer.png b/public/docs-static/img/manage/networks/through-or-to-the-routing-peer.png new file mode 100644 index 00000000..e289da15 Binary files /dev/null and b/public/docs-static/img/manage/networks/through-or-to-the-routing-peer.png differ diff --git a/public/docs-static/img/manage/reverse-proxy/authentication/target-access-badges.png b/public/docs-static/img/manage/reverse-proxy/authentication/target-access-badges.png new file mode 100644 index 00000000..4e5f560d Binary files /dev/null and b/public/docs-static/img/manage/reverse-proxy/authentication/target-access-badges.png differ diff --git a/public/docs-static/img/manage/reverse-proxy/authentication/target-access-setting.png b/public/docs-static/img/manage/reverse-proxy/authentication/target-access-setting.png new file mode 100644 index 00000000..09394499 Binary files /dev/null and b/public/docs-static/img/manage/reverse-proxy/authentication/target-access-setting.png differ diff --git a/public/docs-static/img/manage/team/idp-sync/okta-sync/nwutb3Z.png b/public/docs-static/img/manage/team/idp-sync/okta-sync/nwutb3Z.png deleted file mode 100644 index 4d1518fd..00000000 Binary files a/public/docs-static/img/manage/team/idp-sync/okta-sync/nwutb3Z.png and /dev/null differ diff --git a/public/docs-static/img/manage/team/idp-sync/okta-sync/okta-sso-enter-details.png b/public/docs-static/img/manage/team/idp-sync/okta-sync/okta-sso-enter-details.png new file mode 100644 index 00000000..8363a951 Binary files /dev/null and b/public/docs-static/img/manage/team/idp-sync/okta-sync/okta-sso-enter-details.png differ diff --git a/public/docs-static/img/manage/team/sign-in-domains/authentication-tab.png b/public/docs-static/img/manage/team/sign-in-domains/authentication-tab.png new file mode 100644 index 00000000..1c66d733 Binary files /dev/null and b/public/docs-static/img/manage/team/sign-in-domains/authentication-tab.png differ diff --git a/public/docs-static/img/manage/team/sign-in-domains/dns-verification.png b/public/docs-static/img/manage/team/sign-in-domains/dns-verification.png new file mode 100644 index 00000000..f1790c74 Binary files /dev/null and b/public/docs-static/img/manage/team/sign-in-domains/dns-verification.png differ diff --git a/public/docs-static/img/manage/team/sign-in-domains/login.png b/public/docs-static/img/manage/team/sign-in-domains/login.png new file mode 100644 index 00000000..f39c1c5d Binary files /dev/null and b/public/docs-static/img/manage/team/sign-in-domains/login.png differ diff --git a/src/components/HeroPattern.jsx b/src/components/HeroPattern.jsx index fabc5ae0..c8187a23 100644 --- a/src/components/HeroPattern.jsx +++ b/src/components/HeroPattern.jsx @@ -15,7 +15,17 @@ export function HeroPattern() { [7, 3], [10, 6], ]} - className="absolute inset-x-0 inset-y-[-50%] h-[200%] w-full skew-y-[-18deg] fill-black/40 stroke-black/50 mix-blend-overlay dark:fill-white/2.5 dark:stroke-white/5" + className="absolute + inset-x-0 + inset-y-[-50%] + h-[200%] + w-full + skew-y-[-18deg] + supports-[(-moz-appearance:none)]:skew-y-0 + fill-black/40 + stroke-black/50 + mix-blend-overlay + dark:fill-white/2.5 dark:stroke-white/5" />
diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx index a54a6e11..4eefa80c 100644 --- a/src/components/Layout.jsx +++ b/src/components/Layout.jsx @@ -188,7 +188,7 @@ export function Layout({ children, title, tableOfContents }) {
-
-
- {children} +
+
+ {children}
diff --git a/src/components/MobileNavigation.jsx b/src/components/MobileNavigation.jsx index 54c531be..64188353 100644 --- a/src/components/MobileNavigation.jsx +++ b/src/components/MobileNavigation.jsx @@ -7,6 +7,7 @@ import { Header } from '@/components/Header' import { NavigationAPI } from '@/components/NavigationAPI' import {NavigationDocs} from "@/components/NavigationDocs"; import {useRouter} from "next/router"; +import { useAnnouncements } from '@/components/announcement-banner/AnnouncementBannerProvider' function MenuIcon(props) { return ( @@ -52,6 +53,7 @@ export const useMobileNavigationStore = create((set) => ({ export function MobileNavigation() { let isInsideMobileNavigation = useIsInsideMobileNavigation() let { isOpen, toggle, close } = useMobileNavigationStore() + let { bannerHeight } = useAnnouncements() let ToggleIcon = isOpen ? XIcon : MenuIcon let router = useRouter() @@ -78,7 +80,10 @@ export function MobileNavigation() { leaveFrom="opacity-100" leaveTo="opacity-0" > -
+
@@ -105,7 +110,8 @@ export function MobileNavigation() { > {router.route.startsWith("/ipa") ? : } diff --git a/src/components/NavigationAPI.jsx b/src/components/NavigationAPI.jsx index 337bc76b..06e9a4c4 100644 --- a/src/components/NavigationAPI.jsx +++ b/src/components/NavigationAPI.jsx @@ -55,7 +55,6 @@ export const apiNavigation = [ { title: 'Cloud Resources', links: [ - { title: 'Ingress Ports', href: '/api/resources/ingress-ports' }, { title: 'IDP (Azure API)', href: '/api/resources/idp-azure-integrations', diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx index c77be557..2ad99a9e 100644 --- a/src/components/NavigationDocs.jsx +++ b/src/components/NavigationDocs.jsx @@ -69,6 +69,7 @@ export const docsNavigation = [ { title: 'Proxmox VE', href: '/get-started/install/proxmox-ve' }, { title: 'Synology', href: '/get-started/install/synology' }, { title: 'TrueNAS', href: '/get-started/install/truenas' }, + { title: 'Unraid', href: '/get-started/install/unraid' }, { title: 'pfSense', href: '/get-started/install/pfsense' }, { title: 'OPNsense', href: '/get-started/install/opnsense' }, { title: 'OpenWrt', href: '/get-started/install/openwrt' }, @@ -110,6 +111,22 @@ export const docsNavigation = [ title: 'MDM Deployment', isOpen: false, links: [ + { + title: 'MDM Integration', + href: '/client/mdm-integration', + }, + { + title: 'Enforce Settings on Windows', + href: '/manage/peers/mdm-deployment/windows-mdm-policy', + }, + { + title: 'Enforce Settings on macOS', + href: '/manage/peers/mdm-deployment/macos-mdm-policy', + }, + { + title: 'Enforce Settings on iOS', + href: '/manage/peers/mdm-deployment/ios-mdm-policy', + }, { title: 'Deploy with Group Policy (GPO)', href: '/manage/peers/mdm-deployment/windows-gpo-deployment', @@ -172,6 +189,10 @@ export const docsNavigation = [ title: 'Disable route when in the office', href: '/manage/access-control/posture-checks/connecting-from-the-office', }, + { + title: 'Requiring a device certificate', + href: '/manage/access-control/posture-checks/device-certificates', + }, ], }, { @@ -262,6 +283,10 @@ export const docsNavigation = [ title: 'Bring Your Own Proxy', href: '/manage/reverse-proxy/bring-your-own-proxy', }, + { + title: 'OpenShift', + href: '/manage/reverse-proxy/openshift', + }, { title: 'Custom Domains', href: '/manage/reverse-proxy/custom-domains', @@ -369,6 +394,10 @@ export const docsNavigation = [ // { title: 'JumpCloud', href: '/manage/team/single-sign-on/jumpcloud' }, // ] }, + { + title: 'Sign-in Domains', + href: '/manage/team/sign-in-domains', + }, ], }, { @@ -517,6 +546,7 @@ export const docsNavigation = [ { title: 'Quickstart', href: '/agent-network/quickstart' }, { title: 'Providers', href: '/agent-network/providers' }, { title: 'Clusters', href: '/agent-network/clusters' }, + { title: 'Control Center', href: '/agent-network/control-center' }, ], }, { @@ -598,6 +628,10 @@ export const docsNavigation = [ title: 'High Availability', href: '/selfhosted/maintenance/scaling/high-availability', }, + { + title: 'External Relays (Licensed)', + href: '/selfhosted/enterprise/external-relays', + }, { title: 'Grafana Dashboard', href: '/selfhosted/enterprise/grafana-dashboard', @@ -867,6 +901,10 @@ export const docsNavigation = [ title: 'External IdP to Embedded IdP', href: '/selfhosted/migration/external-to-embedded-idp', }, + { + title: 'MySQL to PostgreSQL', + href: '/selfhosted/migration/mysql-to-postgresql', + }, ], }, ], @@ -875,6 +913,7 @@ export const docsNavigation = [ title: 'CLIENT', links: [ { title: 'Desktop App', href: '/client/desktop-app' }, + { title: 'nblink', href: '/client/nblink' }, { title: 'Profiles', href: '/client/profiles' }, { title: 'Profile Ownership', href: '/client/profile-ownership' }, { title: 'gRPC Daemon Socket', href: '/client/grpc-socket' }, @@ -1026,6 +1065,16 @@ export const docsNavigation = [ }, ], }, + { + title: 'Deployment', + isOpen: false, + links: [ + { + title: 'Roll Out with MDM', + href: '/use-cases/deployment/mdm-rollout', + }, + ], + }, { title: 'Homelab', href: '/use-cases/homelab' }, ], }, @@ -1094,6 +1143,10 @@ export const docsNavigation = [ title: 'Pending approval', href: '/help/troubleshooting-account-access', }, + { + title: 'User in another account', + href: '/help/troubleshooting-user-in-another-account', + }, { title: 'IdP & SSO setup', href: '/manage/team/single-sign-on' }, { title: 'User provisioning', href: '/manage/team/idp-sync' }, { diff --git a/src/components/StillStuck.jsx b/src/components/StillStuck.jsx index 5eb9b145..06920e74 100644 --- a/src/components/StillStuck.jsx +++ b/src/components/StillStuck.jsx @@ -1,48 +1,105 @@ import { Fragment } from 'react' +import Link from 'next/link' import { Button } from '@/components/Button' /** * "Still stuck?" call-to-action banner for the bottom of the Troubleshooting - * hub. Renders a short prompt and a row of action buttons. + * hub. Renders a centered title, a row of action buttons with an optional note + * under each, and an optional row of secondary links. * * @param {string} [title='Still stuck?'] - * @param {string} description - * @param {Array<{label: string, href: string, primary?: boolean}>} actions + * @param {string} [id] - Optional id for the title anchor (listed in "On this page") + * @param {string} [description] + * @param {Array<{label: string, href: string, primary?: boolean, note?: string}>} actions * @param {string} [separator] - Optional word rendered between buttons (e.g. "or"), * so two equally-weighted actions don't read as a recommended-vs-secondary pair. + * @param {string} [linksLabel] - Lead-in text, on its own line above the secondary links + * @param {Array<{label: string, href: string}>} [links] - Secondary text links */ export function StillStuck({ title = 'Still stuck?', + id, description, actions = [], separator, + linksLabel, + links = [], }) { + // `not-prose` on the card would also drop the prose layout (left margin and + // max width), so the outer div keeps it and matches TroubleshootingTiles. return ( -
-

- {title} -

- {description && ( -

- {description} -

- )} -
- {actions.map((action, i) => ( - - {i > 0 && separator && ( - - {separator} - - )} - - - ))} +
+
+

+ {title} +

+ {description && ( +

+ {description} +

+ )} + {/* Stacked in reading order on phones. From `sm` up, a two-row grid puts + each note under its button and keeps the separator level with the + buttons, not the notes. */} +
'minmax(0, 16rem)').join(' auto '), + }} + > + {actions.map((action, i) => ( + + {i > 0 && separator && ( + + {separator} + + )} +
+ +
+ {action.note && ( +

+ {action.note} +

+ )} +
+ ))} +
+ {links.length > 0 && ( +
+ {linksLabel &&

{linksLabel}

} +

+ {links.map((link, i) => ( + + {i > 0 && } + + {link.label} + + + ))} +

+
+ )}
) diff --git a/src/components/announcement-banner/AnnouncementBanner.jsx b/src/components/announcement-banner/AnnouncementBanner.jsx index 1ae0e96f..f2ce8f57 100644 --- a/src/components/announcement-banner/AnnouncementBanner.jsx +++ b/src/components/announcement-banner/AnnouncementBanner.jsx @@ -1,4 +1,4 @@ -import clsx from 'clsx' +import { useLayoutEffect, useRef } from 'react' import Link from 'next/link' import { useAnnouncements } from '@/components/announcement-banner/AnnouncementBannerProvider' @@ -36,19 +36,34 @@ function CloseIcon(props) { function AnnouncementItem({ announcement, onClose }) { const announcementLink = useCustomQueryURL(announcement.link || '') + const bannerRef = useRef(null) + const { setBannerHeight } = useAnnouncements() + + useLayoutEffect(() => { + const banner = bannerRef.current + // Text wrapping and viewport changes can make the banner taller on phones. + const updateHeight = () => setBannerHeight(banner.getBoundingClientRect().height) + updateHeight() + const observer = new ResizeObserver(updateHeight) + observer.observe(banner) + + return () => { + observer.disconnect() + setBannerHeight(0) + } + }, [setBannerHeight]) return (
-
+
{announcement.tag ? ( -
+ {announcement.tag} -
+ ) : null} {announcement.text} @@ -101,4 +116,4 @@ export function AnnouncementBanner() { onClose={closeAnnouncement} /> ) -} \ No newline at end of file +} diff --git a/src/components/announcement-banner/AnnouncementBannerProvider.jsx b/src/components/announcement-banner/AnnouncementBannerProvider.jsx index 51f17ac0..72231de9 100644 --- a/src/components/announcement-banner/AnnouncementBannerProvider.jsx +++ b/src/components/announcement-banner/AnnouncementBannerProvider.jsx @@ -12,10 +12,10 @@ const ANNOUNCEMENTS_URL = 'https://raw.githubusercontent.com/netbirdio/dashboard/main/announcements.json' const STORAGE_KEY = 'netbird-announcements' const CACHE_DURATION_MS = 30 * 60 * 1000 -const BANNER_HEIGHT = 33 const AnnouncementContext = createContext({ bannerHeight: 0, + setBannerHeight: () => {}, announcements: undefined, closeAnnouncement: () => {}, }) @@ -83,6 +83,7 @@ const saveAnnouncements = (closedAnnouncements) => { export function AnnouncementBannerProvider({ children }) { const [announcements, setAnnouncements] = useState(undefined) + const [bannerHeight, setBannerHeight] = useState(0) const fetchingRef = useRef(false) useEffect(() => { @@ -108,12 +109,11 @@ export function AnnouncementBannerProvider({ children }) { [announcements] ) - const bannerHeight = announcements?.some((a) => a.isOpen) ? BANNER_HEIGHT : 0 - return ( + + + ) +} + export const h2 = function H2(props) { return } diff --git a/src/pages/about-netbird/how-netbird-works.mdx b/src/pages/about-netbird/how-netbird-works.mdx index da2324e9..765e2eee 100644 --- a/src/pages/about-netbird/how-netbird-works.mdx +++ b/src/pages/about-netbird/how-netbird-works.mdx @@ -44,6 +44,8 @@ After installation, NetBird client requires login that can be done through Ident * **Keeping the network map.** The Management service stores information about all the registered peers including WireGuard public key that was sent during the registration process. * **Managing private IP addresses.** Each peer receives a unique private IP with which it can be identified in the network. We use [Carrier Grade NAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT) address space with an allocated address block 100.64.0.0/10. +Each account gets its own range inside this block, a randomly chosen /16 by default, and each new peer receives a randomly chosen free address from it. +When a peer is deleted, its address is released immediately and can be assigned to a later peer; there is no holding period. * **Synchronizing network changes to peers.** The Management Service keeps a control channel open to each peer sending network updates. Whenever a new peer joins the network, all other peers that are authorized to connect to it receive an update. After that, they are able to establish a connection to the new peer. @@ -74,7 +76,7 @@ The client application requests a user to log in with an Identity Provider (IDP) * **Receiving network updates from the Management service.** Each peer receives initial configuration and a list of peers with corresponding public keys and IP addresses so that it can establish a point-to-point connection. * **Establishing point-to-point WireGuard connection.** To establish a connection with a remote peer, the Client first discovers the most suitable connection candidate, or simply address (IP:port) that other peers can use to connect to it. -It then sends it to the remote peer via Signal. This message is encrypted with the peer's private key and a public key of the remote peer. +It then sends it to the remote peer via Signal. The message body is encrypted end to end with a shared key that each peer derives from its own private key and the other peer's public key (NaCl `box`: Curve25519, XSalsa20, Poly1305). Signal sees the two peers' public keys, so it knows who is talking to whom, but never the contents. The same shared key also authenticates the message, so there is no separate signature step. The remote peer does the same and once the peers can reach each other, they establish an encrypted WireGuard tunnel. * **Applying access control policies.** The client application applies access control rules that are received from the Management service. The client uses a system's available firewall manager like `nftables` to apply the rules. diff --git a/src/pages/about-netbird/ports-and-firewalls.mdx b/src/pages/about-netbird/ports-and-firewalls.mdx index 382bbf97..fdad0308 100644 --- a/src/pages/about-netbird/ports-and-firewalls.mdx +++ b/src/pages/about-netbird/ports-and-firewalls.mdx @@ -18,6 +18,10 @@ This section covers **network/perimeter firewall** requirements (e.g., Fortigate The NetBird client doesn't require any inbound port to be open; it negotiates the connection with the support of the signal and relay services. + + NetBird peers use WireGuard over UDP (default port `51820`), but you don't need to open that port inbound. Peers connect outbound and use hole punching. If a direct connection isn't possible, they fall back to the relay over UDP/443 (QUIC) or TCP/443 (WebSocket). An outbound rule for UDP port 51820 won't reliably help either: the port a peer sends to is whatever the NAT in front of the other peer assigned, and that is often not 51820. If your policy requires fixed ports, see [Static peer ports for site-to-site firewall rules](#static-peer-ports-for-site-to-site-firewall-rules). + + ### Outbound ports NetBird usually won't need open ports, but sometimes you or your IT team needs to secure and verify all outbound traffic, and that may affect how NetBird clients connect to the [control plane](/about-netbird/how-netbird-works) and negotiate the peer-to-peer connections. diff --git a/src/pages/about-netbird/self-hosted-vs-cloud.mdx b/src/pages/about-netbird/self-hosted-vs-cloud.mdx index 69bf856e..d34d5fcf 100644 --- a/src/pages/about-netbird/self-hosted-vs-cloud.mdx +++ b/src/pages/about-netbird/self-hosted-vs-cloud.mdx @@ -50,14 +50,14 @@ some additional features that are targeted at business customers and help with n - **[Users and groups provisioning](/manage/team/idp-sync)** from your identity provider (IdP). - **[Traffic events logging](/manage/activity/traffic-events-logging)** of connections to internal resources for audit and analysis. -- **[Event streaming](/manage/activity/event-streaming)** to 3rd party platforms and SIEM systems (also available in self-hosted deployments with an [enterprise license](/selfhosted/enterprise)). +- **[Event streaming](/manage/activity/event-streaming)** to 3rd party platforms and SIEM systems. - **[Integrations with EDR](/manage/access-control/endpoint-detection-and-response)** like CrowdStrike and others. - **[Peer approval](/manage/peers/approve-peers)** to join the network. - **[User invites](/manage/team/add-users-to-your-network#direct-user-invites)**. - **[MSP functionality](/manage/for-partners/msp-portal)** for managing multiple tenant networks from a single account. -SCIM provisioning and some enterprise features require a [Commercial License](https://netbird.io/pricing#on-prem) for self-hosted deployments. +Users and groups provisioning (including SCIM), traffic events logging, event streaming, EDR integrations, and peer approval are also available in self-hosted deployments with an [Enterprise Commercial License](/selfhosted/enterprise). The MSP functionality is available only in NetBird Cloud. ## Geo Distributed Relay Servers diff --git a/src/pages/agent-network/control-center.mdx b/src/pages/agent-network/control-center.mdx new file mode 100644 index 00000000..0e178890 --- /dev/null +++ b/src/pages/agent-network/control-center.mdx @@ -0,0 +1,129 @@ +import {Note} from "@/components/mdx" + +export const description = + 'See which groups can reach which AI providers in Control Center, edit providers and agent policies in place, and stage new Agent Network access with Draft Mode.' + +# Agent Network in Control Center + +Agent Network is also available in [Control Center](/manage/control-center) for a better view of who can reach which AI provider. Alongside your peers, groups, and access control policies, it draws Agent Network **providers** and **agent policies** on the same canvas, so you can follow a group to the AI providers it is allowed to reach, edit them in place, and build new access with [Draft Mode](/manage/control-center/draft-mode). + + + **Permissions:** Viewing and changing providers and agent policies in Control Center requires access to Control Center and the same Agent Network permissions as on the **Agent Network** pages. The `Agent Network Admin` role has no access to Control Center, so it manages providers and policies from the **Agent Network** pages only. Learn more about [user roles](/manage/team/user-roles). + + +One example carries through the page: giving the `Engineers` group access to the **Anthropic API** provider. + +## How It Looks on the Canvas + +Agent Network adds two node types to the graph, and they read left to right like an access control policy: + +- **Agent policy:** a pill in the policy column, showing the policy name and a status dot (green when enabled, grey when disabled). It grants its source group access to one or more providers. See [Policies](/agent-network/policies). +- **Provider:** a card in the destination column, showing the provider's logo, name, and upstream URL, like `https://api.anthropic.com`. See [Providers](/agent-network/providers). + +So the path is **group → agent policy → provider**. Disabled policies and providers stay on the canvas, dimmed, so you can still see what they would grant. + +Agent Network nodes show up in the **Peers**, **Users**, and **Groups** views: + +- **Groups:** the providers the selected group can reach, through which agent policies. +- **Peers:** the providers reachable through any of the selected peer's groups. +- **Users:** the providers reachable through the groups of each of the user's peers. + +The **Networks** view does not show Agent Network nodes, because providers are not network resources. + +### Focus on a Policy or Provider + +When the graph gets busy, use [Focus Mode](/manage/control-center#focus-mode) to isolate one path. Right-click an agent policy or provider and choose **Focus**, or press `F` and then select the node. Everything unrelated dims, leaving only the groups and providers connected to it, and a **Focusing on** pill at the top of the canvas shows what is in focus. Close the pill to return to the full view. + +

+ Control Center in the Peer view, focused on an agent policy that connects to the Anthropic API provider, with unrelated nodes dimmed +

+ +## Edit Providers and Agent Policies in Place + +You do not have to leave Control Center to change Agent Network configuration. Click a node, or right-click it and choose **Edit**, to open the same editor you know from the Agent Network pages: + +- **Provider:** the **Edit Provider** dialog, with the **Provider** tab (provider, upstream URL, API key, display name) and the **Models** tab. +- **Agent policy:** the **Update Agent Policy** dialog, with the **Policy**, **Limits**, and **Guardrails** tabs. + +

+ The Edit Provider dialog opened from Control Center, showing the Anthropic API provider, its upstream URL, a masked API key, and the display name +

+ +In the live view, saving asks you to confirm, because the change applies to your account immediately. The right-click menu also offers: + +- **Disable / Enable:** turns the provider or policy off or on right away. +- **Delete:** removes it from your account after a confirmation. + +## Build Access in Draft Mode + +For a new provider, a new policy, or both at once, switch to **Draft** and stage the change before it goes live. Draft Mode works the same as for the rest of Control Center; see [Plan and Deploy Changes with Draft Mode](/manage/control-center/draft-mode) for the full workflow. + +### Add Agent Network Nodes + +Click **Add** in the toolbar (or press `C`) and open the **Agent Network** tab of the components panel: + +- **Add new:** a **Provider** node ("Connect an AI provider or gateway") and an **Agent Policy** node ("Authorize groups to reach providers"). +- **Existing providers** and **Existing agent policies:** the ones already in your account, ready to drag onto the canvas. Each existing policy shows how many groups and providers it connects. + +

+ The Draft Mode components panel on the Agent Network tab, listing the Provider and Agent Policy nodes, existing providers, and existing agent policies +

+ +You can also right-click the canvas and choose **New Agent Policy** or **New Agent Provider**. + +- **Dropping a Provider node** opens the **Connect Provider** dialog. The provider card lands on the canvas once you save it, and its creation is tracked in the draft. +- **Dropping an Agent Policy node** places a blank policy node. It becomes a tracked change once it has a source group and at least one provider. + +For our example, drag the existing **Anthropic API** provider and the existing `Engineers` group onto the canvas. If the provider does not exist yet, drop a new **Provider** node instead and connect it in the dialog. + +### Connect Groups to Policies and Providers + +Draw connections between nodes to wire up access. Unlike access control policies, the drag direction does not matter here: a group is always the source and a provider is always the destination. + +- **Group to provider:** opens the **Create Agent Policy** dialog prefilled with both, named after them (`Engineers → Anthropic API`). This is the quickest way to grant access. +- **Group to agent policy:** sets the policy's source group. +- **Agent policy to provider:** adds the provider to the policy. A policy can reach several providers. + +For our example, connect `Engineers` to **Anthropic API**. Before saving the policy, use the **Limits** and **Guardrails** tabs to cap spend and restrict the policy to the models the team actually uses, so the first version you deploy is already scoped. See [Token & Budget Limits](/agent-network/policies/limits) and [Guardrails](/agent-network/policies/guardrails). + +

+ A draft canvas where the new Engineers group connects through a new agent policy to the Anthropic API provider, with two pending changes on the Review & Deploy button +

+ +### Review and Deploy + +Click **Review & Deploy** to see every change with the request it sends, like `POST /agent-network/policies` for the new `Engineers → Anthropic API` policy. Provider API keys are masked in the preview. + +On deploy, groups are created first, then providers, then agent policies, so a new policy can point at a group and provider created moments earlier in the same batch. + +**Approve & Deploy** stays disabled while a policy is unfinished. The review flags it when: + +- an agent policy has no source group or no provider, +- an agent policy points at a provider or group that is marked for deletion, or +- an agent policy points at a draft provider that was removed from the draft. + +### Remove and Delete + +As elsewhere in Draft Mode, **Remove** takes a node you created in the draft off the canvas, and **Delete** marks an existing provider or agent policy for deletion at deploy. Removing a draft provider also drops any new draft policy that pointed at it. + +## Recap + +- Control Center draws Agent Network as **group → agent policy → provider**, in the Peers, Users, and Groups views. +- Click a provider or agent policy to edit it in place; live saves confirm first and apply immediately. +- In Draft Mode, add providers and agent policies from the **Agent Network** tab, then connect `Engineers` to **Anthropic API** to create a scoped policy in one step. +- Review shows the exact API requests, and the batch deploys groups, then providers, then policies. + +## Related Docs + +- [Control Center overview](/manage/control-center) +- [Plan and Deploy Changes with Draft Mode](/manage/control-center/draft-mode) +- [Agent Network Providers](/agent-network/providers) +- [Agent Network Policies](/agent-network/policies) diff --git a/src/pages/agent-network/quickstart.mdx b/src/pages/agent-network/quickstart.mdx index d588fcd8..eb769fa9 100644 --- a/src/pages/agent-network/quickstart.mdx +++ b/src/pages/agent-network/quickstart.mdx @@ -1,21 +1,98 @@ import { Note } from '@/components/mdx' export const description = - 'Get an LLM request routed through NetBird Agent Network end to end: set up the NetBird server with the proxy, connect a provider, create a policy, and make your first keyless call.' + 'Get an LLM request routed through NetBird Agent Network end to end, on NetBird Cloud or self-hosted: set up a private proxy, connect a provider, create a policy, and make your first keyless call.' # NetBird Agent Network Quickstart -This guide takes you from a fresh server to a working, keyless LLM call through -Agent Network. +This guide takes you to a working, keyless LLM call through Agent Network, either on +NetBird Cloud or on a server of your own. - NetBird Agent Network is open source and self-hosted. This guide sets up a minimal deployment with core functionality; - you can enable the full platform later. - - It is currently in beta, and the setup process installs release candidate versions of the platform. + NetBird Agent Network is currently in beta. It is open source, and you can run it on NetBird Cloud or self-host it. -## Infrastructure Requirements +## Choose Where to Run Agent Network + +Agent Network needs two things: a NetBird account, and a **private proxy** that serves your +agent network endpoint. The proxy is reachable only by devices connected to your NetBird +network, so the endpoint never faces the public internet. + +- **NetBird Cloud** is the fastest path. There is no server to install, and NetBird can run a + dedicated private proxy for your account. Follow [Set Up on NetBird Cloud](#set-up-on-net-bird-cloud). +- **Self-hosted** runs the whole platform, proxy included, on a Linux VM you control. Follow + [Set Up Self-Hosted](#set-up-self-hosted). + +With a managed proxy, your LLM requests pass through a proxy that NetBird operates. If those +requests must stay on your own infrastructure, choose **Deploy a new proxy** during the Cloud +setup instead, or self-host. + +## Before You Begin + +Whichever path you choose, you need: + +- **A device for your agent** that can run the NetBird client, such as your laptop. Agent + network endpoints are reachable only over NetBird, so the agent must run on a connected + device. See [Get Started](/get-started) to install the client. +- **Access to an LLM provider**: the API key for a hosted provider such as Anthropic or OpenAI, + or the URL of an AI gateway or a self-hosted model. You enter it once in the dashboard, and it + never goes on the agent's device. See [Providers](/agent-network/providers). +- **An agent or client to test with**, such as Claude Code, Codex, the OpenAI SDK, or cURL. + +## Set Up on NetBird Cloud + +### Cloud Requirements + +- **A NetBird Cloud account**, or an email address to create one. You create it during the + setup below. +- **The owner or admin role**, if you add Agent Network to an existing account. The guided setup + only opens for those roles. +- **Your own server**, only if you choose **Deploy a new proxy** instead of the managed proxy. + See [Clusters](/agent-network/clusters). + +### Activate Agent Network + +1. [Activate Agent Network on NetBird Cloud](https://app.netbird.io/peers?signup_source=netbird.ai): + sign up, or sign in to your existing account. + - A **new account** opens in the focused Agent Network view, which hides the rest of the + dashboard. You can turn it off later in **Settings**; see + [Focused Dashboard View](/agent-network#focused-dashboard-view). + - An **existing account** keeps its dashboard and gains the **Agent Network** menu. +2. Follow the guided setup. It walks you through the same steps this guide describes: + 1. **Connect your device**: install the NetBird client and connect it. See + [Add Your Device to the Network](#add-your-device-to-the-network). + 2. **Set up your proxy**: choose where your agent network endpoint is served from. + - **Managed proxy**: NetBird deploys a dedicated, private proxy for your account and + reserves your endpoint. This usually takes under a minute. + - **Your own proxy**, to keep LLM requests on your own infrastructure: click **Or use your + own proxy instead**, then select a private proxy you already run or **Deploy a new + proxy**. For a new proxy, enter a domain and run the generated + Docker command or cloud deployment on your server; it starts the proxy in private mode. + The setup continues once the proxy connects and your endpoint is reserved. See + [Clusters](/agent-network/clusters). + 3. **Connect a provider**: see [Connect a Provider](#connect-a-provider). + 4. **Create a policy** (existing accounts only): see [Create a Policy](#create-a-policy). + 5. **Configure your agent**: see [Configure Your Agent](#configure-your-agent), then send a + first request. + +On a new account, the setup also prepares the account for Agent Network's deny-by-default +model: it creates a **Users** group with you in it, creates a starter policy that lets +**Users** reach your first provider, and removes the permissive **Default** access control +policy. An existing account keeps its groups and policies, so you create the Agent Network +policy yourself. + +Once your first request goes through, continue at [Verify in Usage & Logs](#verify-in-usage-and-logs). + +## Set Up Self-Hosted + + + This setup installs a minimal deployment with core functionality, using release candidate versions of the + platform. You can enable the full platform later. + + +### Self-Hosted Requirements + +#### Infrastructure - A Linux VM with at least **1 CPU** and **2 GB** of memory. - The VM must be publicly accessible on **TCP ports 80 and 443**, and **UDP port 3478**. @@ -26,7 +103,7 @@ Agent Network. The public domain is not used for the Agent Network itself. It is only used to establish network connectivity and manage the platform. -## Software Requirements +#### Software - Docker with the docker-compose plugin v2 or higher ([Docker installation guide](https://docs.docker.com/engine/install/)) - [jq](https://jqlang.github.io/jq/): install with `sudo apt install jq` or `sudo yum install jq` @@ -40,8 +117,11 @@ Download and run the installation script: curl -fsSL https://pkgs.netbird.io/getting-started.sh | NETBIRD_AGENT_NETWORK=true bash ``` +The script deploys the NetBird server together with a private proxy, so your deployment +already has the proxy Agent Network needs. + Once the script finishes, open `https://netbird.example.com`, create your admin -account on the setup page, create an admin user, and log in. +account on the setup page, create an admin user, and log in. Then continue with the steps below. ## Add Your Device to the Network @@ -59,8 +139,9 @@ and protected by a peer-to-peer encrypted WireGuard tunnel. You should now see your device in the peers list in the NetBird dashboard. -To access a provider via NetBird, you need to use an **agent network endpoint**, which is generated when you connect your -first provider. +To access a provider via NetBird, you need to use an **agent network endpoint**. On a +self-hosted deployment it is generated when you connect your first provider. On NetBird +Cloud it is reserved when you set up your proxy. ## Connect a Provider @@ -83,7 +164,14 @@ See [Providers](/agent-network/providers) for more details. agent network add provider

-You should now see a newly-generated endpoint above the **Providers** table. + + If **Connect Provider** says you need a reverse proxy cluster with private capabilities, your account has no + private proxy yet. The shared NetBird Cloud proxy cluster can't serve an agent network endpoint. Set up a proxy + first, either through the guided setup or under **Agent Network > Configuration > Clusters**. See + [Clusters](/agent-network/clusters). + + +You should now see your agent network endpoint above the **Providers** table.

agent network endpoint @@ -112,6 +200,9 @@ policy explicitly allows it. A policy connects a **Source Group** (your users or agent devices) to one or more **Providers**, and is where you attach optional token and budget limits and guardrails. +If you set up a new NetBird Cloud account through the guided setup, it already created a +starter policy for the **Users** group. Skip to the test request below. + 1. Go to **Team > Users** and add your user to a group, such as **Engineering**. 2. Go to **Agent Network > Policies** and click **Add Policy**. 3. Select the **Source Group** you want to authorize. @@ -141,4 +232,4 @@ recorded with the caller identity, model, tokens, and cost.

agent network usage & logs -

\ No newline at end of file +

diff --git a/src/pages/agent-network/usage-and-logs/access-logs.mdx b/src/pages/agent-network/usage-and-logs/access-logs.mdx index a7958487..ad93e6dd 100644 --- a/src/pages/agent-network/usage-and-logs/access-logs.mdx +++ b/src/pages/agent-network/usage-and-logs/access-logs.mdx @@ -11,6 +11,14 @@ and the reason a request was allowed or denied. agent network access logs table

+## Who can see what + +`Owner`, `Admin`, `Auditor`, `Agent Network Admin`, and `Usage Viewer` see every caller's requests. Every other user sees only their own requests. + + + When [prompt collection](/agent-network/usage-and-logs/log-collection) is enabled, access log entries include captured prompts and completions. Any user with one of the roles above can read other users' prompts. See [User Roles](/manage/team/user-roles#agent-network-permissions). + + ## Columns - **Time** diff --git a/src/pages/agent-network/usage-and-logs/index.mdx b/src/pages/agent-network/usage-and-logs/index.mdx index 65a46b6c..da518df1 100644 --- a/src/pages/agent-network/usage-and-logs/index.mdx +++ b/src/pages/agent-network/usage-and-logs/index.mdx @@ -20,4 +20,8 @@ identity, cost attribution, and token usage. Token and cost **usage** is always collected, so dashboards and limits stay accurate even if access-log collection is turned off. The full **access log** (request detail and optional prompts) is retained according to your log -collection settings. \ No newline at end of file +collection settings. + +## Who Can See What + +`Owner`, `Admin`, `Auditor`, `Agent Network Admin`, and `Usage Viewer` see usage and access logs for every caller. Every other user sees only their own. When prompt collection is enabled, access logs include captured prompts, so these roles can read other users' prompts. See [User Roles](/manage/team/user-roles#agent-network-permissions). diff --git a/src/pages/agent-network/usage-and-logs/usage-overview.mdx b/src/pages/agent-network/usage-and-logs/usage-overview.mdx index e45b949c..86adda7a 100644 --- a/src/pages/agent-network/usage-and-logs/usage-overview.mdx +++ b/src/pages/agent-network/usage-and-logs/usage-overview.mdx @@ -22,6 +22,10 @@ Filter the view by: Filters apply to both the chart and the table. +## Who can see what + +`Owner`, `Admin`, `Auditor`, `Agent Network Admin`, and `Usage Viewer` see usage for the whole account. Every other user sees only their own usage. See [User Roles](/manage/team/user-roles#agent-network-permissions). + ## Tokens vs. Cost Switch between input/output token totals and estimated USD spend. Cost is diff --git a/src/pages/client/desktop-app.mdx b/src/pages/client/desktop-app.mdx index 40d9701f..80e666fc 100644 --- a/src/pages/client/desktop-app.mdx +++ b/src/pages/client/desktop-app.mdx @@ -116,9 +116,9 @@ The renewal flow follows one rule: re-authenticating never closes an active conn ## Localization -You can switch languages from the **Display Language** picker in **Settings → General**, and the change applies instantly. NetBird v0.75 ships with English, French, German, Hungarian, Italian, Japanese, Portuguese, Russian, Simplified Chinese, and Spanish. +You can switch languages from the **Display Language** picker in **Settings → General**, and the change applies instantly. The desktop app is available in English, French, Galician, German, Hebrew, Hungarian, Italian, Japanese, Portuguese, Russian, Simplified Chinese, Spanish, and Ukrainian. -Translations are a continuing community effort. If your language is missing or a phrase reads incorrectly, please [open a pull request](https://github.com/netbirdio/netbird). +Translations are a community effort on [Crowdin](https://crowdin.com/project/netbird). If your language is missing or a phrase reads incorrectly, join the project and translate there. Crowdin syncs finished translations into the NetBird repository, so please don't open pull requests that edit the translation files directly. ## Capturing a Debug Bundle diff --git a/src/pages/client/environment-variables.mdx b/src/pages/client/environment-variables.mdx index 3dc584ad..0038b834 100644 --- a/src/pages/client/environment-variables.mdx +++ b/src/pages/client/environment-variables.mdx @@ -98,7 +98,6 @@ sudo ip -6 rule del not from all fwmark 0x1bd00 lookup netbird priority 110 | Variable | Platform | Description | |---|---|---| | `NB_DNS_FORWARDER_PORT` | All | Override the port the internal DNS forwarder binds to for handling DNS routes received from clients (default: `22054`). The forwarder is separate from the local resolver. | -| `NB_SKIP_DNS_PROBE` | All | Skip the startup check that sends a test query to the local resolver to verify it is reachable. Set this if the probe causes delays (e.g. in air-gapped networks). | | `NB_DNS_LAZY_WARMUP_TIMEOUT` | All | Per-query budget for waking an idle peer when the local resolver returns one of its A or AAAA records (Go duration, default: `2s`). The value must be positive; invalid, zero, or negative values use the default. | | `NB_USE_LEGACY_DNS_RESOLUTION` | Windows | Available from client v0.78.0. Go back to only setting the NetBird address as the tunnel adapter's nameserver, leaving Windows free to query the other adapters' resolvers in parallel. By default, a peer with a primary nameserver also gets a Name Resolution Policy Table rule that sends every query to the NetBird resolver and nowhere else. Set this if a zone that only the local network resolves has to stay reachable while connected and cannot be declared as a match domain — it also brings back the query leak and the non-deterministic resolution the rule prevents. | | `NB_UNCLEAN_SHUTDOWN_RESOLV_FILE` | Linux, FreeBSD | Custom file path to store a backup of `/etc/resolv.conf` before the client modifies it. Used to restore the original on next startup if the client crashed without cleaning up (default: `/resolv.conf`, i.e. `/var/lib/netbird/resolv.conf` on Linux, `/var/db/netbird/resolv.conf` on FreeBSD). | diff --git a/src/pages/client/mdm-integration.mdx b/src/pages/client/mdm-integration.mdx index 2b26c141..03bc054d 100644 --- a/src/pages/client/mdm-integration.mdx +++ b/src/pages/client/mdm-integration.mdx @@ -1,412 +1,341 @@ import { Note, Warning } from "@/components/mdx"; export const description = - "Enforce NetBird client configuration through your MDM channel (Intune, Jamf, Kandji, Mosyle, Workspace ONE, JumpCloud, Group Policy). Push management URL, kill switches, and other settings to a fleet of Windows and macOS devices."; + "Enforce NetBird client settings on a fleet of Windows, macOS, and iOS devices through your MDM (Intune, Jamf, Kandji, Mosyle, Workspace ONE, JumpCloud, Group Policy): how a policy is applied and locked, and what every policy key does, grouped by the job it solves."; # MDM Integration -NetBird's client honors policies pushed by your Mobile Device Management -(MDM) channel, so an administrator can enforce configuration across a -fleet of devices instead of touching each machine. On every supported -platform the daemon reads from the **OS-native managed-configuration -store** that your MDM already writes to. No extra NetBird software sits between -you and the MDM provider; whatever you can push to that store (manually, -via Group Policy, via a Configuration Profile, via your MDM console) -becomes effective NetBird policy. +Installing NetBird on a few hundred laptops is the easy half of a rollout. The hard half is everything that happens afterwards: a user points the client at a different server, creates a second profile, turns on an SSH server, or switches off a setting your security posture depends on. Fixing that one machine at a time does not scale, and asking users nicely is not a control. -This page covers Windows and macOS. iOS and Android support is on the -roadmap. +The NetBird client solves this by honoring **MDM policy**: settings your device management tool writes to the operating system's own managed-configuration store. The client reads that store, applies what it finds on top of every other setting, and locks those settings so nobody on the device can change them. NetBird adds no agent or connector of its own. If your MDM can write to the store, it can manage NetBird. -## At a glance +This page is in four parts: -| Platform | Where NetBird reads policy | How an admin writes it | +1. [What you can achieve](#what-you-can-achieve): the goals you can reach with a policy, and the setting for each. +2. [Recommended policies](#recommended-policies): a starting policy for user devices, shared devices, and routing peers. +3. [How the client applies a policy](#how-the-client-applies-a-policy): the mental model, and what a user sees on a managed device. +4. [Policy keys reference](#policy-keys-reference), then [verifying enforcement](#verifying-enforcement) and [troubleshooting](#troubleshooting). + +How to deliver a policy is specific to each operating system, so it has its own page: + +| Platform | Where NetBird reads policy | How to deliver it | | --- | --- | --- | -| Windows | `HKLM\Software\Policies\NetBird` (registry) | Group Policy (ADMX) · Intune ADMX ingestion or OMA-URI · `reg import` · MDM-vendor scripts | -| macOS | `/Library/Managed Preferences/io.netbird.client.plist` | Configuration Profile (`.mobileconfig`) pushed by your MDM, targeting bundle id `io.netbird.client` | +| Windows | Registry, `HKLM\Software\Policies\NetBird` | [Enforce settings on Windows](/manage/peers/mdm-deployment/windows-mdm-policy): Intune, Group Policy, `.reg` import, JumpCloud | +| macOS | `/Library/Managed Preferences/io.netbird.client.plist` | [Enforce settings on macOS](/manage/peers/mdm-deployment/macos-mdm-policy): a configuration profile for bundle id `io.netbird.client` | +| iOS and tvOS | Managed App Configuration for bundle id `io.netbird.app` (tvOS: `io.netbird.app.tv`) | [Enforce settings on iOS](/manage/peers/mdm-deployment/ios-mdm-policy): an app configuration sent with the managed app | -Both backends are the de-facto convention for desktop apps (the same -shape Chrome, Edge, Firefox, Zoom, Tailscale, Citrix Workspace, and -others use). Any MDM that supports the platform also supports NetBird — -there is no NetBird-specific integration to build. +Linux and Android have no MDM channel today. See [When not to use MDM policy](#when-not-to-use-mdm-policy). -## How enforcement works +To plan a whole rollout, from installing the client to users signing in and a policy in place, follow [Roll Out NetBird Across Your Organization with MDM](/use-cases/deployment/mdm-rollout), which walks through it with Intune. -When NetBird starts, and every minute while it runs, the daemon: +## What you can achieve -1. Reads the platform-native managed-configuration store. -2. Merges the values **on top of every other configuration layer** - (defaults → on-disk profile → environment variables → CLI/UI input → - **MDM**). MDM always wins. -3. Locks any field that came from the MDM source. Attempts to change - that field from the GUI, the CLI (`netbird up --flag=...`, `netbird - login --flag=...`) or via direct gRPC are rejected with a clear - error listing the locked fields. The client UI greys these fields - out and tags them with **(MDM)** so the user knows they cannot - change them. -4. If the MDM payload changes (admin pushes new values), the change - takes effect within ~1 minute on the device — no client restart - needed. +Find what you want to do, set the key, and deliver it with your MDM (see the platform pages above). Each key links to its full entry in the [Policy keys reference](#policy-keys-reference). + +Include only the keys you mean to pin. A key you set is locked, even when you set it to `false`; a key you leave out stays the user's to change. + +### Set the server and startup + +| You want to | Set | What to know | +| --- | --- | --- | +| Make every device use your management server | [`managementURL`](#managementURL) to your server's URL | Pins the server, not the account: users can still sign out and in to another account on that server. | +| Make devices connect automatically at boot | [`disableAutoConnect`](#disableAutoConnect) to `false` | Users can still disconnect by hand. | +| Make users connect by hand | [`disableAutoConnect`](#disableAutoConnect) to `true` | The client starts disconnected; the user clicks **Connect** or runs `netbird up`. | +| Stop the NetBird app opening at login | [`disableAutostart`](#disableAutostart) to `true` | The background service still runs. A user can switch launch-at-login back on, but the app turns it off again the next time it starts. | + +### Lock the client app + +| You want to | Set | What users can still do | +| --- | --- | --- | +| Stop users changing NetBird's settings | [`disableUpdateSettings`](#disableUpdateSettings) to `true` | Connect, disconnect, and sign in. Every settings change, in the app or on the command line, is refused. | +| Stop users adding a second profile, for example to reach a personal NetBird account | [`disableProfiles`](#disableProfiles) to `true` | Use their current profile as normal. They can still sign out and sign in to a different account; see the [`managementURL`](#managementURL) note. | +| Make sure users keep the networks and exit node you assign | [`disableNetworks`](#disableNetworks) to `true` | Use the assigned networks and exit node. They cannot turn them off. To send all internet traffic through an exit node, also enable **Auto Apply** on it in the dashboard. | +| Give non-technical users a simpler app | [`disableAdvancedView`](#disableAdvancedView) to `true` | Everything except the **Peers** and **Resources** lists. | +| Lock the app down completely, for example on shared or kiosk devices | all four to `true` | Connect and disconnect. | + +None of these settings stops a user from disconnecting NetBird. See [What a policy does not do](#what-a-policy-does-not-do). + +### Control what the device exposes + +| You want to | Set | What to know | +| --- | --- | --- | +| Stop other peers opening connections to a device | [`blockInbound`](#blockInbound) to `true` | Outbound connections still work. It also stops the device's SSH server and its routing. Never use it on a routing peer. | +| Make sure no NetBird SSH server runs on a device | [`allowServerSSH`](#allowServerSSH) to `false` | Wins even when SSH is enabled for the peer in the dashboard. | +| Allow the NetBird SSH server on a device | [`allowServerSSH`](#allowServerSSH) to `true` | SSH must also be enabled for the peer in the dashboard, and `blockInbound` must be off. | +| Stop a laptop from carrying other peers' traffic | [`disableServerRoutes`](#disableServerRoutes) to `true` | Protects against a laptop wrongly assigned as a routing peer. Never use it on a real routing peer. | +| Stop a device using routed networks and exit nodes | [`disableClientRoutes`](#disableClientRoutes) to `true` | Cuts the device off from every network resource and exit node, so it is rarely what you want on a laptop. | + +### Encrypt the tunnel + +| You want to | Set | What to know | +| --- | --- | --- | +| Add a shared secret to every connection | [`preSharedKey`](#preSharedKey) | Every peer, including routing peers and servers, needs the same key, or they cannot connect to each other. Local users can read the key. | +| Add post-quantum protection | [`rosenpassEnabled`](#rosenpassEnabled) and [`rosenpassPermissive`](#rosenpassPermissive) to `true` | Permissive mode lets peers without Rosenpass still connect. Remove it once every peer runs Rosenpass. | + +### Support and monitoring + +| You want to | Set | What to know | +| --- | --- | --- | +| Let support collect debug bundles remotely | [`allowRemoteJobs`](#allowRemoteJobs) to `true`, and [`debugBundleUploadURL`](#debugBundleUploadURL) to your upload server | Bundles hold peer names, IP addresses, and logs; the upload URL keeps them on infrastructure you control. | +| Make sure remote jobs never run on a device | [`allowRemoteJobs`](#allowRemoteJobs) to `false` | Remote jobs are off by default; pinning `false` stops anyone turning them on. | +| Scrape connection state with Prometheus | [`enableLocalMetrics`](#enableLocalMetrics) to `true` | Listens on `127.0.0.1:9191` by default. Keep `localMetricsAddress` on loopback: the endpoint has no authentication. | + +### Tune the connection + +| You want to | Set | What to know | +| --- | --- | --- | +| Keep every connection open on a device | [`lazyConnection`](#lazyConnection) to `false` | Overrides the account setting for that device. `NB_LAZY_CONN` on the device beats this key. | +| Force lazy connections on a device | [`lazyConnection`](#lazyConnection) to `true` | Same precedence as above. | +| Use a fixed WireGuard port | [`wireguardPort`](#wireguardPort) | Only when another program needs the default port or your firewall rules expect a specific one. | + +## Recommended policies + +Start from the column that matches the device group, then add or remove keys for your own posture. Send each policy to one device group, and keep routing peers out of every group that gets `blockInbound` or `disableServerRoutes`. + +| Key | User devices | Shared or kiosk devices | Routing peers and servers | +| --- | --- | --- | --- | +| `managementURL` | ✓ | ✓ | ✓ | +| `disableUpdateSettings` | ✓ | ✓ | ✓ | +| `disableProfiles` | ✓ | ✓ | ✓ | +| `disableServerRoutes` | ✓ | ✓ | never | +| `blockInbound` | optional | ✓ | never | +| `allowRemoteJobs` and `debugBundleUploadURL` | optional | optional | optional | +| `disableNetworks` | optional | ✓ | | +| `disableAdvancedView` | optional | ✓ | | +| `disableAutoConnect: false` (always connect at boot) | | ✓ | ✓ | + +Why each column looks the way it does: + +- **User devices**, such as laptops and desktops, consume resources and never serve them, so `disableServerRoutes` stops one that is wrongly assigned as a routing peer from carrying other peers' traffic. Add `blockInbound` if nothing ever needs to reach these devices. Leave it out if IT reaches them over [NetBird SSH](/manage/peers/ssh), and control inbound access with access policies instead. Add `disableNetworks` to stop users choosing networks and exit nodes (the ones the dashboard assigns keep working), and `disableAdvancedView` to hide the **Peers** and **Resources** lists. +- **Shared or kiosk devices** should leave the user as little as possible to change: no network or exit node choice, no advanced view, and a connection at every boot. +- **Routing peers and servers** must keep forwarding traffic and accepting connections, so they never get `blockInbound` or `disableServerRoutes`. This column only applies to Windows and macOS machines; Linux has no MDM channel, so configure Linux routing peers with [CLI flags](#when-not-to-use-mdm-policy). +- **Remote jobs** are optional in every column. Turn on `allowRemoteJobs` wherever your support team collects [debug bundles](/manage/peers/remote-jobs) remotely, and always pin `debugBundleUploadURL` with it, so bundles go to an upload server you control. Routing peers are often the first place to look when a resource is unreachable or relayed. + +Leave these keys out unless you have a specific reason: + +- `preSharedKey`, `rosenpassEnabled`, `rosenpassPermissive`: each has to match on every peer, or devices lose their connections. See [Encryption keys](#encryption-keys). +- `disableClientRoutes`: cuts a device off from every network resource and exit node. +- `wireguardPort`: the default suits almost every fleet. +- `lazyConnection`: leave lazy connections to the account setting, so one dashboard switch controls the whole fleet. Push `false` only for devices that must hold every connection open. +- `enableLocalMetrics`, `localMetricsAddress`: only for devices you scrape with Prometheus. +- `splitTunnelMode`, `splitTunnelApps`, `disableMetricsCollection`: no effect yet. + +### The user device baseline + +The three keys every column above shares make the usual starting policy, and the platform pages use it as their example. With a self-hosted management server: + +| Key | Value | What it does | +| --- | --- | --- | +| `managementURL` | `https://netbird.example.com:443` | Every device talks to your server and nothing else | +| `disableUpdateSettings` | `true` | Users can connect and disconnect, but cannot change settings | +| `disableProfiles` | `true` | Users cannot add a second profile | + +On NetBird Cloud, use `https://api.netbird.io:443`. Send the policy only to the device group that holds your user devices, so routing peers and servers stay out of it. + +## How the client applies a policy + +Think of a policy as **a set of pinned switches**. Every key in the payload pins one setting to the value you chose and takes it out of the user's reach. Every key you leave out stays an ordinary setting that the user, the CLI, or the dashboard controls as usual. **A key that is absent is not pinned, and a key that is present is pinned, whatever its value.** -MDM is **authoritative**. When a key is present in the MDM payload, the -MDM value wins regardless of whether it is `true` or `false`. An admin -pushing `disableNetworks=false` via MDM re-enables the feature even on a -host that was installed with `--disable-networks`. The MDM payload is -the source of truth as long as the policy is in place. MDM-supplied -values are also never written back to the on-disk profile, so removing -the policy at the MDM side takes effect on the next reload with no -stale residue on the device. +**Pushing `false` is not the same as leaving a key out.** `allowServerSSH: false` pins the SSH server off: no user, CLI flag, or dashboard setting can turn it on while the policy stands. Leaving `allowServerSSH` out of the payload leaves the SSH server to whoever controls the device. Only include the keys you mean to pin. +On Windows and macOS, the client daemon reads the policy at startup and again every minute. Each time: + +1. **It applies the policy on top of every other configuration layer.** Defaults, the on-disk profile, environment variables, and CLI or app input all apply first; the policy is applied last and wins. A policy also overrides the service-install flags `--disable-update-settings`, `--disable-profiles`, and `--disable-networks`: pushing `disableNetworks: false` re-enables network selection even on a device installed with `--disable-networks`. One exception: the `NB_LAZY_CONN` environment variable takes precedence over the `lazyConnection` key. +2. **It locks the pinned settings.** A request that tries to change a pinned setting to a different value, from the desktop app, the CLI (`netbird login --flag`, or `netbird up --flag` on a disconnected client) or the daemon API, is rejected as a whole with `fields managed by MDM cannot be modified: []`. None of its other fields are applied either. A request that repeats the pinned value is accepted, because it changes nothing. These locks read the policy file on every request, so they apply as soon as the file changes; the `disableUpdateSettings`, `disableProfiles`, and `disableNetworks` gates apply at the next reload. Two keys only change what the app shows or does, and lock nothing: [`disableAutostart`](#disableAutostart) and [`disableAdvancedView`](#disableAdvancedView). +3. **It never writes policy values to disk.** The on-disk profile keeps the device's own settings. When you remove a key from the policy, the setting falls back to that on-disk value on the next reload and becomes editable again, with nothing left behind. + +When a reload finds that the policy has changed, the client restarts its connection to apply it. Expect a brief interruption on every policy change (typically a second or two), and plan larger changes outside working hours. The desktop app then shows a notification: **NetBird settings updated**, "Your NetBird configuration was updated by your IT policy." + +On iOS the timing is different: the app passes the policy to the tunnel only while the app is running. See [Enforce settings on iOS](/manage/peers/mdm-deployment/ios-mdm-policy#when-a-policy-takes-effect). + +### What a user sees + +Since NetBird v0.75.0, the desktop app **hides** the controls for pinned settings rather than greying them out. With the user device baseline, the server address field is gone. Read-only mode removes the **Network**, **Security**, **SSH**, and **Advanced** settings tabs, and `disableProfiles` removes the **Profiles** tab. **General**, **Troubleshooting**, and **About** remain, and the **Connect** button still works. + +On the CLI, changing a pinned setting fails with the error above. The [`disableUpdateSettings`](#disableUpdateSettings), [`disableProfiles`](#disableProfiles), and [`disableNetworks`](#disableNetworks) keys have errors of their own, listed under [Troubleshooting](#troubleshooting). + +### What a policy does not do + +A policy pins the client's configuration. It does not make the tunnel mandatory: a user can still disconnect with `netbird down` or the **Disconnect** button, no key blocks `netbird logout` of the active profile (which removes the peer from your account), and anyone with administrator rights on the device can stop the service, uninstall the client, or edit the registry or the managed-preferences file. NetBird has no always-on mode. Enforce that layer with the operating system: give users standard accounts, manage the NetBird service with your MDM, and write your [access policies](/manage/access-control) so that disconnecting from NetBird costs a user access to company resources rather than freeing them from restrictions. + ## Policy keys reference -The same 21 keys apply on every platform. Names are camelCase in the -managed-configuration payload; the Windows ADMX template renders the -PascalCase variant in the Group Policy Editor — both are recognized. +The client recognizes 24 keys. They are grouped below by the job they do, starting with the ones nearly every rollout needs. A key that has no meaning on a platform (for example `allowServerSSH` on iOS) is ignored there, so one payload is safe to send to every platform. -| Key | Type | Description | +Key names are camelCase in plist and app-configuration payloads. On Windows, the registry value names and the Group Policy templates use PascalCase (`ManagementURL`, `BlockInbound`); both forms are recognized, and names are matched without regard to case. In the Group Policy editor, each policy is named after its key in plain words, such as **Block inbound** for `blockInbound`. + +Value types: + +- **boolean**: a native boolean (`` or `` in a plist), or one of the strings `true`/`false`, `1`/`0`, `yes`/`no`, `on`/`off`. An integer (`0` is false, anything else is true) also works on Windows (`REG_DWORD`) and iOS, but **not on macOS**: there, a boolean written as `` is listed as managed and locked, but never applied, so the setting is frozen at whatever the device had. +- **string**: a string. An empty string counts as not set. +- **integer**: an integer, or a string of decimal digits. + +The registry type for each key on Windows is on [Enforce settings on Windows](/manage/peers/mdm-deployment/windows-mdm-policy#value-types). + +### Server and startup keys + +Nearly every rollout sets `managementURL`. The other two keys decide whether NetBird connects, and whether its app opens, on their own. + +| Key | Type | What it does | | --- | --- | --- | -| `managementURL` | string | Override the management server URL (e.g. `https://api.netbird.io:443` or a self-hosted URL). | -| `preSharedKey` | string | WireGuard pre-shared key. Treated as secret and redacted in logs. | -| `wireguardPort` | integer | UDP port the local WireGuard interface binds to. Range `1–65535`. | -| `allowServerSSH` | boolean | Allow the embedded NetBird SSH server on this peer. | -| `allowRemoteJobs` | boolean | Allow management-requested remote jobs (e.g. debug bundles) on this peer. Off by default; equivalent to `--allow-remote-jobs`. | -| `debugBundleUploadURL` | string | Override the upload service used for debug bundles produced by remote jobs, taking precedence over the value requested by Management. Must be an `https` URL with a host. | -| `disableAutoConnect` | boolean | Skip auto-connecting on startup; require an explicit `netbird up`. | -| `disableAutostart` | boolean | Prevent the GUI from registering itself as an OS autostart entry on fresh installs, and — when enabled at any later point — remove an existing registration on the next GUI launch (Windows Registry `Run` key, macOS Login Item, Linux `.desktop`). Desktop GUIs only; no-op on iOS/Android. Once the admin lifts the policy, the setting stays off until the user re-enables it in Settings. | -| `lazyConnection` | boolean | Local override for lazy connections. `true` forces lazy connections on, `false` forces them off, and an absent key defers to the Management setting. `NB_LAZY_CONN` takes precedence when both are configured. | -| `rosenpassEnabled` | boolean | Turn on the post-quantum Rosenpass key exchange. | -| `rosenpassPermissive` | boolean | Permissive mode for Rosenpass (interop with non-Rosenpass peers). | -| `blockInbound` | boolean | Drop all inbound traffic except established/related — kill-switch style. | -| `disableClientRoutes` | boolean | This peer does not route traffic to other peers. | -| `disableServerRoutes` | boolean | This peer is not a router for others. | -| `disableMetricsCollection` | boolean | Disable anonymous usage telemetry. | -| `disableUpdateSettings` | boolean | Block every configuration change from UI or CLI on this device (read-only mode). | -| `disableProfiles` | boolean | Hide the profile menu in the GUI and reject profile CRUD via CLI. | -| `disableNetworks` | boolean | Hide the Networks / Exit Node menus in the GUI and reject the related RPCs. | -| `enableLocalMetrics` | boolean | Expose the client's [local Prometheus `/metrics` endpoint](/client/local-metrics). | -| `localMetricsAddress` | string | Listen address of the local `/metrics` endpoint (default `127.0.0.1:9191`). | +| `managementURL` | string | The management server the client connects to, for example `https://api.netbird.io:443` for NetBird Cloud or `https://netbird.example.com:443` for a self-hosted server. Must start with `http://` or `https://`; a URL without a port gets `:443` (or `:80`) added. An invalid URL is logged and ignored, and the client keeps its previous server. | +| `disableAutoConnect` | boolean | `true`: the client does not connect when its service starts, so the user has to click **Connect** or run `netbird up`. `false`: pins [Connect on Startup](/client/connect-on-startup) on, so the client always connects at boot. | +| `disableAutostart` | boolean | `true`: the desktop app does not open itself at login. The app removes its login entry (the Windows `Run` registry key or the macOS Login Item) every time it starts, and never creates one on a fresh install. The background service is not affected. Desktop app only. | + +- **`managementURL` pins the server, not the account.** NetBird Cloud serves every account from `https://api.netbird.io:443`, so on Cloud this key stops a user from pointing the client at another server, but not from signing in to another Cloud account. No key pins the account: even with `managementURL`, `disableProfiles`, and `disableUpdateSettings` all pinned, `netbird logout` followed by `netbird up --setup-key ` moves the device to another account on the same server. A device signed in to another account has no access to your resources, because your access policies do not apply to it. +- **`disableAutostart` only acts when the app starts.** The **Launch NetBird UI at Login** switch in the app's settings stays usable. A user who turns it back on gets the app at their next login, and the app removes the entry again as soon as it opens. Pushing `false` does not turn launch-at-login on; it only stops the app from removing the entry. + +### Client app keys + +These keys decide how much of the app a user can touch. The user device baseline uses the first two. + +| Key | Type | What it does | +| --- | --- | --- | +| `disableUpdateSettings` | boolean | Read-only mode. Every request that would change a setting is rejected, from the app or the CLI (`netbird login --flag`, or `netbird up --flag` on a disconnected client). Connecting, disconnecting, and signing in again still work, because they change no settings. | +| `disableProfiles` | boolean | Hides profile management in the app and rejects creating, renaming, removing, or switching [profiles](/client/profiles). The device stays on its current profile. | +| `disableNetworks` | boolean | Stops the user changing which networks and exit node the device uses. Networks and exit nodes **keep working**: the device still reaches the resources and exit node the dashboard assigns to it. Only the choice is taken away. The app hides the exit node switcher and the **Resources** tab, and `netbird networks list`, `select`, and `deselect` are rejected. The device keeps whatever selection it had when the policy arrived. | +| `disableAdvancedView` | boolean | Removes **Advanced View** from the desktop app's menu, along with its **Peers** and **Resources** tabs: the list of peers this device can see, with each connection's details, and the list of network resources. A user already in that view is switched back to the simple view. On iOS it hides the Advanced section. It only changes what the app shows: no setting is locked, and no request is rejected because of it. | | `allowGroups` | string | Let only the listed groups connect to the daemon socket. Overrides `--allow-group`. An empty value removes the restriction. On macOS, set one `gid:` value. On Windows, set comma-separated `sid:` values. See [Restrict daemon access to a group](/client/profile-ownership#restrict-daemon-access-to-a-group). | -| `splitTunnelMode` | string | `allow` or `disallow` — split-tunnel policy mode (Android only at the client level; harmless on desktop). | -| `splitTunnelApps` | string | Comma-separated list of package names that the split-tunnel mode applies to (Android only). | -### Notes on a few keys +- **`disableUpdateSettings` covers settings no key names.** The DNS, firewall, interface, and SSH sub-options have no key of their own, so the only way to stop users changing them is read-only mode. On a device with `disableUpdateSettings: true`, an administrator who needs to change one of those settings has to lift the policy first. +- **`disableNetworks` plus an exit node pins the device to that exit node.** With an exit node that has **Auto Apply** enabled, the exit node is applied automatically and the user cannot deselect it. See [Enforcing the exit node on managed devices](/use-cases/remote-access/exit-nodes#enforcing-the-exit-node-on-managed-devices) for the full recipe, including why the policy has to reach the devices before users touch the exit node switch. -- `disableUpdateSettings` and `disableProfiles` overlap with the - service-install CLI flags `--disable-update-settings` and - `--disable-profiles`. Either source can disable the feature; the MDM - value wins when present. -- `disableUpdateSettings` keeps the Settings view in the GUI visible - (so users can inspect current values) but rejects every attempt to - save changes. Use it for read-only fleets. -- `disableNetworks` combined with an exit node that has Auto Apply - enabled pins a device to that exit node: the exit node applies - automatically and the daemon rejects every attempt to deselect it. - See [Enforcing the Exit Node on Managed Devices](/use-cases/remote-access/exit-nodes#enforcing-the-exit-node-on-managed-devices) - for the full recipe, including why the policy must be deployed - before users touch the exit node switch. -- `localMetricsAddress` is applied as pushed. Users are held to a - loopback address unless they are root, because the endpoint is - unauthenticated, but a policy already comes from an administrator and - is not subject to that check. A non-loopback address publishes peer - names and connectivity state to anything that can reach the port. -- `splitTunnelMode` and `splitTunnelApps` are wired into Android's - `VpnService.Builder.addAllowedApplication()` flow; on Windows and - macOS the daemon parses the keys but ignores them. They are safe to - ship in a cross-platform payload. -- `disableAutostart` only affects the desktop GUI's OS autostart entry - (Windows Registry `Run` key, macOS Login Item, Linux `.desktop`). - iOS and Android have no equivalent user-space autostart mechanism — - the daemon runs as a system service — so the key is parsed and - ignored there. Safe to ship in cross-platform payloads. -- The `disableMetricsCollection` key is reserved for an upcoming - metrics integration; the client recognizes it today but no metrics - pipeline is shipped yet. +### Device exposure keys -## Windows +These keys decide what reaches the device, and what the device passes on. Each pins a client-side setting that also exists in the app and the CLI, and each one wins over what the dashboard configures: routes, routing-peer assignments, SSH, and access policies. -The NetBird daemon reads policies from -`HKLM\Software\Policies\NetBird`. Anything that ends up under that -registry key — through whichever delivery channel — becomes policy. -The shapes are: - -| Value name | Registry type | Example | +| Key | Type | What it does | | --- | --- | --- | -| `ManagementURL`, `PreSharedKey`, `SplitTunnelMode`, `SplitTunnelApps` | `REG_SZ` | `"https://api.netbird.io:443"` | -| All `Disable*` flags, `AllowServerSSH`, `RosenpassEnabled`, `RosenpassPermissive` | `REG_DWORD` (0 / 1) | `0x00000001` | -| `WireguardPort` | `REG_DWORD` | `0x0000ca6c` (51820 decimal) | +| `blockInbound` | boolean | `true`: drops every inbound connection from other peers, whatever your access policies allow. It also stops the SSH server and stops the device acting as a routing peer. Outbound connections from the device are not affected. See [Block Inbound Connections](/client/block-inbound-connections). | +| `allowServerSSH` | boolean | `false`: the [NetBird SSH server](/manage/peers/ssh) never runs on this device, even if SSH is enabled for the peer in the dashboard. `true`: lets it run, but only once SSH is also enabled for the peer in the dashboard and `blockInbound` is off. | +| `disableServerRoutes` | boolean | `true`: the device never acts as a [routing peer](/manage/networks/how-routing-peers-work). It does not forward traffic to the networks, resources, or exit node it is assigned to serve. | +| `disableClientRoutes` | boolean | `true`: the device ignores the routes it receives. It does not install routes to network resources reached through a routing peer, and it cannot use an exit node. It can still reach other peers directly. | -Choose one of the delivery channels below. All four converge on the -same registry key. +- **The two route keys are easy to mix up.** "Client" and "server" describe the device's role in a route, not the direction of the traffic. `disableClientRoutes` stops the device *using* routes, which is what an end-user laptop does. `disableServerRoutes` stops the device *providing* routes, which is what a routing peer does. On a laptop, `disableClientRoutes: true` cuts it off from every network resource and exit node, which is rarely what you want. `disableServerRoutes: true` is the safe choice for laptops, because it stops a laptop that has been wrongly assigned as a routing peer from carrying other peers' traffic. +- **Do not send `blockInbound` or `disableServerRoutes` to routing peers.** A routing peer with either key set stops forwarding. If it is the only routing peer for a network, that network becomes unreachable for everyone. Scope these keys to a device group that excludes routing peers and servers. +- **`blockInbound` is not a kill switch.** It blocks connections *to* the device, not traffic *from* it, and it does not keep the tunnel up. Use it for devices that only ever consume resources, such as most end-user laptops, and never for a device other peers need to reach. -### Group Policy (on-prem AD / local gpedit) +### Encryption keys -For a full end-to-end walkthrough — domain Central Store, GPO creation, silent MSI install, and troubleshooting — see [Deploying NetBird with Group Policy (GPO)](/manage/peers/mdm-deployment/windows-gpo-deployment). The steps below cover the minimal local setup. +These keys change how connections are encrypted. A pre-shared key, and Rosenpass outside permissive mode, have to match on both ends of a connection, so a key sent to only part of a fleet can cut that part off from the rest. -1. Copy the ADMX/ADML files into the system Policy Definitions store: - - Place `netbird.admx` in `C:\Windows\PolicyDefinitions\`. - - Place `netbird.adml` in `C:\Windows\PolicyDefinitions\en-US\`. -2. Open `gpedit.msc` (or the AD Group Policy Management Editor). -3. Navigate to **Computer Configuration → Administrative Templates → - NetBird**. -4. Edit any policy (e.g. **Management URL**), set it to **Enabled** - with the desired value, and click **OK**. -5. Run `gpupdate /force` on each target device (or wait for the - periodic refresh). -6. Verify with `reg query HKLM\Software\Policies\NetBird` — the values - you set should appear there. +| Key | Type | What it does | +| --- | --- | --- | +| `preSharedKey` | string | A WireGuard pre-shared key mixed into every connection this device makes. Only peers configured with the **same** key can connect to each other. Redacted in logs, and never returned by the client API. | +| `rosenpassEnabled` | boolean | Turns on [Rosenpass](/client/post-quantum-cryptography), a post-quantum key exchange that rotates a fresh pre-shared key every two minutes. Experimental. A device with Rosenpass on cannot connect to a peer without it unless `rosenpassPermissive` is also on. | +| `rosenpassPermissive` | boolean | With Rosenpass on, lets this device fall back to a standard WireGuard connection with a peer that does not run Rosenpass, and keeps using Rosenpass with the peers that do. | -Download the templates: netbird.admx / netbird.adml. +- **A pre-shared key splits your network in two.** Devices that have the key and devices that do not cannot connect to each other. If you pushed a `preSharedKey` to your laptops only, they would lose every routing peer and server that does not carry the same key. Only use it when every peer, including routing peers and servers, gets the same key. +- **The pre-shared key is not hidden from local users.** By default, standard users can read the NetBird policy key on Windows, and the macOS managed-preferences file is readable by every user. Treat the key as an extra layer of protection, never as a credential that only administrators know. +- **To roll out Rosenpass, start in permissive mode.** Push `rosenpassEnabled: true` with `rosenpassPermissive: true`, so devices that do not have Rosenpass yet can still connect. Remove permissive mode once every peer runs Rosenpass. -### Microsoft Intune (ADMX ingestion) +### Support and monitoring keys -Recommended for cloud-managed Windows fleets. +These keys turn on remote diagnostics and local metrics. Both features expose information about the device, so the safe default is to leave them off and pin them on only where you need them. -1. In the Intune admin center, go to **Devices → Configuration → Import - ADMX**, upload `netbird.admx` together with `netbird.adml`. Wait for - the **Available** status. -2. Create a new **Configuration Profile → Templates → Imported - Administrative templates → NetBird**. -3. Configure the policies you want to enforce. -4. Assign the profile to your device group(s) and save. +| Key | Type | What it does | +| --- | --- | --- | +| `allowRemoteJobs` | boolean | `true`: lets the management server run [remote jobs](/manage/peers/remote-jobs) on this device, such as collecting a debug bundle. Off by default. `false`: pins remote jobs off, so a user cannot turn them on. | +| `debugBundleUploadURL` | string | Where debug bundles produced by remote jobs are uploaded, overriding the location the management server asks for. Must be an `https` URL with a host; any other value is ignored. Treated as a secret and never logged, because the URL can carry credentials or a signed token. | +| `enableLocalMetrics` | boolean | Turns on the client's [local Prometheus `/metrics` endpoint](/client/local-metrics), which reports peer connection state, latency, and whether each connection is direct or relayed. | +| `localMetricsAddress` | string | The address the `/metrics` endpoint listens on. Default `127.0.0.1:9191`. | -Devices pick up the policy on the next Intune sync (typically within -8 hours, sooner if you trigger a manual sync from the device). The -values end up in `HKLM\Software\Policies\NetBird`. +- **Pin the upload URL wherever you allow remote jobs.** A debug bundle holds peer names, IP addresses, and logs. Setting `debugBundleUploadURL` to an upload server you run keeps bundles on infrastructure you control. +- **Keep the metrics endpoint on loopback.** The endpoint has no authentication. A user without administrator rights can only bind it to a loopback address, but a policy value is applied exactly as written. A non-loopback `localMetricsAddress` publishes peer names and connection state to anything that can reach the port. -### Microsoft Intune (custom OMA-URI) +### Connection tuning keys -If you cannot ingest the ADMX template, you can push individual values -via OMA-URI under -`./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/...` or via -the Registry CSP at -`./Device/Vendor/MSFT/Registry/HKEY_LOCAL_MACHINE/Software/Policies/NetBird/`. -ADMX ingestion is simpler and gives admins the same UI as on-prem GPO, -so prefer that. +Most fleets never need these keys. -### `.reg` import (single source of truth) +| Key | Type | What it does | +| --- | --- | --- | +| `lazyConnection` | boolean | Overrides what the management server decides about [lazy connections](/manage/peers/lazy-connection) for this device. `true` forces them on and `false` forces them off, for every peer the device connects to. Leaving the key out follows the account's **Lazy connections** setting, which is on by default for accounts created by management v0.74.0 or later; older accounts keep the setting they had. The `NB_LAZY_CONN` environment variable, if set on the device, takes precedence over this key. | +| `wireguardPort` | integer | The local UDP port the WireGuard interface listens on. Default `51820`. Values outside `1` to `65535` are ignored, and the client keeps its previous port. | -For fleets without an MDM, or as a quick-test path, you can carry the -whole policy in a single `.reg` file: +Set `wireguardPort` only when another program on the device needs the default port, or your firewall rules expect a specific one. -1. Configure the policy values on a reference machine (via `gpedit` or - `reg add`). -2. Export the key: - ``` - reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y - ``` -3. Distribute the resulting file and apply with: - ``` - reg import netbird-policy.reg - ``` +### Keys with no effect yet -Download a sample: netbird-policy.reg. +The client recognizes these keys, so they do no harm in a payload, but none of them changes anything today. -### JumpCloud - -NetBird ships a JumpCloud companion script: netbird-policy.reg.ps1. To use it: - -1. In the JumpCloud admin console, go to **Device Management → - Commands → +**. -2. Type: **Windows PowerShell**. Run as: **SYSTEM**. -3. Paste `netbird-policy.reg.ps1` verbatim into the command body. -4. In the same command, attach the `netbird-policy.reg` file you - produced above. JumpCloud copies attached files into the command's - working directory before invoking the script. -5. Bind the command to the target system group and run it. - -The script wipes the existing `HKLM\Software\Policies\NetBird` key -before importing the `.reg`, so the `.reg` is the **single source of -truth** for that device. To unset all policy, attach an empty (header- -only) `.reg`; the daemon will pick up the absence on the next reload. - -## macOS - -The NetBird daemon reads policy from -`/Library/Managed Preferences/io.netbird.client.plist`. macOS writes -that file when an MDM provider pushes a Configuration Profile whose -`com.apple.ManagedClient.preferences` payload targets the bundle id -`io.netbird.client`. - - -macOS wipes the contents of `/Library/Managed Preferences/` on every -boot if the device is not MDM-enrolled. Manual `defaults write` works -for a quick test but does not survive a reboot on an un-enrolled Mac. -Use a real MDM channel for production rollouts. - - -### Custom Configuration Profile (recommended) - -This is the canonical macOS path and works with every MDM -(Jamf, Kandji, Mosyle, Microsoft Intune for Mac, Workspace ONE, -JumpCloud, Apple Configurator 2, etc.). - -1. Start from the template netbird-macos.mobileconfig. Open it in your editor (or in - [iMazing Profile Editor](https://imazing.com/profile-editor) / - [ProfileCreator](https://github.com/ProfileCreator/ProfileCreator)). -2. Inside the `mcx_preference_settings` dictionary, set the keys you - want to enforce. Keep the bundle id `io.netbird.client` as the - preference domain. -3. Replace the placeholder `PayloadUUID` values with freshly generated - UUIDs (`uuidgen` on macOS) so each deployment has unique ids. -4. (Optional, recommended for production) sign the profile with your - organization's Developer ID Installer certificate using - `productsign` — unsigned profiles on Sonoma/Sequoia/Tahoe require - an extra user confirmation on install. -5. Upload the resulting `.mobileconfig` to your MDM as a **Custom - Configuration Profile** and scope it to the target device group. - -Verify on a target device with: - -```bash -sudo defaults read "/Library/Managed Preferences/io.netbird.client" -``` - -The output should match the keys you set in the profile. - -### MDM-specific notes - -- **Jamf Pro**: upload as **Computers → Configuration Profiles → New → - Application & Custom Settings → External Applications → Upload File - (Plist file)** for the preference domain `io.netbird.client`. -- **Kandji**: use the **Custom Profile** assignment library item. -- **Mosyle**: **Profiles → Add new profile → Custom Settings** with - domain `io.netbird.client`. -- **Microsoft Intune (for Mac)**: **Devices → Configuration → Create - profile → macOS → Templates → Custom**, upload the `.mobileconfig`. -- **Apple Configurator 2** (no MDM, ideal for testing on a tethered - device): drag the `.mobileconfig` onto the device in Configurator and - push. - -### JumpCloud - -JumpCloud supports two delivery channels for the NetBird policy on -macOS. Pick whichever fits how your fleet is enrolled. - -#### MDM Custom Configuration Profile (recommended for MDM-enrolled fleets) - -If your Macs are MDM-enrolled with JumpCloud, push the policy as a -managed-preferences plist: - -1. In the JumpCloud admin console, open **Policy Management → - Policies → +** and choose the **Mac** platform. -2. Pick the **MDM Custom Configuration Profile** policy template. -3. Upload io.netbird.client.plist - as the plist payload. Edit the file before upload to enable just - the keys you want to enforce — leave the rest commented out. -4. Bind the policy to the target Device Group and save. - -Notes: - -- JumpCloud's **MDM Custom Configuration Profile** accepts a bare - managed-preferences plist (the inner Apple managed-prefs dictionary) - — **not** a full `.mobileconfig` envelope. Uploading - `netbird-macos.mobileconfig` will be rejected. Use the bare - `io.netbird.client.plist` for this code path; reserve - `netbird-macos.mobileconfig` for other MDMs that expect the full - Configuration Profile shape. -- Keep the filename as `io.netbird.client.plist`. The Apple - convention for managed-preferences plists is - `.plist` (this is how macOS materializes the file at - `/Library/Managed Preferences/.plist`), and JumpCloud's - policy form does not currently expose a separate bundle-identifier - field — keeping the canonical filename is the safest path. If your - JumpCloud console version surfaces a bundle-id / preference-domain - field elsewhere in the policy wizard, set it to `io.netbird.client` - too. - -JumpCloud wraps the plist into an Apple Configuration Profile and -pushes it via the MDM channel. The OS materializes the file at -`/Library/Managed Preferences/io.netbird.client.plist`, where the -NetBird daemon picks it up within the next 1-minute reload tick. -Removing the policy from JumpCloud removes the file on the next sync, -which un-locks the corresponding fields on the client. - -#### Shell Command (no MDM enrollment required) - -If your fleet is JumpCloud-managed but not MDM-enrolled, NetBird ships -a companion script: netbird-macos.sh. It is the macOS -counterpart of the Windows `.reg.ps1` script — same fleet, different -backend: - -1. Edit the `### POLICY VALUES ###` block at the top of the script; - set the variables for the keys you want to enforce and leave the - rest at `$NULL`. -2. In the JumpCloud admin console, go to **Device Management → - Commands → +**. Type: **Mac, Shell**. Run as: **root**. -3. Paste the edited script verbatim into the command body. -4. Bind to the target system group and run. - -The script writes -`/Library/Managed Preferences/io.netbird.client.plist`, sets ownership -to `root:wheel` with mode `644`, and kicks the NetBird daemon so the -change applies immediately. On MDM-enrolled devices the file survives -reboots; on un-enrolled devices the file is wiped at the next reboot -(macOS-imposed). Prefer the Custom Mac Application Settings policy -above when the fleet is enrolled. +| Key | Type | Status | +| --- | --- | --- | +| `splitTunnelMode` | string | `allow` or `disallow`. Intended for per-app split tunneling on Android, which has no MDM channel yet. Ignored on every other platform. | +| `splitTunnelApps` | string | A comma-separated list of Android package names for `splitTunnelMode`. Same status. | +| `disableMetricsCollection` | boolean | Reserved for a future client telemetry feature. Unrelated to `enableLocalMetrics`. | ## Verifying enforcement -On any platform, the cleanest verification is the daemon's own debug -dump: +On Windows and macOS, ask the daemon what it is enforcing: ```bash netbird debug config ``` -The response includes a `mDMManagedFields` array that lists every key -the daemon is currently honoring from the MDM source. If a key you -expected to be locked is missing from that array, the MDM payload did -not reach the device (or used a value name the daemon does not -recognize). +This prints the resolved configuration as JSON, after every layer including the policy. Its `mDMManagedFields` array lists every key in the policy the client reads. For the user device baseline it reads: -The client UI mirrors the same state: any submenu item, settings field, -or kill switch driven by MDM appears greyed out with a **(MDM)** tag -next to its label. +```json +"mDMManagedFields": [ + "disableProfiles", + "disableUpdateSettings", + "managementURL" +] +``` -Daemon logs (`/var/log/netbird/client.log` on Linux/macOS, -`%ProgramData%\Netbird\` on Windows) contain a one-line -`MDM enrolled with N managed key(s): [...]` entry on every reload, plus -one `MDM override = ` line per applied key. Secrets are -redacted. +If a key you sent is missing from the array, the policy did not reach the device, or it used a name the client does not recognize. + +`netbird debug config` reads the policy file at the moment you run it, so it shows a change straight away. The daemon applies the change at its next reload, within a minute, and logs it: `MDM policy changed: added=[...] removed=[...] changed=[...]`, followed, while the client is connected, by `MDM policy changed; restarting engine to apply new configuration`. Those lines are the confirmation that the policy is enforced, not just delivered. The client log records the same information on every reload: one `MDM enrolled with N managed key(s): [...]` line, and one `MDM override = ` line for each setting it overrode, with secrets redacted. The log is `/var/log/netbird/client.log` on macOS and `C:\ProgramData\Netbird\client.log` on Windows. + +On iOS there is no CLI. See [Verifying on a device](/manage/peers/mdm-deployment/ios-mdm-policy#verifying-on-a-device). ## Troubleshooting -**The policy did not apply at all.** -Check that the daemon can see the source. +Problems specific to one delivery channel, such as a Group Policy that never reaches the registry or a macOS profile that disappears after a reboot, are covered on the [Windows](/manage/peers/mdm-deployment/windows-mdm-policy#intune-troubleshooting), [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#troubleshooting), and [iOS](/manage/peers/mdm-deployment/ios-mdm-policy#troubleshooting) pages. The problems below look the same on every platform. -- Windows: `reg query HKLM\Software\Policies\NetBird` — if empty, the - delivery channel did not write the values. Check `gpresult /h` for - GPO failures or the Intune sync status in **Settings → Accounts → - Access work or school → Info → Sync**. -- macOS: - `sudo defaults read /Library/Managed\ Preferences/io.netbird.client` - — if the file is missing, the MDM payload was not pushed or the - bundle id in the profile does not match `io.netbird.client`. +**`mDMManagedFields` is empty or missing keys.** +Start at the source. Confirm the values are in the registry or the managed-preferences file, using the commands on the platform page. If they are, look for `MDM ignoring unknown` in the client log: the value name does not match any key in the [reference](#policy-keys-reference). Names are matched without regard to case, but must otherwise be exact. `WireGuardPort`, for example, matches; `WgPort` does not. -**The key shows up in the registry / plist but not in `netbird debug -config` `mDMManagedFields`.** -The value name is misspelled. Names are case-insensitive but must match -one of the keys in the reference table above. The daemon log emits an -`MDM ignoring unknown ` warning when this happens. +**A key is listed in `mDMManagedFields` but the setting did not change.** +The value is the wrong type or out of range. The key still counts as managed, so users cannot change the setting, but the client keeps its previous value. The client log has a warning such as `MDM management URL "..." invalid` or `MDM wireguard port ... out of range`. On Windows, check that the value has the [registry type](/manage/peers/mdm-deployment/windows-mdm-policy#value-types) the key expects: a `managementURL` stored as a `REG_DWORD`, for example, is ignored. On macOS, check that booleans are `` or ``, not ``; the client logs no warning for those. -**The user can still change the field from the GUI / CLI.** -The change is being rejected by the daemon but the UI may not have -caught up yet. The UI refreshes within a couple of seconds after a -config change; try closing and reopening the Settings window. If the -change actually sticks, double-check that the MDM payload is still -present on the device — it may have been removed by another policy. +**A user gets `fields managed by MDM cannot be modified: [...]`.** +The policy is working: the user, or a script, tried to change a pinned setting. The keys in the brackets are the pinned settings involved. Change those settings through your MDM instead. -**On macOS, the file disappears after a reboot.** -The device is not MDM-enrolled. macOS protects -`/Library/Managed Preferences/` by wiping it at boot if no MDM -controls the directory. Enroll the device with a real MDM provider for -persistent rollouts. +**`netbird up --flag` prints `Already connected` and changes nothing.** +On a connected client, `netbird up` ignores its flags, whether or not a policy is in place. Run `netbird down` first. On a managed device, the change is then rejected if the policy pins that setting. -**My MDM provider is not in the list above.** -Any MDM that can push a Configuration Profile on macOS or write a -registry value on Windows works. The mechanism is OS-native, not -NetBird-specific. If you hit a quirk specific to your provider, please -open an issue at -https://github.com/netbirdio/netbird/issues with the provider name and -what you observed. +**A user gets `update settings are disabled, you cannot use this feature without update settings enabled`.** +`disableUpdateSettings` is on, or the service was installed with `--disable-update-settings`. Every settings change is rejected, including changes to settings no key names. Lift the policy to make the change. + +**A user gets `profiles are disabled, you cannot use this feature without profiles enabled`.** +`disableProfiles` is on, or the service was installed with `--disable-profiles`. The device stays on its current profile. + +**A user gets `network selection is disabled by the administrator`.** +`disableNetworks` is on, or the service was installed with `--disable-networks`. + +**Lazy connections do not follow the policy.** +The `NB_LAZY_CONN` environment variable is set on the device, and it takes precedence over `lazyConnection`. Remove it from the service environment. + +**Devices lost access to network resources after a policy change.** +Check for `disableClientRoutes: true` or a `preSharedKey` in the policy. The first stops the device using routes; the second stops it connecting to any peer that does not have the same key. See [Device exposure keys](#device-exposure-keys) and [Encryption keys](#encryption-keys). + +**A network became unreachable for everyone after a policy change.** +A routing peer received `blockInbound: true` or `disableServerRoutes: true`, and stopped forwarding. Take routing peers out of the policy's scope. + +**My MDM is not mentioned on these pages.** +Any MDM that can write a registry value on Windows, push a configuration profile on macOS, or send an app configuration on iOS works, because the mechanism belongs to the operating system, not to NetBird. + +## When not to use MDM policy + +- **Linux and Android devices.** Neither has an MDM channel in NetBird today. On Linux, set the same options with `netbird up` flags or the service-install flags (`netbird service install --disable-update-settings --disable-profiles --disable-networks`), or [bootstrap the client from a config file](/manage/peers/bootstrap-via-config-file). +- **One device, one change.** To change a setting on a single machine you manage by hand, use the app or the CLI. A policy is for settings that must hold across a fleet and must not drift. +- **Access control.** A policy configures the client on one device. Which peers can reach which resources belongs in [access policies](/manage/access-control), which apply to every device and do not depend on an MDM. + +## TL;DR + +- **Find the goal, set the key.** [What you can achieve](#what-you-can-achieve) maps each goal to a setting, and [the user device baseline](#the-user-device-baseline) (`managementURL`, `disableUpdateSettings`, `disableProfiles`) is the usual start. +- **Present means pinned, even as `false`.** Every key you leave out stays the user's. The client applies the policy last, locks what it pins, re-reads it every minute, and restarts the connection briefly when it changes. +- **Keep routing peers out of the wrong policy.** Never send them `blockInbound` or `disableServerRoutes`, and send a `preSharedKey` to every peer or none. +- **Verify with `netbird debug config`, then the log.** `mDMManagedFields` shows the policy the client reads; the `MDM policy changed` log line confirms it is enforced. diff --git a/src/pages/client/nblink.mdx b/src/pages/client/nblink.mdx new file mode 100644 index 00000000..00e85764 --- /dev/null +++ b/src/pages/client/nblink.mdx @@ -0,0 +1,247 @@ +import { Note, Warning } from '@/components/mdx' + +export const description = + 'Forward local ports into your NetBird network from an unprivileged process, without installing the agent or granting root.' + +# nblink + +`nblink` forwards local ports into your NetBird network from an ordinary, +unprivileged process. It runs the client in userspace mode, so there is no TUN +device to create and no root to grant. + +That makes it useful where the regular agent cannot run: + +- Managed laptops where you cannot install software that needs administrator rights. +- Rootless containers, including OpenShift and Podman. +- Short-lived environments such as CI jobs, where installing an agent is not worth it. + +Names on the upstream side of a forward are resolved inside the tunnel, so +`/etc/resolv.conf` is never modified. + + + `nblink` forwards HTTP. Support for raw TCP, UDP and SOCKS5 is planned, and + the way a forward is written will not change when they arrive. + + +## Use cases + +### An MCP server or local AI agent reaching an internal API + +AI agents increasingly run in containers or restricted sandboxes where +installing a client is not an option, and the consumer is code rather than a +person, so opening a browser is not a substitute. + +```shell +nblink --forward 'http://3000=https://tickets.internal' --state-dir ~/.nblink +``` + +Point the MCP server at `http://localhost:3000`. The agent carries a real peer +identity, so what it can reach is decided by your policies rather than by +whatever the agent was told to do. + +### Agent Network access without installing a client + +An [Agent Network](/agent-network) endpoint only answers when you are connected +to your network and authenticated through your IdP, which normally means +installing the client and therefore having root. + +```shell +nblink --forward 'http://8080=https://mirror.netbird.ai' +``` + +Set the agent's base URL to `http://localhost:8080`. It still gets identity, +spending limits and the models you approved, and no API key is handed to the +agent itself. + +### A sidecar for an app you cannot modify + +A vendor or legacy application hardcodes an HTTP endpoint and ignores proxy +settings, so pointing it somewhere else is not possible. Running `nblink` +beside it gives it `localhost:8080` without touching the application, on +platforms that forbid privileged containers and assign an arbitrary UID at run +time. + +## Install + +Download the archive for your platform from the +[releases page](https://github.com/netbirdio/netbird/releases), extract it, and +run the binary. There is no installer and no service to register. + +```shell +tar -xzf nblink__linux_amd64.tar.gz +./nblink --help +``` + +## Forward a port + +A forward is a single argument, and the flag repeats: + +```shell +nblink --forward 'http://8080=https://grafana.internal' +``` + +The scheme on the left selects the listener type. The right side is the +upstream reached over your network. Giving only a port binds loopback, so the +command above listens on `127.0.0.1:8080`. + +With no setup key, `nblink` opens your browser to log in. It always prints the +login URL as well, so the same command works over SSH. + +Forward several services from one session and one login: + +```shell +nblink --forward 'http://8080=https://grafana.internal' \ + --forward 'http://9090=https://prometheus.internal' +``` + +Use `0` as the port to let the operating system pick a free one. The address +actually bound is printed when the forward starts. + +Check a configuration without connecting to anything: + +```shell +nblink --forward 'http://8080=https://grafana.internal' --check +``` + +## Run without a browser + +Pass a [setup key](/manage/peers/register-machines-using-setup-keys) to log in +without any interaction: + +```shell +nblink --forward 'http://8080=https://grafana.internal' --setup-key "$NB_SETUP_KEY" +``` + +## Run in a container + +Every flag has a matching `NB_`-prefixed environment variable, which is how the +image is configured. `NB_FORWARD` accepts a comma separated list. + +```shell +docker run --rm -p 127.0.0.1:8080:8080 \ + --mount type=bind,src="$(pwd)/nb_setup_key",dst=/run/secrets/nb_setup_key,readonly \ + -e NB_SETUP_KEY=file:/run/secrets/nb_setup_key \ + -e NB_FORWARD='http://0.0.0.0:8080=https://grafana.internal' \ + -e NB_ALLOW_PUBLIC_BIND=true \ + netbirdio/nblink +``` + +The image needs no `--privileged`, no `--cap-add NET_ADMIN`, no +`--device /dev/net/tun` and no host networking. It runs as a non-root user and +works on platforms that assign an arbitrary UID at run time. + +Inside a container the listener has to bind `0.0.0.0`, because a published +port arrives on the container's own interface rather than its loopback. That +still requires `NB_ALLOW_PUBLIC_BIND`, so exposing a forward is always +something you wrote down. + + + Publish the port on the host's loopback, as above. A bare `-p 8080:8080` + publishes it on every host interface, and anything that can reach the host + can then use the forward under the peer's identity. Publish on another + address only when the network around the host restricts who can reach it. + + +A forward on a public address accepts requests addressed to `localhost` or to +an IP address. A name that other machines use to reach it, such as a DNS name +or a Kubernetes Service name, has to be listed with `--allowed-host` +(`NB_ALLOWED_HOST`); an unlisted name is answered with `421`. See +[Access control](#access-control) for what listing a name trusts. + + + The key file must exist before the run and be readable by the container user, + UID 65532 in group 0. A bind mount keeps the host file's owner and mode, so a + root-owned key written with the usual `chmod 600` is not readable and startup + fails with `permission denied`. Either give the file to that UID + (`chown 65532`) or keep it mode `640` in group 0 (`chgrp 0`). If the source + path does not exist, `--mount` refuses to start the container; the older `-v` + form creates a directory there instead. + + +The image keeps its state in memory, so it runs on a read-only root filesystem +and each start registers a new peer. To keep one identity across restarts, set +`NB_STATE_DIR` and mount a writable volume there: + +```shell +docker run --rm --read-only -p 127.0.0.1:8080:8080 \ + -v nblink-state:/var/lib/nblink -e NB_STATE_DIR=/var/lib/nblink \ + ... +``` + +Without a state directory, use an ephemeral setup key so the peers left behind +by earlier starts are removed once they go offline. + +A kept identity is not yet a kept login. Without a setup key every start runs +the browser flow, even when the peer's session is still valid. Give a restart +that has to be unattended a setup key. + + + A value given as `file:/path` is read from that file. Prefer it for setup + keys in containers, so the secret is not visible to anything that can read + the process environment. + + +## Configuration + +| Flag | Environment variable | Default | Description | +| --- | --- | --- | --- | +| `--forward` | `NB_FORWARD` | | Forward spec, repeatable. Comma separated in the environment. | +| `--setup-key` | `NB_SETUP_KEY` | | Log in without a browser. Accepts a `file:` prefix. | +| `--management-url` | `NB_MANAGEMENT_URL` | NetBird cloud | Management server URL. | +| `--hostname` | `NB_HOSTNAME` | host name | Peer name in your network. | +| `--state-dir` | `NB_STATE_DIR` | memory | Directory for identity and state. | +| `--log-level` | `NB_LOG_LEVEL` | `info` | Log level. | +| `--allow-public-bind` | `NB_ALLOW_PUBLIC_BIND` | `false` | Required to bind an address other than loopback. | +| `--allowed-host` | `NB_ALLOWED_HOST` | | Name a request may arrive under, beyond loopback names and addresses. Repeatable. Comma separated in the environment. | +| `--no-browser` | `NB_NO_BROWSER` | `false` | Print the login URL instead of opening a browser. | +| `--check` | `NB_CHECK` | `false` | Validate the configuration, print the forwards and exit. | + +## Access control + +A peer created by `nblink` is a peer like any other. It appears in your peer +list and reaches only what your +[access control policies](/manage/access-control) allow. + +`nblink` only dials out. It refuses inbound connections and does not reach the +network of the machine it runs on, so the peer cannot be used as a way into +that machine. + +A loopback forward answers only requests whose `Host` names the loopback +interface or a name listed with `--allowed-host`, and refuses any request a +browser marks as belonging to another page, through either a cross-origin +`Origin` or a cross-site `Sec-Fetch-Site`. +A web page you visit can point its own hostname at `127.0.0.1` or embed the +address directly, and without those checks your browser could reach the +upstream through the listener under your identity. Ordinary callers send +neither header and are unaffected. + +A browser too old to send `Sec-Fetch-Site`, which browsers have sent since +2020, can still reach a loopback forward with an embedded no-cors GET. + +A forward on a public address applies the same checks and also accepts any IP +address in `Host`, since a page cannot rebind an address it does not control. + +A name listed with `--allowed-host` is trusted rather than merely permitted, +and the list applies to every forward in the process, loopback ones included. +`Host` is the one signal a rebound page cannot forge, and listing a name gives +that up for it: a page served on that name can resolve it to the listener and +send a matching `Host` and `Origin`. List only names you control, and prefer a +fully qualified one, since a single-label name can be answered by anything on +the local network. + + + Forwards bind `127.0.0.1` unless you pass `--allow-public-bind`. A forward on + a public address lets anything that can reach the port use your network, so + enable it only when the surrounding environment restricts access. + + +## Limits + +Userspace mode carries TCP, UDP and ping only. An `nblink` peer cannot: + +- act as a [routing peer](/manage/networks/how-routing-peers-work) or an exit node, +- take over system DNS for the machine, +- bind privileged ports below 1024. + +If you need any of those, install the +[NetBird client](/get-started/install) instead. diff --git a/src/pages/get-started/install/index.mdx b/src/pages/get-started/install/index.mdx index b189963a..ba07d363 100644 --- a/src/pages/get-started/install/index.mdx +++ b/src/pages/get-started/install/index.mdx @@ -18,6 +18,7 @@ The NetBird client allows a peer to join a pre-existing NetBird deployment. If a ## Additional Platforms * [Install on Synology](/get-started/install/synology) * [Install on TrueNAS](/get-started/install/truenas) +* [Install on Unraid](/get-started/install/unraid) * [Install on pfSense](/get-started/install/pfsense) * [Install on OPNSense](/get-started/install/opnsense) * [Install on OpenWrt](/get-started/install/openwrt) diff --git a/src/pages/get-started/install/unraid.mdx b/src/pages/get-started/install/unraid.mdx new file mode 100644 index 00000000..65117b9e --- /dev/null +++ b/src/pages/get-started/install/unraid.mdx @@ -0,0 +1,147 @@ +import {Note} from "@/components/mdx"; + +export const description = 'Install the NetBird plugin on Unraid, register your server with a setup key, and access it remotely over your NetBird network.' + +# Install NetBird on Unraid + +To reach your Unraid server from another location, install the [NetBird plugin](https://github.com/netbirdio/netbird-unraid) and join it to your NetBird network. The plugin runs directly on the Unraid host, making the server a peer (a device connected to NetBird) with its own NetBird IP address. + +## Prerequisites + +- Unraid **7.0.0 or later**, with access to its web interface. +- A [NetBird Cloud account](https://app.netbird.io/) or a [self-hosted NetBird deployment](/selfhosted/selfhosted-quickstart). +- A [setup key](/manage/peers/register-machines-using-setup-keys), which registers the server with your NetBird account. Create a one-off key under **Settings → Setup Keys** in the NetBird dashboard. Leave **Ephemeral Peers** disabled for this persistent server. +- [NetBird installed](/get-started/install) on the laptop you will use to access Unraid, connected to the same account. + +## Install the plugin + +Choose either the app catalog or the direct plugin URL. + +### From the app catalog + +1. Open **Apps** in the Unraid web interface and search for **NetBird**. +2. Select the **NetBird** plugin and click **Install**. +3. Wait for installation to finish, then open **Settings → NetBird** and select the plugin's **Settings** tab. + +### With the plugin URL + +1. In the Unraid web interface, open **Plugins → Install Plugin**. +2. Paste the following URL into the plugin URL field: + + ```text + https://raw.githubusercontent.com/netbirdio/netbird-unraid/main/plugin/netbird.plg + ``` + +3. Click **Install** and wait for installation to finish. +4. Open **Settings → NetBird**, then select the plugin's **Settings** tab. + +The plugin installs the NetBird client and its service. A fresh installation stays disabled until you configure and enable it in the next step. + +## Connect your server + +1. In the plugin's **Settings** tab, configure these fields: + + | Field | Value | + | --- | --- | + | **Enable NetBird** | Select **Yes**. | + | **Management URL** | Leave blank for NetBird Cloud. For self-hosted NetBird, enter your management server URL, such as `https://netbird.example.com`. | + | **Setup Key** | Paste the setup key you created in the NetBird dashboard. | + | **Hostname** | Optional. Enter a name you recognize, or leave blank to use your Unraid server's hostname. | + +2. Leave the other settings at their defaults and click **Apply**. The plugin starts NetBird and registers the server. This plugin uses setup keys for registration, so there is no browser sign-in step. +3. Select the **NetBird** tab to view the connection status. Confirm that **Daemon status**, **Management**, and **Signal** show **Connected**, and note the **NetBird IPv4** address. +4. Open **Peers** in the [NetBird dashboard](https://app.netbird.io/), or your self-hosted dashboard, and confirm that your Unraid server appears as connected. + +NetBird plugin in Unraid showing a running service, its assigned NetBird addresses, and connected management and signal services + +The NetBird tab shows the server's connection details. Your addresses and management URL will differ from this example. + + +Changing **Management URL** or **Hostname** after registration creates a new peer identity and IP address. You will need a valid setup key to register it again, and the old peer remains in the NetBird dashboard until you delete it. Choose these values before the first connection. + + +## Access the Unraid web interface + +1. In the NetBird dashboard, place your Unraid peer in a dedicated peer group and configure an [access control policy](/manage/access-control/manage-network-access) allowing your laptop's group to reach it on the TCP port used by the Unraid web interface. Limit the policy to the devices and ports that need access. +2. On your connected laptop, open the server's NetBird IP address in a browser, using the protocol and port configured for your Unraid web interface. Use the address without the subnet suffix: for example, `100.64.0.10` if the plugin displays `100.64.0.10/16`. +3. Sign in with your normal Unraid credentials. NetBird provides the network connection; Unraid still handles web interface authentication. + +The plugin makes the web interface listen on the NetBird interface. You do not need a network route to reach the Unraid host through its NetBird IP. + +To access other devices on your home network through this server, follow [Access Home Devices](/use-cases/remote-access/access-home-devices) and use the Unraid peer as the routing peer, the device that forwards traffic to those resources. + +## Plugin options and functionality + +### Settings + +Beyond the registration fields, the **Settings** tab provides these options. Click **Apply** after making changes. + +| Option | Default | What it does | +| --- | --- | --- | +| **Enable NetBird** | **No** on a fresh installation | Enables the service and automatic startup. Set it to **No** and apply to stop NetBird and keep it disabled. | +| **Manage DNS** | **Yes** | Lets NetBird configure the host's DNS resolver for peer names and nameservers defined in your NetBird account. Set it to **No** to use Unraid's existing DNS configuration. | +| **Enable NetBird SSH** | **No** | Enables NetBird's built-in SSH server, including root login on Unraid. You must also enable SSH for the peer and configure access policies in the NetBird dashboard. Follow the [SSH access guide](/manage/peers/ssh) and limit access to the users who need it. | +| **Enable Rosenpass** | **No** | Adds [post-quantum key exchange](/client/post-quantum-cryptography) to WireGuard connections. **Yes** requires the other peer to use Rosenpass. **Yes (permissive)** uses it where supported and allows standard WireGuard connections to other peers. | +| **Pre-shared Key** | Empty | Sets an optional additional WireGuard key for the selected profile. Leave blank unless your peers are configured to use one. | +| **Log Level** | **Info** | Controls how much detail NetBird writes to `/var/log/netbird.log`. Use **Debug** or **Trace** when investigating a problem, then return to **Info**. | + +DNS, SSH, Rosenpass, log level, and service enablement apply across profiles. Changing DNS, SSH, or Rosenpass settings reconnects NetBird to apply them. + + +A pre-shared key can be set or changed, but clearing the field does not remove an existing key. Removing it requires erasing the profile and setting it up again. + + +### Profiles + +A profile keeps a separate NetBird identity and registration settings. You can use profiles to switch between accounts, such as NetBird Cloud and a self-hosted deployment. Only one profile is active at a time. + +Profile controls are available while NetBird is running: + +1. Enter a unique profile name in the **Settings** tab and click **+ Add**. +2. Fill in that profile's **Management URL**, **Setup Key**, and optional **Hostname**. Click **Apply** to save, activate, and connect it. +3. To return to a configured profile, choose it from the **Profile** list and click **Switch**. + +Choosing a profile from the list only changes which settings you are viewing. **Switch** activates it; **Apply** saves the selected profile's settings and activates it. Switching disconnects the previous profile. **Delete** removes the selected profile and its stored credentials from this host. + +### Status and service controls + +The **NetBird** tab shows the connection state, assigned addresses, and peer connections. Its buttons let you manage the active connection: + +- **Connect** reconnects an already configured profile. +- **Disconnect** takes the profile offline while leaving the service running. +- **Restart** restarts the NetBird service. +- **Refresh** reloads the status page. + +To keep NetBird stopped across reboots, use **Enable NetBird: No** and **Apply** in **Settings**. Disconnecting alone does not disable automatic startup. + +The Unraid **Dashboard** also includes a NetBird tile for a quick status check. The plugin's **Info** tab lists the plugin and client versions, configuration paths, and log location. + +## Reboots and updates + +Once enabled, the plugin starts NetBird automatically when the Unraid array starts. It stores configuration and peer identity on the USB flash drive, so the server stays registered across reboots: + +- Configuration: `/boot/config/plugins/netbird/etc` +- State: `/boot/config/plugins/netbird/lib` + +Install updates from Unraid's **Plugins** page. The plugin manages both its web interface and the bundled NetBird client. + +## Troubleshooting + +### The server does not connect + +Confirm that **Enable NetBird** is set to **Yes** and that you clicked **Apply**. For a new registration, check that the setup key is valid and that **Management URL** points to the account that issued it. A used one-off key cannot register a new identity. + +Open the Unraid terminal to inspect the connection and recent logs: + +```bash +netbird status -d +tail -n 100 /var/log/netbird.log +``` + +After correcting the settings, click **Apply** and check the **NetBird** tab again. + +### Internet names stop resolving on Unraid + +The plugin enables **Manage DNS** by default. If Unraid cannot resolve internet names after connecting, check your [NetBird DNS configuration](/manage/dns) and configure a nameserver group that can resolve those names for the Unraid peer. + +If you want Unraid to keep using its existing DNS configuration, set **Manage DNS** to **No** in the plugin's **Settings** tab and click **Apply**. This disables NetBird's DNS management on the host, so NetBird peer names will need another DNS configuration to resolve. You can still connect using NetBird IP addresses. diff --git a/src/pages/get-started/install/windows.mdx b/src/pages/get-started/install/windows.mdx index 2fcca95c..cba403c4 100644 --- a/src/pages/get-started/install/windows.mdx +++ b/src/pages/get-started/install/windows.mdx @@ -10,6 +10,9 @@ The NetBird client allows a peer to join a pre-existing NetBird deployment. If a 2. Execute the installer and proceed with the installation steps 3. This will install the UI client in the `C:\Program Files\NetBird` and add the daemon service 4. After installing, you can follow the steps from [Running NetBird with SSO Login](#running-net-bird-with-sso-login). + + Open a new terminal before running `netbird` commands. The installer adds `C:\Program Files\NetBird` to the system `PATH`, but terminals that were already open keep the old `PATH`. + To uninstall the client and service, you can use Add/Remove programs diff --git a/src/pages/help/netbird-support.mdx b/src/pages/help/netbird-support.mdx index 1e5ab522..737c2fbf 100644 --- a/src/pages/help/netbird-support.mdx +++ b/src/pages/help/netbird-support.mdx @@ -1,7 +1,7 @@ import { SupportBanner } from "@/components/SupportBanner" export const description = - "NetBird Support for (paying) Cloud customers and users, and commercial-license self-hosted deployments: reach the team with a pre-filled report." + "NetBird Support, mainly for paying Cloud customers and commercial-license self-hosted deployments: reach the team with a pre-filled report." export const reportTemplate = `NetBird issue report ===================== @@ -39,7 +39,7 @@ export const supportMailto = `mailto:support@netbird.io?subject=${encodeURICompo diff --git a/src/pages/help/support-matrix/index.mdx b/src/pages/help/support-matrix/index.mdx index 307662e8..9346e554 100644 --- a/src/pages/help/support-matrix/index.mdx +++ b/src/pages/help/support-matrix/index.mdx @@ -42,8 +42,8 @@ Each sub-page is maintained alongside its component and updated when support win ## How to read these tables -- **Supported** — covered by bug fixes and security updates. -- **Best-effort** — known to work; issues triaged but not prioritized. -- **Unsupported** — outside the support window; upgrade recommended. +- **Supported**: covered by bug fixes and security updates. +- **Best-effort**: known to work; issues triaged but not prioritized. +- **Unsupported**: outside the support window; upgrade recommended. If you need help confirming compatibility for a version not listed, see [Report bugs and issues](/help/report-bug-issues). diff --git a/src/pages/help/support-matrix/netbird-client/index.mdx b/src/pages/help/support-matrix/netbird-client/index.mdx index dc87927b..5750a560 100644 --- a/src/pages/help/support-matrix/netbird-client/index.mdx +++ b/src/pages/help/support-matrix/netbird-client/index.mdx @@ -55,15 +55,16 @@ The NetBird client runs across desktop, mobile, and TV platforms. Pick an OS fam ## Go toolchain -The client is built with the Go versions listed below. Building from source against an unsupported Go release is not guaranteed to succeed. Each Go version sets a floor on OS support for that NetBird release — see the per-OS pages above for the resulting cutoffs. +The client is built with the Go versions listed below. Building from source against an unsupported Go release is not guaranteed to succeed. Each Go version sets a floor on OS support for that NetBird release. See the per-OS pages above for the resulting cutoffs. | NetBird client version | Go version | |---|---| -| v0.20.0 – v0.25.3 | 1.20 | -| v0.25.4 – v0.29.1 | 1.21 | -| v0.29.2 – v0.60.2 | 1.23 | -| v0.60.3 – v0.62.3 | 1.24 | -| v0.63.0 – current | 1.25 | +| v0.20.0 to v0.25.3 | 1.20 | +| v0.25.4 to v0.29.1 | 1.21 | +| v0.29.2 to v0.60.2 | 1.23 | +| v0.60.3 to v0.62.1 | 1.24 | +| v0.62.2 to v0.77.1 | 1.25 | +| v0.78.0 to current | 1.26 | ## Reporting compatibility issues diff --git a/src/pages/help/support-matrix/netbird-client/linux.mdx b/src/pages/help/support-matrix/netbird-client/linux.mdx index 5b0c8e42..af4e2320 100644 --- a/src/pages/help/support-matrix/netbird-client/linux.mdx +++ b/src/pages/help/support-matrix/netbird-client/linux.mdx @@ -10,7 +10,7 @@ NetBird's Linux support depends on the client mode you run: - **Userspace mode** is supported wherever the Go toolchain is supported. The kernel floor for each NetBird release follows Go's minimum OS requirements (see the table below). - **Kernel mode** depends on the iptables / nftables features available on the host distribution. -The NetBird team's end-to-end tests cover the distributions below — **Ubuntu 20.04** is the oldest tested release: +The NetBird team's end-to-end tests cover the distributions below. **Ubuntu 20.04** is the oldest tested release: - Ubuntu 20.04, 22.04, 24.04 - Debian 12 @@ -20,7 +20,7 @@ The NetBird team's end-to-end tests cover the distributions below — **Ubuntu 2 Other distributions are not actively tested; their compatibility follows the mode-specific rules above. - The kernel range below is derived from Go's [minimum OS requirements](https://go.dev/wiki/MinimumRequirements) per NetBird release's Go version, and applies to userspace mode. Kernel mode may have stricter requirements depending on the distribution — confirm with the team for production deployments. + The kernel range below is derived from Go's [minimum OS requirements](https://go.dev/wiki/MinimumRequirements) per NetBird release's Go version, and applies to userspace mode. Kernel mode may have stricter requirements depending on the distribution, so confirm with the team for production deployments. ## Linux kernel (userspace mode) @@ -28,9 +28,9 @@ Other distributions are not actively tested; their compatibility follows the mod | Kernel range | NetBird release range | |---|---| | 3.2 and newer | v0.60.3 onwards (Go 1.24+) | -| 2.6.32 – 3.1 | ≤ v0.60.2 (Go ≤1.23) | +| 2.6.32 to 3.1 | ≤ v0.60.2 (Go ≤1.23) | -The 2.6.32 floor primarily affected RHEL 6, CentOS 6, Ubuntu 14.04, and Debian 8 — all upstream EOL. +The 2.6.32 floor primarily affected RHEL 6, CentOS 6, Ubuntu 14.04, and Debian 8, all of which are upstream EOL. ## Reporting compatibility issues diff --git a/src/pages/help/support-matrix/netbird-client/macos.mdx b/src/pages/help/support-matrix/netbird-client/macos.mdx index 8c55e6ec..afdf9bac 100644 --- a/src/pages/help/support-matrix/netbird-client/macos.mdx +++ b/src/pages/help/support-matrix/netbird-client/macos.mdx @@ -8,7 +8,7 @@ export const description = The NetBird client supports the macOS versions listed below. The floor moves whenever NetBird upgrades its Go toolchain and Go drops an older macOS release. - The version cutoffs below are derived from the [Go toolchain's minimum OS requirements](https://go.dev/wiki/MinimumRequirements) for each NetBird release's Go version. NetBird may have stricter requirements than Go — confirm with the team before relying on them. + The version cutoffs below are derived from the [Go toolchain's minimum OS requirements](https://go.dev/wiki/MinimumRequirements) for each NetBird release's Go version. NetBird may have stricter requirements than Go, so confirm with the team before relying on them. ## macOS versions @@ -16,8 +16,9 @@ The NetBird client supports the macOS versions listed below. The floor moves whe | Version | Architectures | Version Supported | |---|---|---| | macOS 12 Monterey and newer | x86_64, arm64 | NetBird latest | -| macOS 11 Big Sur | x86_64, arm64 | ≤ v0.62.3 | +| macOS 11 Big Sur | x86_64, arm64 | ≤ v0.62.1 | | macOS 10.15 Catalina | x86_64 | ≤ v0.29.1 | +| macOS 10.13 High Sierra, 10.14 Mojave | x86_64 | ≤ v0.25.3 | ## Reporting compatibility issues diff --git a/src/pages/help/support-matrix/netbird-client/windows.mdx b/src/pages/help/support-matrix/netbird-client/windows.mdx index 9cda7a4d..02e614d7 100644 --- a/src/pages/help/support-matrix/netbird-client/windows.mdx +++ b/src/pages/help/support-matrix/netbird-client/windows.mdx @@ -5,18 +5,18 @@ export const description = # Windows -Windows 10 and 11 run on the latest NetBird client. Windows 7 and 8 are frozen at NetBird **v0.25.3** — the last release built with Go 1.20, the last Go toolchain to target those Windows versions. Older Windows releases may continue to work on pinned client versions but are not covered by bug fixes or security updates. +Windows 10 and 11 run on the latest NetBird client. Windows 7 and 8 are frozen at NetBird **v0.25.3**, the last release built with Go 1.20, the last Go toolchain to target those Windows versions. Older Windows releases may continue to work on pinned client versions but are not covered by bug fixes or security updates. - The version cutoffs below are derived from the [Go toolchain's minimum OS requirements](https://go.dev/wiki/MinimumRequirements) for each NetBird release's Go version. NetBird may have stricter requirements than Go — confirm with the team before relying on them. + The version cutoffs below are derived from the [Go toolchain's minimum OS requirements](https://go.dev/wiki/MinimumRequirements) for each NetBird release's Go version. NetBird may have stricter requirements than Go, so confirm with the team before relying on them. ## Windows versions | Version | Architectures | Version Supported | |---|---|---| -| Windows 11 | x86_64, arm64 | NetBird latest | -| Windows 10 / Server 2016 and newer | x86_64, arm64 | NetBird latest | +| Windows 11 | x86_64, arm64 (since v0.54.0) | NetBird latest | +| Windows 10 / Server 2016 and newer | x86_64, arm64 (since v0.54.0) | NetBird latest | | Windows 8 / Server 2012 / 2012 R2 | all | ≤ v0.25.3 | | Windows 7 / Server 2008 R2 | all | ≤ v0.25.3 | diff --git a/src/pages/help/troubleshooting-account-access.mdx b/src/pages/help/troubleshooting-account-access.mdx index 33cd5fa4..6da05b1c 100644 --- a/src/pages/help/troubleshooting-account-access.mdx +++ b/src/pages/help/troubleshooting-account-access.mdx @@ -1,24 +1,86 @@ import {Note} from "@/components/mdx"; -export const description = "Why your NetBird user might be pending approval, or added to an account you don't recognize, and how to resolve it safely." +export const description = "Why NetBird shows you a Waiting for Approval screen, adds you to an account you don't recognize, asks you to approve yourself, or gives a teammate an account of their own, and how to resolve it safely." # Pending approval and account invitations -Signed in and found your user **pending approval**, or landed in a NetBird account you don't recognize? This usually is not a bug. It almost always means you were added to an existing organization automatically, and an admin still has to approve you. +Signed in to NetBird and landed on a **Waiting for Approval** screen, or in a NetBird account you don't recognize? This usually is not a bug. It almost always means you were added to an existing organization automatically, and someone in that organization still has to approve you. + +The opposite also happens: a teammate signs in and gets a new, empty account of their own instead of joining yours. See [A teammate got their own account](#a-teammate-got-their-own-account). + +If the screen asks you to wait for approval from your own email address, see [The screen names your own address](#the-screen-names-your-own-address). + +## What the Waiting for Approval screen tells you + +After you sign in, the dashboard shows three steps: **Account Created**, **Waiting for Approval**, and **Join Account**. Your user exists, but the account requires new users to be approved before they can use it, so you are held at the second step until someone approves you. + +Below the steps, the screen names the owner of the account you are waiting to join, for example `li****g@example.com`. Most of the part before the `@` is hidden to protect the owner's privacy, but the domain is shown in full. That domain is the quickest way to tell which organization you are waiting on. + +If the screen only says "Ask the owner of the account to approve your access", with no address, NetBird could not look up the owner's email. Everything below still applies. + +The screen has two buttons: + +- **Refresh** checks again whether you have been approved. Use it once someone tells you they approved you. +- **Log Out** signs you out. Use it if you signed in with the wrong account, or you don't want to join this one. ## Why this happens NetBird can add people to an account without a manual, one-by-one invite: -- **Indirect (domain) invites.** If an account has verified your email domain, new sign-ups with that domain join it automatically. See [Indirect user invites](/manage/team/add-users-to-your-network#indirect-user-invites). +- **Domain matching.** On NetBird Cloud, new sign-ups join an existing account automatically when their email domain belongs to it: either the private domain the account signed up with, or another domain the account has verified as a [sign-in domain](/manage/team/sign-in-domains). See [Indirect user invites](/manage/team/add-users-to-your-network#indirect-user-invites). - **Identity provider sync.** If your organization connects NetBird to its identity provider, being added to a synced group in the IdP provisions your user in NetBird. See [Provision users and groups from your identity provider](/manage/team/idp-sync). -- **Approval is required.** When an account turns on [user approval](/manage/team/approve-users#require-user-approval), new users stay blocked until an admin approves them. That block is the "pending approval" state you see. +- **Approval is required.** When an account turns on [user approval](/manage/team/approve-users#require-user-approval), new users stay blocked until an admin approves them. That block is the Waiting for Approval screen you see. ## What to do -1. **Confirm it is your organization.** Check with your IT or security team whether they run NetBird and expect you on it. The account [Owner](/manage/team/user-roles#owner), of which there is exactly one per account, or an admin can confirm and approve you. -2. **Ask an admin to approve your user.** Approval happens in the dashboard from the account's user list. See [Approve or reject user](/manage/team/approve-users#approve-or-reject-user). -3. **If something looks off,** for example you don't recognize the account, your domain was claimed unexpectedly, or no one internally owns it, do not approve or accept anything. Treat it as suspicious and verify internally first. +1. **Check the domain on the screen.** It is normally your organization's email domain. Confirm with your IT or security team that they run NetBird and expect you on it. If the address looks like your own, see [The screen names your own address](#the-screen-names-your-own-address) instead. +2. **Ask the owner or an admin to approve you.** The screen names the account [Owner](/manage/team/user-roles#owner), of which there is exactly one per account, but any admin can approve you as well. They do it from the dashboard's user list. See [Approve or reject user](/manage/team/approve-users#approve-or-reject-user). +3. **Click Refresh once you are approved.** The screen moves you into the account. If it still shows Waiting for Approval, your browser may be holding on to a cached session: open the dashboard in a private browser window and sign in again. +4. **If something looks off,** for example the domain is not your organization's, you don't recognize the account, or no one internally owns it, do not ask anyone to approve you. Click **Log Out**, treat it as suspicious, and verify internally first. + + +On self-hosted NetBird, the Waiting for Approval screen needs dashboard v2.93.0 or later, and the owner's address on it needs management v0.80.0 or later. Older dashboards show a **User Approval Pending** error page with only a **Sign Out** button instead: the steps above still apply, but after you are approved, sign out and sign in again. + + +## The screen names your own address + +You signed in as `lisa.wong@example.com`, and the Waiting for Approval screen says you are waiting on `li****g@example.com`. The Owner you are waiting on is you. + +On NetBird Cloud, this happens when you created the account with one sign-in method, for example **Google** or **Microsoft**, and later signed in with a different one, for example an email address and password. NetBird tells users apart by how they sign in, not by their email address, so the second sign-in created a second user with the same email. Its domain, `example.com`, already belongs to the account you own, so [domain matching](#why-this-happens) added that second user to your account, where it waits for approval from the Owner: you, under your original sign-in method. + +The two users are not linked, and approving the second one does not merge them. It only gives you a second user in your account with the User role, not Owner. To get back in as Owner: + +1. **Click Log Out.** +2. **Sign in with the method you used to create the account.** If you don't remember which one, try each option on the sign-in page with the same email address. The right one opens your account as its Owner, with your peers and settings. +3. **Reject the extra user.** In **Team → Users**, the second user shows your email address and is waiting for approval. Click **Reject** to remove it. See [Approve or reject user](/manage/team/approve-users#approve-or-reject-user). + +From then on, sign in with your original method only. If you use the other method again, NetBird creates the second user again and you are back on this screen. + +If your account does not [require user approval](/manage/team/approve-users#require-user-approval), you don't see this screen. The second sign-in joins your account straight away with the User role, so you are in your own account but can't manage it. Log out and sign in with your original method as in steps 1 and 2. The extra user is not pending, so you can't reject it: in **Team → Users**, open the actions menu on its row and click **Delete** instead. + +If you signed up with a public email address, such as a Gmail account, domain matching never applies. The second sign-in lands in a new, empty account of its own instead. Sign out and sign in with your original method to get back to your account. If you don't need the empty account, sign in to it once more and [delete it](/manage/settings/delete-account). + +## A teammate got their own account + +A teammate signs in to NetBird Cloud and lands in a new, empty account, with none of your peers, users, or policies. NetBird did not match their email domain to your account, so it created a separate one for them. One of these is usually the reason: + +- **They use another of your business's domains.** Only the domain your account signed up with matches from the start. A country domain such as `company.co.uk`, a subsidiary, or a second brand matches only after an Owner or Admin adds and verifies it as a [sign-in domain](/manage/team/sign-in-domains). +- **They signed in with a personal address**, such as a Gmail account. Public email domains never match an account. Ask them to sign in with their work address instead. You can't [invite them directly](/manage/team/add-users-to-your-network#direct-user-invites) now that they have their own account, so if they need to keep the personal address, contact [NetBird Support](/help/netbird-support). +- **Your domain was not classified as private.** NetBird only groups sign-ups by domain when it classifies the domain as private. If it is unsure, it plays safe and does not group them. [NetBird Support](/help/netbird-support) can correct the classification. See [Indirect user invites](/manage/team/add-users-to-your-network#indirect-user-invites). + +To fix it for the next person, an Owner or Admin adds and verifies the domain under **Settings → Authentication → Sign-in Domains**, or invites the user directly. Do this before onboarding a team on another domain, not after. + +The **Sign-in Domains** section only appears once your account's own domain is classified as private. If you don't see it, invite the user directly or contact [NetBird Support](/help/netbird-support). + +To confirm the cause, ask your teammate which email address they signed in with, and check its domain against the Sign-in Domains list. + +Verifying a domain does not move a teammate who already has an account of their own: it only changes where new sign-ins land, and inviting or syncing them fails until they leave that account. For a teammate already in a separate account, see [User already belongs to another NetBird account](/help/troubleshooting-user-in-another-account). + +If your account requires user approval, a teammate who joins through domain matching is held for approval. That is the [Waiting for Approval](#what-the-waiting-for-approval-screen-tells-you) screen above, and an admin approves them as described in [What to do](#what-to-do). + + +On self-hosted NetBird, domain matching and sign-in domains do not apply: by default, every user joins the same account. + Still unsure who owns the account, or think you were added to the wrong one? Reach out through [NetBird Support](/help/netbird-support) and the team can help confirm the account and its owner. diff --git a/src/pages/help/troubleshooting-user-in-another-account.mdx b/src/pages/help/troubleshooting-user-in-another-account.mdx new file mode 100644 index 00000000..c7e71c50 --- /dev/null +++ b/src/pages/help/troubleshooting-user-in-another-account.mdx @@ -0,0 +1,111 @@ +import {Warning} from "@/components/mdx"; + +export const description = "Why NetBird refuses to invite or sync a user who already has a NetBird account elsewhere, how to move them into your account, and how Sign-in Domains stops it happening again." + +# User Already Belongs to Another NetBird Account + +You try to add someone to your NetBird account and it doesn't work: the invite fails with a `409` error, or Identity Provider Sync never adds them, even though they are in the right group in your identity provider (IdP). + +In almost every case, the person already signed in to NetBird before you added them, and that sign-in put them in a **different NetBird account**. A NetBird user belongs to exactly one account, so NetBird won't copy or move them into yours. You have to free the user from the other account first. + +This page is for NetBird Cloud, where many organizations share one service. On self-hosted NetBird, see [Self-hosted](#self-hosted) at the end. + +## Symptoms + +You see one of these: + +- **Invite fails with 409.** Inviting the user from **Team → Users → Invite User** shows `Code 409: Can't invite a user with an existing NetBird account`. +- **IdP sync skips the user.** The user is assigned to NetBird in your IdP, other users sync fine, but this one never appears under **Team → Users**. Your IdP's provisioning log may show the user as provisioned successfully, because NetBird accepts the record first and adds users to your account in a later step. That later step is where it fails, and the integration's sync log records: + + ```text + failed to sync new users: failed to process update for user : user account ID mismatch + ``` + + The dashboard shows only when the integration last synced, not the log itself. To read the log, call the sync logs endpoint for your integration in the API: [Entra ID (API)](/ipa/resources/idp-azure-integrations#get-azure-integration-sync-logs), [Google Workspace](/ipa/resources/idp-google-integrations#get-google-integration-sync-logs), [Okta](/ipa/resources/idp-okta-scim-integrations#get-okta-scim-integration-sync-logs), or [the SCIM-based integrations](/ipa/resources/idp-scim-integrations#get-scim-integration-sync-logs). + +Both errors usually mean the same thing: NetBird already knows this person, and they belong to another account. Rule out the simpler case below first. + +## Check your own account first + +An invite also fails with `409` when the person is already a user in **your** account. For example, they signed in on their own and joined through their email domain, or they are waiting for approval. Look for their email address under **Team → Users**, including users waiting for approval. If they are there, don't invite them again: [approve them](/manage/team/approve-users#approve-or-reject-user), or change their role if they need dashboard access. + +## Why the user ended up in another account + +When someone signs in to NetBird for the first time, NetBird decides which account they join from their email domain. They join your account only if the domain is one your account owns. Otherwise NetBird creates a new account with them as its Owner. Ordered by how often we see them: + +1. **Their email domain isn't your signup domain.** You signed up as `admin@company.com`, and the user signed in as `jane@company.co.uk`, a subsidiary, or a second brand. NetBird doesn't know `company.co.uk` belongs to you, so Jane got an account of her own. This is the case [Sign-in Domains](/manage/team/sign-in-domains) solves. +2. **They signed in before your rollout.** They tried NetBird early, or opened the dashboard link before you set up IdP sync or invited them. If their domain was one you hadn't added yet, they landed in an account of their own, as in case 1. +3. **They are a member of another organization's account.** For example, a partner or customer account, a former employer's account, or a test account someone created. +4. **Their domain isn't recognized as a business domain.** NetBird treats public email providers like Gmail as personal, and every user on them gets their own account. Rarely, a company domain is classified this way too. [NetBird Support](/help/netbird-support) can correct the classification. + +## Fix it + +### 1. Find out which account the user is in + +Ask the user to sign in at [app.netbird.io](https://app.netbird.io) with the same email address you are adding, and look at what they see: + +- **A network with only their own devices, and the Settings → Danger zone tab is available, not greyed out.** Only an Owner can open it, so they are the Owner of an account of their own (cases 1, 2, or 4). Go to [step 2a](#2a-the-user-owns-an-account-of-their-own). +- **Someone else's network, or a Waiting for Approval screen naming another organization.** They are a member of another account (case 3). Go to [step 2b](#2b-the-user-is-a-member-of-another-organizations-account). +- **A Waiting for Approval screen naming your own domain.** They are already in your account. See [Check your own account first](#check-your-own-account-first). + +If neither fits, or the user doesn't know where the account came from, contact [NetBird Support](/help/netbird-support). The team verifies the user's identity and points them to whoever administers that account. + +### 2a. The user owns an account of their own + + + Deleting an account permanently deletes everything in it: its peers, groups, policies, setup keys, and any other users. Before the user deletes it, confirm it holds nothing they still need, and that no one else relies on it. + + +1. **If their domain differs from yours, add it as a sign-in domain first.** In your account, go to **Settings → Authentication → Sign-in Domains** and add and verify their domain, for example `company.co.uk`. See [Sign-in Domains](/manage/team/sign-in-domains). Skip this step if they share your domain. +2. **Have the user delete their account.** In their account, they go to **Settings → Danger zone** and click **Delete Account**. See [Delete your account](/manage/settings/delete-account). +3. **Bring them into your account.** If you verified their domain in step 1, or they share your domain, their next sign-in joins your account, and IdP sync attaches their groups on its next run. If you don't use either, invite them again or run the sync. If your account requires [user approval](/manage/team/approve-users#approve-or-reject-user), approve them under **Team → Users**. + +Doing step 1 before step 2 matters. Without a matching domain, a user who signs in again before your next sync gets a fresh account of their own, and you are back where you started. + +If verification in step 1 fails, the user's own account may already hold that domain. Only one account can verify a domain, so do step 2 first, then verify the domain right away. Ask the user not to sign in again until the domain shows as verified. + +### 2b. The user is a member of another organization's account + +Only that account's admins can release the user. Ask them to delete the user from **Team → Users** in their account. Removing a user also removes the peers registered under them in that account. + +Once the user is removed, invite them again, or let IdP sync pick them up on its next run. + +### 3. Run the sync or the invite again + +- **Direct invite.** Invite the user again from **Team → Users**. +- **Entra ID (API) and Google Workspace.** Open the integration under **Integrations → Identity Provider Sync** and force a synchronization, or wait for the next scheduled one. +- **SCIM integrations** (Entra ID (SCIM), Okta, JumpCloud, Keycloak, IIJ ID, generic SCIM). Your IdP pushes changes on its own schedule. Trigger provisioning for the user in your IdP, for example **Provision on demand** in Entra ID, or wait for the next cycle. + +## Confirm it worked + +The user appears under **Team → Users** in your account, with the groups from your IdP, and the next sync log no longer reports `user account ID mismatch` for them. When the user signs in at [app.netbird.io](https://app.netbird.io), they see your network, or a Waiting for Approval screen naming your own domain if your account requires user approval. + +If the invite still fails with `409`, or the user can't sign in after their old account is gone, contact [NetBird Support](/help/netbird-support) with their email address. + +## Prevent it + +Most of these conflicts come from case 1: an organization uses more than one email domain and only the signup domain is known to NetBird. + +- **Add every domain your organization uses as a [sign-in domain](/manage/team/sign-in-domains), before you roll NetBird out.** Sign-in domains decide where new users land, so a verified domain sends every first sign-in on it to your account. They don't move users who already have an account elsewhere, which is why adding them first matters. +- **Set up IdP sync and sign-in domains before you announce NetBird to users.** Anyone who signs in early, on a domain you haven't added yet, gets an account of their own. + +## Self-hosted + +A self-hosted NetBird deployment runs one account by default, and every user joins it. A user can't be stuck in another account, so `user account ID mismatch` doesn't apply. + +With the embedded identity provider, a `409` on invite means the email address already belongs to a user in your own account. Look for them under **Team → Users** instead of inviting them again. + +## Recap + +- One NetBird user belongs to exactly one account. A `409` on invite, or `user account ID mismatch` in the sync log, usually means the user is already in another one. Check **Team → Users** in your own account first. +- Usually they signed in early, with an email domain your account didn't know yet. +- Free the user first: they delete their own account, or the other account's admins remove them. Then invite or sync again. +- Add and verify every domain you use as a sign-in domain before rollout, so first sign-ins land in your account. + +## Related docs + +- [Allow users from other domains to join your account](/manage/team/sign-in-domains) +- [Add users to your network](/manage/team/add-users-to-your-network) +- [Provision users and groups from your identity provider](/manage/team/idp-sync) +- [Pending approval and account invitations](/help/troubleshooting-account-access) +- [Delete your account](/manage/settings/delete-account) diff --git a/src/pages/help/troubleshooting.mdx b/src/pages/help/troubleshooting.mdx index 0f4fa5fc..1181e95b 100644 --- a/src/pages/help/troubleshooting.mdx +++ b/src/pages/help/troubleshooting.mdx @@ -2,43 +2,91 @@ import { TroubleshootingTiles } from "@/components/TroubleshootingTiles" import { StillStuck } from "@/components/StillStuck" export const description = - "Start here to troubleshoot NetBird. Find your issue by area and jump straight to the relevant guide." + "Start here to troubleshoot NetBird. See the common issues, or browse by area." # Troubleshooting -Something not working as expected? Pick the area closest to your problem and jump to the -guide and section that covers it. Each section links to troubleshooting docs that live -next to the feature they cover, so nothing here is a copy. +Something not working as expected? Check the common issues below, or browse by area. + +## Common issues + +The problems support sees most often. + +
+ +| Symptom | Start here | +|---|---| +| The dashboard shows **Waiting for Approval** after you sign in | [Pending approval and account invitations](/help/troubleshooting-account-access) | +| Inviting a user fails with `409`, or IdP sync never adds them | [User already belongs to another NetBird account](/help/troubleshooting-user-in-another-account) | +| The client keeps asking you to log in again, or a peer shows `Login required` | [Peer session expiration](/manage/settings/enforce-periodic-user-authentication) | +| A peer shows `Relayed` instead of `P2P` | [Troubleshooting relayed connections](/help/troubleshooting-relayed-connections) | +| The tunnel works but is slow, or takes long to connect | [Troubleshooting performance](/help/troubleshooting-performance) | +| A service behind a routing peer is unreachable, or a new resource never shows up | [Troubleshooting resource connectivity](/help/troubleshooting-resource-connectivity) | +| A network or host firewall blocks NetBird, or traffic stops at a security product such as a SASE or SSL-inspection service | [Ports & firewalls](/about-netbird/ports-and-firewalls)
On the peer itself: [Symptoms of host-based firewall issues](/about-netbird/ports-and-firewalls#symptoms-of-host-based-firewall-issues) | +| Names do not resolve, or DNS stops working after NetBird disconnects | [DNS troubleshooting](/manage/dns/troubleshooting) | +| A NetBird Reverse Proxy service returns `502`, times out, or its certificate is never issued | [Reverse proxy troubleshooting](/manage/reverse-proxy/troubleshooting)
Certificate: [Certificate is not issued after verification](/manage/reverse-proxy/custom-domains#certificate-is-not-issued-after-verification) | + +
diff --git a/src/pages/ipa/resources/accounts.mdx b/src/pages/ipa/resources/accounts.mdx index 8a078d43..00de843b 100644 --- a/src/pages/ipa/resources/accounts.mdx +++ b/src/pages/ipa/resources/accounts.mdx @@ -201,6 +201,7 @@ echo $response; "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -260,6 +261,7 @@ echo $response; "auto_update_version": "string", "auto_update_always": "boolean", "metrics_push_enabled": "boolean", + "peer_hostname_collision_rejected": "boolean", "agent_network_only": "boolean", "dashboard_features": { "agent_network": "boolean" @@ -609,6 +611,11 @@ echo $response; Enables or disables client metrics push for all peers in the account + + + + When enabled, registering or renaming a peer to a hostname already used by another peer in the account fails instead of receiving an IP-based suffix. Applies to ephemeral peers as well. + @@ -730,6 +737,7 @@ curl -X PUT https://api.netbird.io/api/accounts/{accountId} \ "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -788,6 +796,7 @@ let data = JSON.stringify({ "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -868,6 +877,7 @@ payload = json.dumps({ "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -948,6 +958,7 @@ func main() { "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -1047,6 +1058,7 @@ request.body = JSON.dump({ "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -1109,6 +1121,7 @@ RequestBody body = RequestBody.create(mediaType, '{ "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -1187,6 +1200,7 @@ curl_setopt_array($curl, array( "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -1260,6 +1274,7 @@ echo $response; "auto_update_version": "0.51.2", "auto_update_always": false, "metrics_push_enabled": false, + "peer_hostname_collision_rejected": false, "agent_network_only": false, "dashboard_features": { "agent_network": true @@ -1317,6 +1332,7 @@ echo $response; "auto_update_version": "string", "auto_update_always": "boolean", "metrics_push_enabled": "boolean", + "peer_hostname_collision_rejected": "boolean", "agent_network_only": "boolean", "dashboard_features": { "agent_network": "boolean" diff --git a/src/pages/ipa/resources/agent-network.mdx b/src/pages/ipa/resources/agent-network.mdx index e4a054c1..39630361 100644 --- a/src/pages/ipa/resources/agent-network.mdx +++ b/src/pages/ipa/resources/agent-network.mdx @@ -6,7 +6,7 @@ export const title = 'Agent Network'
- Starts provisioning of a NetBird-managed Agent Network gateway for the account, allocating its endpoint under the managed zone on the first call. Idempotent — answers 202 when this call started (or, after a failure, restarted) provisioning and 200 when a deployment already exists, reporting current state either way. Returns 409 when the account already has an Agent Network endpoint that managed provisioning does not own, and 503 when endpoint allocation is temporarily exhausted. + Starts provisioning of a NetBird-managed Agent Network gateway for the account, allocating its endpoint under the managed zone on the first call. Idempotent — answers 202 when this call started (or, after a failure, restarted) provisioning and 200 when a deployment already exists, reporting current state either way. A disabled deployment answers 200 with state `disabled` and stays disabled. Returns 409 when the account already has an Agent Network endpoint that managed provisioning does not own, and 503 when endpoint allocation is temporarily exhausted. @@ -194,7 +194,7 @@ echo $response; - Reports the account's managed gateway deployment and its derived state. Returns 404 when the account has no managed deployment. + Reports the account's managed gateway deployment and its derived state, including `disabled` for a deployment that is turned off. Returns 404 when the account has no managed deployment. diff --git a/src/pages/ipa/resources/ingress-ports.mdx b/src/pages/ipa/resources/ingress-ports.mdx deleted file mode 100644 index e424d4a1..00000000 --- a/src/pages/ipa/resources/ingress-ports.mdx +++ /dev/null @@ -1,2480 +0,0 @@ -export const title = 'Ingress Ports' - - - -## List all Port Allocations {{ tag: 'GET' , label: '/api/peers/{peerId}/ingress/ports' }} - - - - Returns a list of all ingress port allocations for a peer - - ### Path Parameters - - - - The unique identifier of a peer - - - - ### Query Parameters - - - - Filters ingress port allocations by name - - - - - - -```bash {{ title: 'cURL' }} -curl -X GET https://api.netbird.io/api/peers/{peerId}/ingress/ports \ --H 'Accept: application/json' \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'get', - maxBodyLength: Infinity, - url: '/api/peers/{peerId}/ingress/ports', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/peers/{peerId}/ingress/ports" - -headers = { - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("GET", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/peers/{peerId}/ingress/ports" - method := "GET" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/peers/{peerId}/ingress/ports") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Get.new(url) -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/peers/{peerId}/ingress/ports") - .method("GET") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/peers/{peerId}/ingress/ports', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -[ - { - "id": "ch8i4ug6lnn4g9hqv7m0", - "name": "Ingress Peer Allocation 1", - "ingress_peer_id": "x7p3kqf2rdd8j5zxw4n9", - "region": "germany", - "enabled": true, - "ingress_ip": "192.34.0.123", - "port_range_mappings": [ - { - "translated_start": 80, - "translated_end": 320, - "ingress_start": 1080, - "ingress_end": 1320, - "protocol": "tcp" - } - ] - } -] -``` -```json {{ title: 'Schema' }} -[ - { - "id": "string", - "name": "string", - "ingress_peer_id": "string", - "region": "string", - "enabled": "boolean", - "ingress_ip": "string", - "port_range_mappings": [ - { - "translated_start": "integer", - "translated_end": "integer", - "ingress_start": "integer", - "ingress_end": "integer", - "protocol": "string" - } - ] - } -] -``` - - - - - - ---- - - -## Create a Port Allocation {{ tag: 'POST' , label: '/api/peers/{peerId}/ingress/ports' }} - - - - Creates a new ingress port allocation for a peer - - ### Path Parameters - - - - The unique identifier of a peer - - - - ### Request-Body Parameters - - - - Name of the ingress port allocation - - - - - Indicates if an ingress port allocation is enabled - - - - -
- List of port ranges that are forwarded by the ingress peer - - - - - The starting port of the range of forwarded ports - - - - - The ending port of the range of forwarded ports - - - - - The protocol accepted by the port range - - - - - -
- -
- - -
- More Information - - - - - The number of ports to be forwarded - - - - - The protocol accepted by the port - - - - - -
- -
-
- - - - - - -```bash {{ title: 'cURL' }} -curl -X POST https://api.netbird.io/api/peers/{peerId}/ingress/ports \ --H 'Accept: application/json' \ --H 'Content-Type: application/json' \ --H 'Authorization: Token ' \ ---data-raw '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}' -``` - -```js -const axios = require('axios'); -let data = JSON.stringify({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}); -let config = { - method: 'post', - maxBodyLength: Infinity, - url: '/api/peers/{peerId}/ingress/ports', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Token ' - }, - data : data -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/peers/{peerId}/ingress/ports" -payload = json.dumps({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}) -headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("POST", url, headers=headers, data=payload) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/peers/{peerId}/ingress/ports" - method := "POST" - - payload := strings.NewReader(`{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}`) - client := &http.Client { - } - req, err := http.NewRequest(method, url, payload) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/peers/{peerId}/ingress/ports") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Post.new(url) -request["Content-Type"] = "application/json" -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -request.body = JSON.dump({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}) -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); -MediaType mediaType = MediaType.parse("application/json"); -RequestBody body = RequestBody.create(mediaType, '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}'); -Request request = new Request.Builder() - .url("https://api.netbird.io/api/peers/{peerId}/ingress/ports") - .method("POST", body) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/peers/{peerId}/ingress/ports', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POSTFIELDS => '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "name": "Ingress Peer Allocation 1", - "ingress_peer_id": "x7p3kqf2rdd8j5zxw4n9", - "region": "germany", - "enabled": true, - "ingress_ip": "192.34.0.123", - "port_range_mappings": [ - { - "translated_start": 80, - "translated_end": 320, - "ingress_start": 1080, - "ingress_end": 1320, - "protocol": "tcp" - } - ] -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "name": "string", - "ingress_peer_id": "string", - "region": "string", - "enabled": "boolean", - "ingress_ip": "string", - "port_range_mappings": [ - { - "translated_start": "integer", - "translated_end": "integer", - "ingress_start": "integer", - "ingress_end": "integer", - "protocol": "string" - } - ] -} -``` - - - - - - ---- - - -## Retrieve a Port Allocation {{ tag: 'GET' , label: '/api/peers/{peerId}/ingress/ports/{allocationId}' }} - - - - Get information about an ingress port allocation - - ### Path Parameters - - - - The unique identifier of a peer - - - - The unique identifier of an ingress port allocation - - - - - - -```bash {{ title: 'cURL' }} -curl -X GET https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId} \ --H 'Accept: application/json' \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'get', - maxBodyLength: Infinity, - url: '/api/peers/{peerId}/ingress/ports/{allocationId}', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" - -headers = { - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("GET", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" - method := "GET" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Get.new(url) -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - .method("GET") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "name": "Ingress Peer Allocation 1", - "ingress_peer_id": "x7p3kqf2rdd8j5zxw4n9", - "region": "germany", - "enabled": true, - "ingress_ip": "192.34.0.123", - "port_range_mappings": [ - { - "translated_start": 80, - "translated_end": 320, - "ingress_start": 1080, - "ingress_end": 1320, - "protocol": "tcp" - } - ] -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "name": "string", - "ingress_peer_id": "string", - "region": "string", - "enabled": "boolean", - "ingress_ip": "string", - "port_range_mappings": [ - { - "translated_start": "integer", - "translated_end": "integer", - "ingress_start": "integer", - "ingress_end": "integer", - "protocol": "string" - } - ] -} -``` - - - - - - ---- - - -## Update a Port Allocation {{ tag: 'PUT' , label: '/api/peers/{peerId}/ingress/ports/{allocationId}' }} - - - - Update information about an ingress port allocation - - ### Path Parameters - - - - The unique identifier of a peer - - - - The unique identifier of an ingress port allocation - - - - ### Request-Body Parameters - - - - Name of the ingress port allocation - - - - - Indicates if an ingress port allocation is enabled - - - - -
- List of port ranges that are forwarded by the ingress peer - - - - - The starting port of the range of forwarded ports - - - - - The ending port of the range of forwarded ports - - - - - The protocol accepted by the port range - - - - - -
- -
- - -
- More Information - - - - - The number of ports to be forwarded - - - - - The protocol accepted by the port - - - - - -
- -
-
- - - - - - -```bash {{ title: 'cURL' }} -curl -X PUT https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId} \ --H 'Accept: application/json' \ --H 'Content-Type: application/json' \ --H 'Authorization: Token ' \ ---data-raw '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}' -``` - -```js -const axios = require('axios'); -let data = JSON.stringify({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}); -let config = { - method: 'put', - maxBodyLength: Infinity, - url: '/api/peers/{peerId}/ingress/ports/{allocationId}', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Token ' - }, - data : data -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" -payload = json.dumps({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}) -headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("PUT", url, headers=headers, data=payload) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" - method := "PUT" - - payload := strings.NewReader(`{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}`) - client := &http.Client { - } - req, err := http.NewRequest(method, url, payload) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Put.new(url) -request["Content-Type"] = "application/json" -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -request.body = JSON.dump({ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}) -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); -MediaType mediaType = MediaType.parse("application/json"); -RequestBody body = RequestBody.create(mediaType, '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}'); -Request request = new Request.Builder() - .url("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - .method("PUT", body) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'PUT', - CURLOPT_POSTFIELDS => '{ - "name": "Ingress Port Allocation 1", - "enabled": true, - "port_ranges": [ - { - "start": 80, - "end": 320, - "protocol": "tcp" - } - ], - "direct_port": { - "count": 5, - "protocol": "udp" - } -}', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "name": "Ingress Peer Allocation 1", - "ingress_peer_id": "x7p3kqf2rdd8j5zxw4n9", - "region": "germany", - "enabled": true, - "ingress_ip": "192.34.0.123", - "port_range_mappings": [ - { - "translated_start": 80, - "translated_end": 320, - "ingress_start": 1080, - "ingress_end": 1320, - "protocol": "tcp" - } - ] -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "name": "string", - "ingress_peer_id": "string", - "region": "string", - "enabled": "boolean", - "ingress_ip": "string", - "port_range_mappings": [ - { - "translated_start": "integer", - "translated_end": "integer", - "ingress_start": "integer", - "ingress_end": "integer", - "protocol": "string" - } - ] -} -``` - - - - - - ---- - - -## Delete a Port Allocation {{ tag: 'DELETE' , label: '/api/peers/{peerId}/ingress/ports/{allocationId}' }} - - - - Delete an ingress port allocation - - ### Path Parameters - - - - The unique identifier of a peer - - - - The unique identifier of an ingress port allocation - - - - - - -```bash {{ title: 'cURL' }} -curl -X DELETE https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId} \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'delete', - maxBodyLength: Infinity, - url: '/api/peers/{peerId}/ingress/ports/{allocationId}', - headers: { - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" - -headers = { - 'Authorization': 'Token ' -} - -response = requests.request("DELETE", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}" - method := "DELETE" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Delete.new(url) -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}") - .method("DELETE") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/peers/{peerId}/ingress/ports/{allocationId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'DELETE', - CURLOPT_HTTPHEADER => array( - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - - - - ---- - - -## List all Ingress Peers {{ tag: 'GET' , label: '/api/ingress/peers' }} - - - - Returns a list of all ingress peers - - - - -```bash {{ title: 'cURL' }} -curl -X GET https://api.netbird.io/api/ingress/peers \ --H 'Accept: application/json' \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'get', - maxBodyLength: Infinity, - url: '/api/ingress/peers', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/ingress/peers" - -headers = { - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("GET", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/ingress/peers" - method := "GET" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/ingress/peers") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Get.new(url) -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/ingress/peers") - .method("GET") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/ingress/peers', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -[ - { - "id": "ch8i4ug6lnn4g9hqv7m0", - "peer_id": "x7p3kqf2rdd8j5zxw4n9", - "ingress_ip": "192.34.0.123", - "available_ports": { - "tcp": 45765, - "udp": 50000 - }, - "enabled": true, - "connected": true, - "fallback": true, - "region": "germany" - } -] -``` -```json {{ title: 'Schema' }} -[ - { - "id": "string", - "peer_id": "string", - "ingress_ip": "string", - "available_ports": { - "tcp": "integer", - "udp": "integer" - }, - "enabled": "boolean", - "connected": "boolean", - "fallback": "boolean", - "region": "string" - } -] -``` - - - - - - ---- - - -## Create a Ingress Peer {{ tag: 'POST' , label: '/api/ingress/peers' }} - - - - Creates a new ingress peer - - ### Request-Body Parameters - - - - ID of the peer that is used as an ingress peer - - - - - Defines if an ingress peer is enabled - - - - - Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer - - - - - - - - - -```bash {{ title: 'cURL' }} -curl -X POST https://api.netbird.io/api/ingress/peers \ --H 'Accept: application/json' \ --H 'Content-Type: application/json' \ --H 'Authorization: Token ' \ ---data-raw '{ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}' -``` - -```js -const axios = require('axios'); -let data = JSON.stringify({ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}); -let config = { - method: 'post', - maxBodyLength: Infinity, - url: '/api/ingress/peers', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Token ' - }, - data : data -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/ingress/peers" -payload = json.dumps({ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}) -headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("POST", url, headers=headers, data=payload) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/ingress/peers" - method := "POST" - - payload := strings.NewReader(`{ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}`) - client := &http.Client { - } - req, err := http.NewRequest(method, url, payload) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/ingress/peers") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Post.new(url) -request["Content-Type"] = "application/json" -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -request.body = JSON.dump({ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}) -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); -MediaType mediaType = MediaType.parse("application/json"); -RequestBody body = RequestBody.create(mediaType, '{ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}'); -Request request = new Request.Builder() - .url("https://api.netbird.io/api/ingress/peers") - .method("POST", body) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/ingress/peers', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'POST', - CURLOPT_POSTFIELDS => '{ - "peer_id": "ch8i4ug6lnn4g9hqv7m0", - "enabled": true, - "fallback": true -}', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "peer_id": "x7p3kqf2rdd8j5zxw4n9", - "ingress_ip": "192.34.0.123", - "available_ports": { - "tcp": 45765, - "udp": 50000 - }, - "enabled": true, - "connected": true, - "fallback": true, - "region": "germany" -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "peer_id": "string", - "ingress_ip": "string", - "available_ports": { - "tcp": "integer", - "udp": "integer" - }, - "enabled": "boolean", - "connected": "boolean", - "fallback": "boolean", - "region": "string" -} -``` - - - - - - ---- - - -## Retrieve a Ingress Peer {{ tag: 'GET' , label: '/api/ingress/peers/{ingressPeerId}' }} - - - - Get information about an ingress peer - - ### Path Parameters - - - - The unique identifier of an ingress peer - - - - - - -```bash {{ title: 'cURL' }} -curl -X GET https://api.netbird.io/api/ingress/peers/{ingressPeerId} \ --H 'Accept: application/json' \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'get', - maxBodyLength: Infinity, - url: '/api/ingress/peers/{ingressPeerId}', - headers: { - 'Accept': 'application/json', - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" - -headers = { - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("GET", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" - method := "GET" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Get.new(url) -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - .method("GET") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/ingress/peers/{ingressPeerId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'GET', - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "peer_id": "x7p3kqf2rdd8j5zxw4n9", - "ingress_ip": "192.34.0.123", - "available_ports": { - "tcp": 45765, - "udp": 50000 - }, - "enabled": true, - "connected": true, - "fallback": true, - "region": "germany" -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "peer_id": "string", - "ingress_ip": "string", - "available_ports": { - "tcp": "integer", - "udp": "integer" - }, - "enabled": "boolean", - "connected": "boolean", - "fallback": "boolean", - "region": "string" -} -``` - - - - - - ---- - - -## Update a Ingress Peer {{ tag: 'PUT' , label: '/api/ingress/peers/{ingressPeerId}' }} - - - - Update information about an ingress peer - - ### Path Parameters - - - - The unique identifier of an ingress peer - - - - ### Request-Body Parameters - - - - Defines if an ingress peer is enabled - - - - - Defines if an ingress peer can be used as a fallback if no ingress peer can be found in the region of the forwarded peer - - - - - - - - - -```bash {{ title: 'cURL' }} -curl -X PUT https://api.netbird.io/api/ingress/peers/{ingressPeerId} \ --H 'Accept: application/json' \ --H 'Content-Type: application/json' \ --H 'Authorization: Token ' \ ---data-raw '{ - "enabled": true, - "fallback": true -}' -``` - -```js -const axios = require('axios'); -let data = JSON.stringify({ - "enabled": true, - "fallback": true -}); -let config = { - method: 'put', - maxBodyLength: Infinity, - url: '/api/ingress/peers/{ingressPeerId}', - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/json', - 'Authorization': 'Token ' - }, - data : data -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" -payload = json.dumps({ - "enabled": true, - "fallback": true -}) -headers = { - 'Content-Type': 'application/json', - 'Accept': 'application/json', - 'Authorization': 'Token ' -} - -response = requests.request("PUT", url, headers=headers, data=payload) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" - method := "PUT" - - payload := strings.NewReader(`{ - "enabled": true, - "fallback": true -}`) - client := &http.Client { - } - req, err := http.NewRequest(method, url, payload) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Content-Type", "application/json") - req.Header.Add("Accept", "application/json") - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Put.new(url) -request["Content-Type"] = "application/json" -request["Accept"] = "application/json" -request["Authorization"] = "Token " - -request.body = JSON.dump({ - "enabled": true, - "fallback": true -}) -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); -MediaType mediaType = MediaType.parse("application/json"); -RequestBody body = RequestBody.create(mediaType, '{ - "enabled": true, - "fallback": true -}'); -Request request = new Request.Builder() - .url("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - .method("PUT", body) - .addHeader("Content-Type", "application/json") - .addHeader("Accept", "application/json") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/ingress/peers/{ingressPeerId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'PUT', - CURLOPT_POSTFIELDS => '{ - "enabled": true, - "fallback": true -}', - CURLOPT_HTTPHEADER => array( - 'Content-Type: application/json', - 'Accept: application/json', - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - -```json {{ title: 'Example' }} -{ - "id": "ch8i4ug6lnn4g9hqv7m0", - "peer_id": "x7p3kqf2rdd8j5zxw4n9", - "ingress_ip": "192.34.0.123", - "available_ports": { - "tcp": 45765, - "udp": 50000 - }, - "enabled": true, - "connected": true, - "fallback": true, - "region": "germany" -} -``` -```json {{ title: 'Schema' }} -{ - "id": "string", - "peer_id": "string", - "ingress_ip": "string", - "available_ports": { - "tcp": "integer", - "udp": "integer" - }, - "enabled": "boolean", - "connected": "boolean", - "fallback": "boolean", - "region": "string" -} -``` - - - - - - ---- - - -## Delete a Ingress Peer {{ tag: 'DELETE' , label: '/api/ingress/peers/{ingressPeerId}' }} - - - - Delete an ingress peer - - ### Path Parameters - - - - The unique identifier of an ingress peer - - - - - - -```bash {{ title: 'cURL' }} -curl -X DELETE https://api.netbird.io/api/ingress/peers/{ingressPeerId} \ --H 'Authorization: Token ' -``` - -```js -const axios = require('axios'); - -let config = { - method: 'delete', - maxBodyLength: Infinity, - url: '/api/ingress/peers/{ingressPeerId}', - headers: { - 'Authorization': 'Token ' - } -}; - -axios(config) -.then((response) => { - console.log(JSON.stringify(response.data)); -}) -.catch((error) => { - console.log(error); -}); -``` - -```python -import requests -import json - -url = "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" - -headers = { - 'Authorization': 'Token ' -} - -response = requests.request("DELETE", url, headers=headers) - -print(response.text) -``` - -```go -package main - -import ( - "fmt" - "strings" - "net/http" - "io/ioutil" -) - -func main() { - - url := "https://api.netbird.io/api/ingress/peers/{ingressPeerId}" - method := "DELETE" - - client := &http.Client { - } - req, err := http.NewRequest(method, url, nil) - - if err != nil { - fmt.Println(err) - return - } - - - req.Header.Add("Authorization", "Token ") - - res, err := client.Do(req) - if err != nil { - fmt.Println(err) - return - } - defer res.Body.Close() - - body, err := ioutil.ReadAll(res.Body) - if err != nil { - fmt.Println(err) - return - } - fmt.Println(string(body)) -} -``` - -```ruby -require "uri" -require "json" -require "net/http" - -url = URI("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - -https = Net::HTTP.new(url.host, url.port) -https.use_ssl = true - -request = Net::HTTP::Delete.new(url) -request["Authorization"] = "Token " - -response = https.request(request) -puts response.read_body -``` - -```java -OkHttpClient client = new OkHttpClient().newBuilder() - .build(); - -Request request = new Request.Builder() - .url("https://api.netbird.io/api/ingress/peers/{ingressPeerId}") - .method("DELETE") - .addHeader("Authorization: Token ") - .build(); -Response response = client.newCall(request).execute(); -``` - -```php - 'https://api.netbird.io/api/ingress/peers/{ingressPeerId}', - CURLOPT_RETURNTRANSFER => true, - CURLOPT_ENCODING => '', - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 0, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => 'DELETE', - CURLOPT_HTTPHEADER => array( - 'Authorization: Token ' - ), -)); - -$response = curl_exec($curl); - -curl_close($curl); -echo $response; -``` - - - - - - - - ---- diff --git a/src/pages/ipa/resources/msp.mdx b/src/pages/ipa/resources/msp.mdx index 66a783e3..2feb6b1a 100644 --- a/src/pages/ipa/resources/msp.mdx +++ b/src/pages/ipa/resources/msp.mdx @@ -845,6 +845,171 @@ echo $response; --- +## Delete MSP tenant {{ tag: 'DELETE' , label: '/api/integrations/msp/tenants/{id}' }} + + + + + + ### Path Parameters + + + + The unique identifier of a tenant account + + + + + + +```bash {{ title: 'cURL' }} +curl -X DELETE https://api.netbird.io/api/integrations/msp/tenants/{id} \ +-H 'Authorization: Token ' +``` + +```js +const axios = require('axios'); + +let config = { + method: 'delete', + maxBodyLength: Infinity, + url: '/api/integrations/msp/tenants/{id}', + headers: { + 'Authorization': 'Token ' + } +}; + +axios(config) +.then((response) => { + console.log(JSON.stringify(response.data)); +}) +.catch((error) => { + console.log(error); +}); +``` + +```python +import requests +import json + +url = "https://api.netbird.io/api/integrations/msp/tenants/{id}" + +headers = { + 'Authorization': 'Token ' +} + +response = requests.request("DELETE", url, headers=headers) + +print(response.text) +``` + +```go +package main + +import ( + "fmt" + "strings" + "net/http" + "io/ioutil" +) + +func main() { + + url := "https://api.netbird.io/api/integrations/msp/tenants/{id}" + method := "DELETE" + + client := &http.Client { + } + req, err := http.NewRequest(method, url, nil) + + if err != nil { + fmt.Println(err) + return + } + + + req.Header.Add("Authorization", "Token ") + + res, err := client.Do(req) + if err != nil { + fmt.Println(err) + return + } + defer res.Body.Close() + + body, err := ioutil.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return + } + fmt.Println(string(body)) +} +``` + +```ruby +require "uri" +require "json" +require "net/http" + +url = URI("https://api.netbird.io/api/integrations/msp/tenants/{id}") + +https = Net::HTTP.new(url.host, url.port) +https.use_ssl = true + +request = Net::HTTP::Delete.new(url) +request["Authorization"] = "Token " + +response = https.request(request) +puts response.read_body +``` + +```java +OkHttpClient client = new OkHttpClient().newBuilder() + .build(); + +Request request = new Request.Builder() + .url("https://api.netbird.io/api/integrations/msp/tenants/{id}") + .method("DELETE") + .addHeader("Authorization: Token ") + .build(); +Response response = client.newCall(request).execute(); +``` + +```php + 'https://api.netbird.io/api/integrations/msp/tenants/{id}', + CURLOPT_RETURNTRANSFER => true, + CURLOPT_ENCODING => '', + CURLOPT_MAXREDIRS => 10, + CURLOPT_TIMEOUT => 0, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, + CURLOPT_CUSTOMREQUEST => 'DELETE', + CURLOPT_HTTPHEADER => array( + 'Authorization: Token ' + ), +)); + +$response = curl_exec($curl); + +curl_close($curl); +echo $response; +``` + + + + + + + + +--- + + ## Unlink a tenant {{ tag: 'POST' , label: '/api/integrations/msp/tenants/{id}/unlink' }} diff --git a/src/pages/ipa/resources/peers.mdx b/src/pages/ipa/resources/peers.mdx index 747b70d1..d2f3895f 100644 --- a/src/pages/ipa/resources/peers.mdx +++ b/src/pages/ipa/resources/peers.mdx @@ -18,6 +18,10 @@ export const title = 'Peers' Filter peers by IP address + + + Filter peers by MAC address of a network interface + @@ -180,6 +184,12 @@ echo $response; "created_at": "2023-05-05T09:00:35.477782Z", "ip": "10.64.0.1", "ipv6": "fd00:4e42:ab12::1", + "network_addresses": [ + { + "net_ip": "192.168.0.11/24", + "mac": "00:93:37:bd:83:0f" + } + ], "connection_ip": "35.64.0.1", "connected": true, "last_seen": "2023-05-05T10:05:26.420578Z", @@ -242,6 +252,12 @@ echo $response; "created_at": "string", "ip": "string", "ipv6": "string", + "network_addresses": [ + { + "net_ip": "string", + "mac": "string" + } + ], "connection_ip": "string", "connected": "boolean", "last_seen": "string", @@ -475,6 +491,12 @@ echo $response; "created_at": "2023-05-05T09:00:35.477782Z", "ip": "10.64.0.1", "ipv6": "fd00:4e42:ab12::1", + "network_addresses": [ + { + "net_ip": "192.168.0.11/24", + "mac": "00:93:37:bd:83:0f" + } + ], "connection_ip": "35.64.0.1", "connected": true, "last_seen": "2023-05-05T10:05:26.420578Z", @@ -534,6 +556,12 @@ echo $response; "created_at": "string", "ip": "string", "ipv6": "string", + "network_addresses": [ + { + "net_ip": "string", + "mac": "string" + } + ], "connection_ip": "string", "connected": "boolean", "last_seen": "string", @@ -875,6 +903,12 @@ echo $response; "created_at": "2023-05-05T09:00:35.477782Z", "ip": "10.64.0.1", "ipv6": "fd00:4e42:ab12::1", + "network_addresses": [ + { + "net_ip": "192.168.0.11/24", + "mac": "00:93:37:bd:83:0f" + } + ], "connection_ip": "35.64.0.1", "connected": true, "last_seen": "2023-05-05T10:05:26.420578Z", @@ -934,6 +968,12 @@ echo $response; "created_at": "string", "ip": "string", "ipv6": "string", + "network_addresses": [ + { + "net_ip": "string", + "mac": "string" + } + ], "connection_ip": "string", "connected": "boolean", "last_seen": "string", diff --git a/src/pages/ipa/resources/posture-checks.mdx b/src/pages/ipa/resources/posture-checks.mdx index c5f61084..2d6e3719 100644 --- a/src/pages/ipa/resources/posture-checks.mdx +++ b/src/pages/ipa/resources/posture-checks.mdx @@ -214,6 +214,11 @@ echo $response; "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } } @@ -269,6 +274,11 @@ echo $response; "windows_path": "string" } ] + }, + "certificate_check": { + "ca_certificates": [ + "string" + ] } } } @@ -520,6 +530,23 @@ echo $response; + + + +
+ Posture check for a certificate held by the peer that chains to one of the given CA certificates. Not supported on mobile devices (iOS and Android), which always fail this check and are denied access + + + + + PEM encoded CA certificates the peer's certificate must chain to + + + + + +
+
@@ -590,6 +617,11 @@ curl -X POST https://api.netbird.io/api/posture-checks \ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }' @@ -648,6 +680,11 @@ let data = JSON.stringify({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }); @@ -728,6 +765,11 @@ payload = json.dumps({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }) @@ -808,6 +850,11 @@ func main() { "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }`) @@ -907,6 +954,11 @@ request.body = JSON.dump({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }) @@ -969,6 +1021,11 @@ RequestBody body = RequestBody.create(mediaType, '{ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }'); @@ -1047,6 +1104,11 @@ curl_setopt_array($curl, array( "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }', @@ -1120,6 +1182,11 @@ echo $response; "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } } @@ -1173,6 +1240,11 @@ echo $response; "windows_path": "string" } ] + }, + "certificate_check": { + "ca_certificates": [ + "string" + ] } } } @@ -1405,6 +1477,11 @@ echo $response; "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } } @@ -1458,6 +1535,11 @@ echo $response; "windows_path": "string" } ] + }, + "certificate_check": { + "ca_certificates": [ + "string" + ] } } } @@ -1716,6 +1798,23 @@ echo $response; + + + +
+ Posture check for a certificate held by the peer that chains to one of the given CA certificates. Not supported on mobile devices (iOS and Android), which always fail this check and are denied access + + + + + PEM encoded CA certificates the peer's certificate must chain to + + + + + +
+
@@ -1786,6 +1885,11 @@ curl -X PUT https://api.netbird.io/api/posture-checks/{postureCheckId} \ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }' @@ -1844,6 +1948,11 @@ let data = JSON.stringify({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }); @@ -1924,6 +2033,11 @@ payload = json.dumps({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }) @@ -2004,6 +2118,11 @@ func main() { "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }`) @@ -2103,6 +2222,11 @@ request.body = JSON.dump({ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }) @@ -2165,6 +2289,11 @@ RequestBody body = RequestBody.create(mediaType, '{ "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }'); @@ -2243,6 +2372,11 @@ curl_setopt_array($curl, array( "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } }', @@ -2316,6 +2450,11 @@ echo $response; "windows_path": "C:
rogramData…etBird\netbird.exe" } ] + }, + "certificate_check": { + "ca_certificates": [ + "-----BEGIN CERTIFICATE-----\nMIIB...\n-----END CERTIFICATE-----" + ] } } } @@ -2369,6 +2508,11 @@ echo $response; "windows_path": "string" } ] + }, + "certificate_check": { + "ca_certificates": [ + "string" + ] } } } diff --git a/src/pages/ipa/resources/services.mdx b/src/pages/ipa/resources/services.mdx index 4cc55473..ad7a9167 100644 --- a/src/pages/ipa/resources/services.mdx +++ b/src/pages/ipa/resources/services.mdx @@ -537,6 +537,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -635,6 +636,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -772,6 +774,11 @@ echo $response; URL path prefix for this target (HTTP only) + + + + Access action applied after this target's HTTP path prefix is selected. "inherit" uses the service authentication configuration, "bypass" skips service authentication, and "block" denies access. HTTP services only. + @@ -1056,6 +1063,7 @@ curl -X POST https://api.netbird.io/api/reverse-proxies/services \ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1145,6 +1153,7 @@ let data = JSON.stringify({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1256,6 +1265,7 @@ payload = json.dumps({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1367,6 +1377,7 @@ func main() { "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1497,6 +1508,7 @@ request.body = JSON.dump({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1590,6 +1602,7 @@ RequestBody body = RequestBody.create(mediaType, '{ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1699,6 +1712,7 @@ curl_setopt_array($curl, array( "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1805,6 +1819,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -1901,6 +1916,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -2169,6 +2185,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2265,6 +2282,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -2409,6 +2427,11 @@ echo $response; URL path prefix for this target (HTTP only) + + + + Access action applied after this target's HTTP path prefix is selected. "inherit" uses the service authentication configuration, "bypass" skips service authentication, and "block" denies access. HTTP services only. + @@ -2693,6 +2716,7 @@ curl -X PUT https://api.netbird.io/api/reverse-proxies/services/{serviceId} \ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2782,6 +2806,7 @@ let data = JSON.stringify({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -2893,6 +2918,7 @@ payload = json.dumps({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3004,6 +3030,7 @@ func main() { "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3134,6 +3161,7 @@ request.body = JSON.dump({ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3227,6 +3255,7 @@ RequestBody body = RequestBody.create(mediaType, '{ "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3336,6 +3365,7 @@ curl_setopt_array($curl, array( "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3442,6 +3472,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -3538,6 +3569,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -4210,6 +4242,7 @@ echo $response; "target_id": "cs8i4ug6lnn4g9hqv7mg", "target_type": "subnet", "path": "/", + "access_action": "inherit", "protocol": "http", "host": "10.10.0.1", "port": 8080, @@ -4306,6 +4339,7 @@ echo $response; "target_id": "string", "target_type": "string", "path": "string", + "access_action": "string", "protocol": "string", "host": "string", "port": "integer", @@ -4398,7 +4432,7 @@ echo $response;
- Delete an existing service custom domain + Delete an existing service custom domain after removing or moving all services that use it or its subdomains, including disabled services. ### Path Parameters diff --git a/src/pages/manage/access-control/endpoint-detection-and-response/index.mdx b/src/pages/manage/access-control/endpoint-detection-and-response/index.mdx index e8245c7f..2a9a9ac7 100644 --- a/src/pages/manage/access-control/endpoint-detection-and-response/index.mdx +++ b/src/pages/manage/access-control/endpoint-detection-and-response/index.mdx @@ -28,7 +28,7 @@ NetBird doesn't apply the MDM and EDR checks to all devices in the network. Inst the checks to apply. - This feature is only available in the cloud version of NetBird. + This feature is available in NetBird Cloud on the [Business plan](https://netbird.io/pricing) and in self-hosted deployments with an [Enterprise Commercial License](/selfhosted/enterprise). The open source Community Edition does not include it. ## Supported EDR Platforms diff --git a/src/pages/manage/access-control/posture-checks/device-certificates.mdx b/src/pages/manage/access-control/posture-checks/device-certificates.mdx new file mode 100644 index 00000000..a6c12b7f --- /dev/null +++ b/src/pages/manage/access-control/posture-checks/device-certificates.mdx @@ -0,0 +1,162 @@ +import {Note, Warning} from "@/components/mdx"; + +export const description = "Use a Certificate posture check so only devices holding a certificate from your own CA, with the private key still on the device, reach the resources a policy protects." + +# Requiring a device certificate + +Most posture checks read something the device reports about itself: its client version, its operating system, a process name. Those are good at keeping unmanaged devices out, but a determined user can satisfy them on a personal laptop. What they cannot produce is a certificate your own certificate authority issued to a company device, and the private key that goes with it. The **Certificate** posture check asks for exactly that: the peer must hold a certificate that chains to a CA you name, and it must prove it still has the private key by signing a challenge from the management server. A copied certificate without its key proves nothing. + +Think of it as a badge check at the door, repeated. The CA is the badge printer you trust, the certificate is the badge, and the signature is the guard asking the holder to sign their name rather than just wave the badge. NetBird repeats the check every 4 hours for as long as the peer is connected, so a key that is removed from a device, or a certificate that expires, closes the door within 4 hours. + +## The scenario + +A company runs a PKI with a root CA, `Corp Root CA`, that issues a certificate to every managed laptop. The laptops are NetBird peers in the group `laptops`, and an access policy from `laptops` to the group `internal-apps` grants them the internal services. The goal: a device that is in `laptops` but has no company certificate, or has lost its key, must not reach `internal-apps`. Everything below is built around that example. + +Three things to do, in order: + +1. Create a Certificate posture check that trusts `Corp Root CA`. +2. Attach it to the policy from `laptops` to `internal-apps`. +3. Make sure each laptop holds its certificate where the NetBird client can use it. + +## Prerequisites + +- NetBird management and client version [0.81.0](https://github.com/netbirdio/netbird/releases) or newer on both sides. Older clients never answer the challenge and fail the check. +- The PEM encoded certificate of the CA that issues your device certificates. A root or an intermediate works; whichever you paste becomes a trust anchor, so an intermediate narrows the check to the devices it signed. +- Device certificates with a P-256 or P-384 ECDSA key or an RSA key of at least 2048 bits, issued by that CA and valid at the time of the check. An Ed25519 key also works, as a PEM key file on Linux. The private key must be usable without a prompt: a key that asks for a PIN or a password on every signature cannot be used by a background service. + + + The check accepts any certificate under the CA, whatever it was issued for, and it does not tie the certificate to a user or a hostname. Dedicate a CA, or an intermediate, to device identities rather than reusing the one that signs your TLS servers. + + +## Create the posture check + +Navigate to `Access Control` > `Posture Checks` in the NetBird dashboard and click `Create Posture Check`. Enable `Certificate`. + +![Certificate posture check with a CA certificate field](/docs-static/img/manage/access-control/posture-checks/posture-checks-certificate.png) + +Paste the PEM of `Corp Root CA` into the `CA Certificates` field. `Add CA Certificate` adds further anchors, for example a second root during a CA rotation; a peer passes if its certificate chains to any of them. Give the check a name on the `Name & Description` tab, for example `Company device certificate`, and save. + +## Attach it to the policy + +Open the policy from `laptops` to `internal-apps`, go to its `Posture Checks` tab, choose `Browse Checks` and add `Company device certificate`. From now on the policy applies only to peers in `laptops` that pass the check; the devices keep whatever other policies grant them. + + + A peer that sends no certificate at all fails the check, including every iOS and Android device: the mobile apps do not support certificate proofs. Do not attach this check to a policy that mobile devices rely on. + + +## Put the certificate on the device + +The client proves possession with whatever the operating system already uses to store certificates, so for a managed fleet this step is usually done by your MDM or enrollment tooling rather than by hand. What the client reads on each platform: + +| Platform | Device certificates | User certificates | +| --- | --- | --- | +| Windows | `Local Computer` > `Personal` store, where Active Directory and Intune enroll device certificates | the `Personal` store of the signed-in user who owns the active NetBird profile | +| macOS | the System keychain, where MDM installs device identities | the login keychain of the signed-in user who owns the active profile | +| Linux | PEM files in `/etc/netbird/certs`, or a PKCS#11 token | not applicable | + +The **profile owner** is the user who created the NetBird profile the client is currently running. Only that user's store is consulted, and only while that user has a session open, so a second person signed in to the same machine cannot answer for the profile, and a laptop sitting at the sign-in screen proves device certificates alone. + +### Windows and macOS + +On Windows there is nothing to configure. A certificate enrolled into the computer store is found by the service; a certificate in the owner's personal store is found through a helper that NetBird runs inside that user's session. Keys protected by a TPM work the same way, since the client only ever asks the operating system to sign. + +On macOS the System keychain needs one setting, described below. A certificate in the owner's login keychain is found through the helper, like on Windows, and keys held in the Secure Enclave work too. + +Two platform details are worth knowing. On macOS, the first signature from the login keychain shows the standard prompt that `netbird` wants to use the key; choose `Always Allow`, or the prompt returns on every check. If the user declines, the client does not ask that user again for an hour. On both platforms a key that would need a PIN or password is skipped rather than prompted for. + +#### macOS: letting NetBird use a device key + +The NetBird service reads the System keychain without a user session, so it cannot answer a keychain prompt. A device identity therefore works only when its key allows NetBird to use it. + +- When the identity is installed with MDM, set `AllowAllAppsAccess` to `true` in the certificate payload. MDM cannot name a single application, so this lets every application on the device use the key, including to produce a proof for this check. +- When importing it by hand, allow the NetBird binary explicitly. `security import` prompts for the `.p12` passphrase, which keeps it out of your shell history: + +```bash +sudo security import device.p12 -k /Library/Keychains/System.keychain \ + -T /Applications/NetBird.app/Contents/MacOS/netbird +``` + +#### Which user counts + +The user whose certificate counts is the owner of the active NetBird profile. For a profile without an owner it is the user signed in at the console. A user certificate is proven shortly after that user signs in. + +### Linux: certificate files + +Place the certificate and its key in `/etc/netbird/certs`, or in the directory named by the `NB_CERT_STORE_DIR` environment variable of the service. A certificate file ends in `.pem`, `.crt` or `.cer` and holds the device certificate first, followed by any intermediates. The key is either in the same file or in a sibling file with the same name and the `.key` extension: + +```text +/etc/netbird/certs/ +├── laptop.pem # device certificate, intermediates after it +└── laptop.key # PKCS#8, EC or RSA private key +``` + +The directory and the files must be owned by root and writable by nobody else, and no file in the directory may be a symlink. Keep plain key files readable by root only (`chmod 600 laptop.key`): the client does not refuse a world-readable key, but any local user could copy it and pass the check from another machine. Several certificates can sit side by side, and the client picks whichever one chains to the CA in the check. + +### Linux: key in the TPM + +A key that lives in the TPM never exists as a file in the clear. Two formats are supported. + +**A TSS2 key file**, the format `tpm2-openssl` and `tpm2-tss-engine` write, goes into the same directory as a plain key would: + +```bash +openssl genpkey -provider tpm2 -algorithm EC -pkeyopt group:P-256 -out /etc/netbird/certs/laptop.key +openssl req -provider tpm2 -provider default -new -key /etc/netbird/certs/laptop.key -subj "/CN=$(hostname)" -out laptop.csr +# have Corp Root CA sign laptop.csr, then store the result as /etc/netbird/certs/laptop.pem +``` + +The client signs through `/dev/tpmrm0` for every challenge and the key file stays useless on any other machine. + +**A tpm2-pkcs11 token** is the other route, and the one Red Hat-based distributions document. Give the service the token through its environment file, `/etc/sysconfig/netbird`. The rpm package installs it readable by root only; on other distributions, such as Debian and Ubuntu, create it yourself before putting the PIN in it (`sudo install -m 0600 /dev/null /etc/sysconfig/netbird`): + +```bash +NB_CERT_PKCS11_URI=pkcs11:token=netbird?module-path=/usr/lib64/pkcs11/libtpm2_pkcs11.so +NB_TPM_PIN= +``` + +The URI follows RFC 7512: `token` selects the token by label, `module-path` names the module and may be left out where p11-kit knows the module. A PIN is accepted only together with a token label, so a mistyped PIN can never be tried against a smartcard that happens to be plugged in. A PIN the token rejects is not tried again until the service restarts, so correct `NB_TPM_PIN` and restart the service. The certificate can be an object on the token, added with `tpm2_ptool addcert`, or a `.pem` file in `/etc/netbird/certs` without a key file next to it: the client pairs such a file with the token key that has the same public key. + + + Do not pass the PIN with `netbird service install --service-env`: the command line ends up in your shell history. Put it in the environment file. + + + + The PKCS#11 store is part of the deb and rpm packages for amd64 and arm64. The 32-bit packages, the generic Linux tarball and the container images cannot load a PKCS#11 module; they log that the configured token is ignored. A TSS2 key file works with every Linux build. + + +## Verify + +A device that proves no certificate yet tries again every five minutes, so a certificate placed later is picked up without a restart. To watch it work, raise the log level with `netbird debug log level debug` and read the client log. On Linux that is `/var/log/netbird/client.log`; the equivalent Windows and macOS paths are listed on the [troubleshooting page](/help/troubleshooting-client). A laptop that passes logs, in this order: + +```text +certificate posture: store holds 1 candidate certificates +certificate posture: challenge 0 proven by "CN=laptop-42" with ecdsa-sha256, signature 71 bytes, chain of 2 +certificate posture: 1 challenges produced 1 proofs +``` + +`challenge 0 matched none of the 1 candidates` means the certificate does not chain to a CA in the check, usually because an intermediate is missing from the file or from the check; the lines before it give the reason for each certificate. `certificate store holds no candidates` means no usable certificate and key pair was found at all. The management server logs nothing for a successful proof and a `dropping a certificate proof of peer` line with the reason for an invalid one. + +Two other messages are worth knowing: + +| Log message | Meaning | +| --- | --- | +| `SecKeyCreateSignature: CFError -25308` | macOS: the key's access list does not allow NetBird. See [letting NetBird use a device key](#mac-os-letting-net-bird-use-a-device-key). | +| `rejected the PIN, not retrying it until the daemon restarts` | The token refused the PIN. Fix `NB_TPM_PIN` and restart the service. | + +In the dashboard the effect is the same as for any other posture check: a peer that fails loses the policy, so it no longer sees the `internal-apps` peers, and regains them on the next sync after it passes. + +## How the check keeps itself current + +The management server sends each connected peer a fresh challenge every 4 hours. The client signs the newest challenge and reports its proofs only when the set of certificates it can prove has changed, so a steady fleet produces no extra traffic. A device whose key disappears fails within 4 hours, and a certificate that expires fails at the next evaluation. This is the one posture check that is refreshed on a timer; the others are driven purely by configuration and metadata changes, as described in [When Posture Checks Are Evaluated](/manage/access-control/posture-checks#when-posture-checks-are-evaluated). + +## When not to use it + +- Fleets with iOS or Android devices in the same policy: they always fail the check. Give mobile devices their own policy, or gate them with checks they support. +- Certificates with keys you cannot make available without a prompt, such as smartcards that require a PIN on every signature. +- As a substitute for user identity. The check proves the device holds a key from your CA; who is using the device is what your identity provider and the policy's groups decide. + +## In one breath + +- A Certificate posture check trusts `Corp Root CA`; attached to the `laptops` to `internal-apps` policy, it hides the policy from any laptop without a company certificate. +- The laptop proves it holds the key by signing a challenge from management, every 4 hours while connected. +- Windows and macOS find the certificate in the system and the profile owner's store; Linux reads `/etc/netbird/certs` or a tpm2-pkcs11 token. +- Lose the key, or let the certificate expire, and the policy is gone within 4 hours. diff --git a/src/pages/manage/access-control/posture-checks/index.mdx b/src/pages/manage/access-control/posture-checks/index.mdx index c92d8e86..12aa935d 100644 --- a/src/pages/manage/access-control/posture-checks/index.mdx +++ b/src/pages/manage/access-control/posture-checks/index.mdx @@ -5,7 +5,7 @@ In this regard, NetBird posture checks verify various aspects of a connecting de -The feature also allows for network-level restrictions by enabling you to **allow or block specific peer network ranges**. Furthermore, you can set constraints based on the operating system of the connecting device, **ensuring that only approved OS versions can gain access**. For an even more detailed level of control, Posture Checks can examine the running processes on a peer device, **allowing or denying access based on the presence of specific applications or services**. +The feature also allows for network-level restrictions by enabling you to **allow or block specific peer network ranges**. Furthermore, you can set constraints based on the operating system of the connecting device, **ensuring that only approved OS versions can gain access**. For an even more detailed level of control, Posture Checks can examine the running processes on a peer device, **allowing or denying access based on the presence of specific applications or services**. Finally, a device can be required to **hold a certificate issued by your own certificate authority** and prove it still has the private key, which ties access to a credential you issued rather than to anything the device merely reports. By using these diverse checking capabilities, NetBird empowers you to create a robust and finely-tuned security posture for your network, significantly reducing the risk of unauthorized access and potential security breaches. @@ -85,6 +85,17 @@ Furthermore, this process-based posture check allows you to create specific poli ![Process Posture Check](/docs-static/img/manage/access-control/posture-checks/posture-checks-07.png) +### Certificate +Restrict access to peers holding a certificate issued by your own certificate authority. Unlike most other checks, this one is not satisfied by something the device reports: the peer must present a certificate that chains to one of the CAs you paste here, and sign a challenge from the management server with the certificate's private key. A certificate copied to another machine without its key does not pass, and a key that is removed or a certificate that expires fails the check within 4 hours. + +![Certificate Posture Check](/docs-static/img/manage/access-control/posture-checks/posture-checks-certificate.png) + +The client finds certificates where the operating system keeps them: the computer and user stores on Windows, the System and login keychains on macOS, and on Linux a directory of PEM files or a PKCS#11 token, including keys held in a TPM. Which store to use, how to place a certificate on each platform and how to verify the result are covered in [Requiring a device certificate](/manage/access-control/posture-checks/device-certificates). + + + The Certificate check requires NetBird version [0.81.0](https://github.com/netbirdio/netbird/releases) or newer on both the management server and the client. It is not supported on iOS and Android: mobile peers always fail it and are denied the access the policy grants, so keep it off policies that mobile devices depend on. + + ## Name & Description After enabling the desired posture check, go to the `Name & Description` tab. Here, enter a descriptive name for your newly created posture check and save it. @@ -120,7 +131,7 @@ Following these steps, you can effectively implement and manage NetBird's Postur NetBird evaluates posture checks when a peer connects or logs in. Checks are also re-evaluated during an active session whenever a peer reports changed system metadata. The management server replays each check against the peer's previous and current metadata, and when a check's verdict flips, it recomputes the network map and distributes the update to the affected peers. -When you add or change a posture check on a policy, the updated configuration is distributed to connected peers immediately. A peer that is already connected is evaluated against the new check on its next connection. There is no periodic redistribution interval; distribution is driven by configuration changes and peer metadata changes, not a timer. For more on how changes reach peers, see [How Policy Changes Are Distributed](/manage/access-control#how-policy-changes-are-distributed). +When you add or change a posture check on a policy, the updated configuration is distributed to connected peers immediately. A peer that is already connected is evaluated against the new check on its next connection. There is no periodic redistribution interval; distribution is driven by configuration changes and peer metadata changes, not a timer. The one exception is the Certificate check, whose challenge the management server renews every four hours so a connected peer keeps re-proving possession of its key; see [How the check keeps itself current](/manage/access-control/posture-checks/device-certificates#how-the-check-keeps-itself-current). For more on how changes reach peers, see [How Policy Changes Are Distributed](/manage/access-control#how-policy-changes-are-distributed). Mid-session re-evaluation requires a NetBird management server running [0.74.0](https://github.com/netbirdio/netbird/releases) or newer, and is driven by the peer reporting a metadata change rather than by a continuous poll. A peer that passes a posture check and later changes state during a long-lived session (for example an operating system downgrade or a required process stopping) is re-evaluated on its next metadata sync. On management servers older than 0.74.0, that state change is not picked up until the peer reconnects. @@ -156,6 +167,10 @@ Upgrade the Android client to 0.77.1 or newer. While older Android clients are s - Use Peer Network Range checks based on the **public connection IP**, which work on every platform and every version - Apply local-NIC-based Peer Network Range posture checks only to policies targeting platforms that report them (Windows, macOS, Linux, iOS 0.69.0+, and Android 0.77.1+) +### Certificate Check on Mobile + +The NetBird iOS and Android apps do not collect certificate proofs, so a mobile peer always fails a Certificate check and is denied the access of every policy that carries it. There is no version of the mobile apps that changes this yet. Keep Certificate checks on policies whose source groups hold desktop and server peers only, and gate mobile devices with the checks they support. + ## Get started with NetBird
diff --git a/src/pages/manage/activity/event-streaming/index.mdx b/src/pages/manage/activity/event-streaming/index.mdx index 4f8b83d1..2fb818c6 100644 --- a/src/pages/manage/activity/event-streaming/index.mdx +++ b/src/pages/manage/activity/event-streaming/index.mdx @@ -23,4 +23,19 @@ supported third-party platforms. To get started, select one of the following int - [Amazon Data Firehose](/manage/activity/event-streaming/amazon-firehose) - [SentinelOne Data Lake](/manage/activity/event-streaming/sentinelone-data-lake) - [Generic HTTP](/manage/activity/event-streaming/generic-http) -- [Wazuh](/manage/activity/event-streaming/wazuh) \ No newline at end of file +- [Wazuh](/manage/activity/event-streaming/wazuh) + +## Delivery + +NetBird streams each audit event and traffic event after saving it, so an event that fails to stream is not lost: it remains in the [audit events log](/manage/activity) or the [traffic events](/manage/activity/traffic-events-logging) store. When the destination is unreachable or rejects an event, Datadog and Generic HTTP behave differently: + +- **Datadog**: one delivery attempt per event. A failed delivery is not retried. +- **Generic HTTP**: a failed delivery is retried twice, after 1 and then 2 seconds, before the event is dropped (three attempts in total by default). + +Creating, updating, and deleting a stream destination are recorded in the audit events log as `Integration created`, `Integration updated`, and `Integration deleted`. Updating includes enabling and disabling the destination. + +Changes to a stream destination take up to five minutes to apply, because NetBird caches the destination's settings. After you disable or delete a destination, audit and traffic events can keep arriving there for up to five minutes, including the event that records the change. After you enable it again, events can take the same time to resume. In self-hosted Enterprise deployments, this interval is set by `NB_EVENT_STREAMING_CACHE_TTL` on the management server and the enricher service. + +NetBird removes any `name` or `email` key from `meta` before streaming an event. For many audit events, such as group, policy, and peer changes, `name` is where the object's name is, so look it up in the audit events log using `target_id`. + +For the fields carried by streamed traffic events, see [Streamed event fields](/manage/activity/traffic-events-logging#streamed-event-fields). diff --git a/src/pages/manage/activity/event-streaming/wazuh.mdx b/src/pages/manage/activity/event-streaming/wazuh.mdx index fdafae9b..70b4b3ca 100644 --- a/src/pages/manage/activity/event-streaming/wazuh.mdx +++ b/src/pages/manage/activity/event-streaming/wazuh.mdx @@ -93,7 +93,7 @@ NetBird emits **two distinct event shapes** through the same integration. Both a - **`ID` may be a number or a string** (test events use a string identifier; real audit events use a numeric ID). Wazuh normalises both to strings in the indexed `data.nb.ID`, so KQL queries don't need to handle the mixed type. - **`Message`** is the most reliable field to pivot rules on; it's a stable English phrase like "peer added" or "policy created". - **`reference`** is the canonical URL into NetBird's activity log; useful as a click-through pivot from a Wazuh alert back to the source event. -- **`meta` is event-type-specific.** Peer events carry `meta.name`, setup-key events carry `meta.type`, integration events carry `meta.platform`. Treat unknown keys as opaque. +- **`meta` is event-type-specific.** Peer events carry `meta.fqdn` and `meta.ip`, setup-key events carry `meta.type`, integration events carry `meta.platform`. Any `name` or `email` key is removed before streaming. Treat unknown keys as opaque. ### Traffic events diff --git a/src/pages/manage/activity/index.mdx b/src/pages/manage/activity/index.mdx index 8d0355a4..53c92ec8 100644 --- a/src/pages/manage/activity/index.mdx +++ b/src/pages/manage/activity/index.mdx @@ -104,6 +104,14 @@ The current version of NetBird tracks a wide range of network changes that occur - Integration updated - Integration deleted + - **Traffic Events Logging Management:** + - Network traffic logging enabled + - Network traffic logging disabled + - Network traffic packet counting enabled + - Network traffic packet counting disabled + - Network traffic group added + - Network traffic group removed + - **Other Events:** - Transferred owner role - Posture check created diff --git a/src/pages/manage/activity/traffic-events-logging.mdx b/src/pages/manage/activity/traffic-events-logging.mdx index 6e81b792..fe1c126d 100644 --- a/src/pages/manage/activity/traffic-events-logging.mdx +++ b/src/pages/manage/activity/traffic-events-logging.mdx @@ -153,12 +153,16 @@ choose the groups you want to include, and click `Save Groups`. traffic-events-groups-logging-settings

+Changes to these settings, including the group scope, are pushed to connected peers and take effect without restarting the NetBird client. Every change is also recorded in the [audit events log](/manage/activity). + ## Log Retention While in experimental mode, logs are retained for **seven days**. Additionally, the current API returns a maximum of **50,000 flow records**. This limit may change. +In self-hosted Enterprise deployments, the retention period is set by `NB_PERSISTENCE_RETENTION_PERIOD` on the enricher service. The installer sets it to `168h` (seven days). + ## Report rate Aggregated flows might take up to **ten minutes** to become available through the API and Dashboard. An end count for some TCP connections can appear in a later window, depending on OS settings and connection termination. @@ -170,6 +174,27 @@ enhancing your ability to detect and respond to security events. For detailed instructions on supported integrations and how to set them up, refer to the [integrations guide](/manage/activity/event-streaming). +### Streamed event fields + +A streamed traffic event uses the same envelope as a streamed audit event: an event ID, a timestamp, and a message. Clients from v0.75 send aggregated events, whose message is always `TYPE_UNKNOWN`, so read activity from the `num_of_*` counts instead. The initiator and target fields are empty, and the reference field is empty or, for Datadog, omitted. Envelope field names depend on the integration: Datadog uses `id` and `timestamp`, Generic HTTP uses `ID` and `Timestamp`. The flow details are in `meta`, under the same keys for every integration unless you set a custom Generic HTTP body template: + +* **User**: `user_id`, the ID of the user associated with the source peer. +* **Flow**: `flow_id`, `reporter_id` (the peer that reported the event), `protocol`, `direction`, `policy_id`, `policy_name`, `icmp_type`, and `icmp_code`. +* **Source and destination**: `source_id`, `source_name`, `source_addr`, `source_dns_label`, `source_connection_ip`, `source_geo_city`, and `source_geo_country`, with the same set of `destination_*` fields. +* **Volume**: `rx_bytes`, `rx_packets`, `tx_bytes`, and `tx_packets`. +* **Counts**: `num_of_starts`, `num_of_ends`, and `num_of_drops`. +* **Timing**: `received_timestamp`, the time the event reached the NetBird servers. + +Compared with API responses, streamed events do not include the user's name or email, the aggregation window start and end, or the operating system of the source and destination. `user_id` is empty when the source peer has no associated user, such as a peer registered with a [setup key](/manage/peers/register-machines-using-setup-keys), and when the source is not a peer. + +### Streaming delivery + +Each traffic event is saved to the traffic events store before it is streamed, so an event that fails to stream is still visible in the Dashboard and API. The NetBird client resends each event until the NetBird servers acknowledge it. Once an event reaches the servers, every step up to saving it is retried: the servers acknowledge an event only after queueing it, and remove it from the queue only after saving it. If server processing falls behind, events wait in the queue and arrive late rather than being dropped. + +In rare cases, such as a server restart while an event is being processed, the same event can be streamed twice. Deduplicate on the event ID. + +Changes to a stream destination take up to five minutes to apply, and what happens when the destination is unreachable depends on the integration; see [Delivery](/manage/activity/event-streaming#delivery). + ## Traffic Events Data When enabled, a NetBird peer will record metadata for each network flow that it participates in. The data collected by peers includes: @@ -192,7 +217,7 @@ In addition to the data collected by the peers, the NetBird API provides additio * **Peer ID**: The unique identifier of the peer. * **Resource name**: The name of the resource or network route. * **Policy Name**: The name of the policy that allowed the flow. -* **User ID, name, and email**: The name and email of the user associated with the source peer. +* **User ID, name, and email**: The name and email of the user associated with the source peer. The API looks these up when you query it; [streamed events](#streamed-event-fields) carry the user ID only. * **Reporter ID**: The unique identifier of the peer that reported the traffic event. * **Received timestamp**: The timestamp when the event was received by the NetBird servers. @@ -352,6 +377,14 @@ On Linux, you can force a routing peer into userspace mode with three [environme sudo netbird service reconfigure --service-env NB_WG_KERNEL_DISABLED=true,NB_FORCE_USERSPACE_FIREWALL=true,NB_FORCE_USERSPACE_ROUTER=true ``` +### Traffic that is not recorded + +Some traffic never produces a traffic event, whatever the peer mode: + +* **DNS queries over UDP.** The NetBird client does not record UDP traffic to port `53`, or to ports `5353` and `22054`, which the NetBird DNS forwarder uses on routing peers. This also applies to other UDP traffic on these ports, such as mDNS. DNS over TCP is recorded as an ordinary flow. +* **Traffic through an exit node.** Flows matched to an exit node route (`0.0.0.0/0`) are not recorded, on the client or on the exit node. Traffic to a destination covered by a more specific route or Networks resource is still recorded. +* **Access withdrawn by a failed posture check.** When a peer fails a [posture check](/manage/access-control/posture-checks), the policy stops applying and the connection between the peers is removed. No traffic reaches either peer's firewall, so there is nothing to record as allowed or blocked. + ## Conclusion Traffic events logging provides a powerful tool for monitoring and analyzing network traffic across your infrastructure. Enabling this feature can provide valuable insights into network activity, enhance security measures, and improve operational efficiency. diff --git a/src/pages/manage/control-center/draft-mode.mdx b/src/pages/manage/control-center/draft-mode.mdx index 6ee5ac82..dfe0f048 100644 --- a/src/pages/manage/control-center/draft-mode.mdx +++ b/src/pages/manage/control-center/draft-mode.mdx @@ -75,6 +75,8 @@ Then drag a **Group** onto the canvas for the access we are about to grant. **Network** and **Resource** templates work the same way and integrate with [Networks](/manage/networks); connecting a policy to a network node asks which of its resources the policy should target. +When Agent Network is enabled, an **Agent Network** tab adds **Provider** and **Agent Policy** nodes; see [Agent Network in Control Center](/agent-network/control-center#build-access-in-draft-mode). + ### Undo and Redo `Ctrl/⌘+Z` undoes any canvas action, `Shift+Ctrl/⌘+Z` redoes it. diff --git a/src/pages/manage/control-center/index.mdx b/src/pages/manage/control-center/index.mdx index 75825f2d..77a4a3e7 100644 --- a/src/pages/manage/control-center/index.mdx +++ b/src/pages/manage/control-center/index.mdx @@ -88,6 +88,10 @@ Use this to see who can access resources in your routed [networks](/manage/netwo - The network node shows its resources. Connecting lines display the port allowed by the policy and which groups have access. - Click any policy chip to edit it in the standard editor. NetBird Networks and routing peers enable access to private subnets and IP resources. +### Agent Network + +When [Agent Network](/agent-network) is enabled for your account, the **Peers**, **Users**, and **Groups** views also show its providers and agent policies, so you can see which groups can reach which AI providers. You can edit providers and agent policies from the canvas and build new ones in Draft Mode. See [Agent Network in Control Center](/agent-network/control-center). + ## Focus Mode When the graph gets busy, focus a single node to see only what is connected to it. Right-click a node and choose **Focus**, or press `F` and then select the node. @@ -133,3 +137,4 @@ For changes that span several components, **Draft Mode** lets you stage the whol - [Apply posture checks to policies](/manage/access-control/posture-checks) - [Networks and routing peers](/manage/networks) - [MSP portal overview](/manage/for-partners/msp-portal) +- [Agent Network in Control Center](/agent-network/control-center) diff --git a/src/pages/manage/dns/index.mdx b/src/pages/manage/dns/index.mdx index f822d5a9..598c577c 100644 --- a/src/pages/manage/dns/index.mdx +++ b/src/pages/manage/dns/index.mdx @@ -18,6 +18,8 @@ NetBird provides centralized DNS management to help your peers resolve domain na Starting with [v0.11.0](https://github.com/netbirdio/netbird/releases), NetBird automatically assigns a domain name to each peer in a private `netbird.cloud` space. You can access machines using names like `my-server.netbird.cloud` instead of IP addresses. +When two peers in an account share a hostname, the second one gets an IP-based suffix such as `my-server-12-7.netbird.cloud` by default. You can make such registrations fail instead with the [Unique Peer Hostnames](/manage/settings/clients#unique-peer-hostnames) setting. + Available on both NetBird Cloud and self-hosted versions running v0.11.0 or later. diff --git a/src/pages/manage/for-partners/msp-portal.mdx b/src/pages/manage/for-partners/msp-portal.mdx index 0f3d2039..aa73cd52 100644 --- a/src/pages/manage/for-partners/msp-portal.mdx +++ b/src/pages/manage/for-partners/msp-portal.mdx @@ -171,6 +171,16 @@ Use email subaddressing to create a user inside the tenant for your MSP engineer 3. Click **Invite User** on the right. 4. Enter the subaddressed email (e.g. `engineer+clientname@yourcompany.com`). + +The invitation email arrives in your own inbox, where your browser is likely still signed in to your MSP account. Opening the invitation link there signs you back in to the MSP account instead of accepting the invite. To accept it as the new user: + +1. Copy the link from the invitation email's button and open it in a private browser window. +2. Enter the subaddressed email and click **Continue**. Don't use Google, Microsoft, or another social login, because those sign you in with your main address. +3. Set a password from the email you receive. + +If you already tried to sign up with the subaddressed email and got **Invalid sign up**, the account exists already. Use **Forgot password** on the login page with the same address to set your password. + + This is the recommended approach as it gives the MSP engineer a proper identity inside the tenant. Use NetBird's [profile switching](/client/profiles) feature to easily switch between your MSP account and client tenant connections on the same device. **Option 2: Add a Network and Routing Peer** diff --git a/src/pages/manage/networks/how-routing-peers-work.mdx b/src/pages/manage/networks/how-routing-peers-work.mdx index 97bcf466..5aec95f2 100644 --- a/src/pages/manage/networks/how-routing-peers-work.mdx +++ b/src/pages/manage/networks/how-routing-peers-work.mdx @@ -37,7 +37,7 @@ NetBird offers two ways to configure routing peers. Both are actively maintained ## Mental model: how traffic flows

- Routing peer bridging NetBird overlay to a private network + Three NetBird peers on the overlay network connect through a WireGuard tunnel to a routing peer in a private network. Access policies on the routing peer allow a database on TCP 5432, a web app on TCP 443, and an RDP workstation on TCP/UDP 3389; a printer with no policy is unreachable.

The walkthrough below describes the **Linux kernel-mode** path, where forwarding and firewalling happen in the kernel via `nftables` / `iptables`. On other platforms, NetBird performs the same logical steps in a userspace filter, but the implementation differs. @@ -98,19 +98,45 @@ The routing peer must have direct network reachability to the resources it serve ## High availability -Multiple routing peers can serve the same network or route. Behavior depends on the **metric** you assign each routing peer in the dashboard. There are no tunable thresholds — the metric is the only high availability control. +Multiple routing peers can serve the same network or route. Each peer that uses the network (a user's laptop, for example) picks one of those routing peers and sends all of that network's traffic through it until something makes it switch. The **metric** you assign each routing peer in the dashboard is the main control. There are no tunable thresholds. + +### How a peer picks a routing peer + +A peer compares the routing peers it is connected to, in this order: + +1. **Metric.** The lowest metric wins, whatever the connection type or latency. +2. **Connection type.** Among equal metrics, a direct (peer-to-peer) connection beats a relayed one. +3. **Latency.** Among equal metrics and the same connection type, the lowest latency wins. To prevent flapping, a peer only moves to a routing peer whose latency is at least 10 ms lower than its current one. + +Latency is measured once, when the connection to a routing peer is set up, and is not re-measured while that connection stays up. + +### What makes a peer switch + +A peer repeats this comparison whenever a routing peer's connection state changes or the network's configuration is updated. In practice it switches when: + +- **Its routing peer goes offline.** The peer moves to the next routing peer in the order above, normally within seconds. +- **A routing peer with a lower metric comes back online.** The peer moves back as soon as that routing peer reconnects, even while its connection is still relayed. +- **Its routing peer's connection falls back from direct to relayed.** If another routing peer with the same metric still has a direct connection, the peer moves to it, although nothing went offline. With different metrics this does not happen, because metric outranks connection type. + +### What a switch does to open connections + +With masquerade on (the default), every switch resets established TCP connections through the network, whatever caused it: a failover, a failback, or a fallback to relayed. The new routing peer translates the traffic to a different source address, so applications such as SSH must reconnect. Expect the connection to stall for several seconds before the reset, while the peer notices the change. With masquerade off, connections are not translated and behave differently: see [High availability with masquerade off](/manage/networks/masquerade#high-availability-with-masquerade-off). + +If users report dropped connections while no routing peer went offline, check whether the routing peers share a metric and whether their connections fall back to relayed. [Troubleshooting relayed connections](/help/troubleshooting-relayed-connections) covers why a connection is relayed. ### Primary / failover (different metrics) -The lower-metric peer carries all traffic. The higher-metric peer is held in reserve and only takes over when the primary becomes unreachable. Failover is automatic: clients start sending traffic through the standby once the primary is seen as unreachable, normally within seconds. When the primary comes back online, clients switch back to it. With masquerade on, the default, established TCP connections through the previous peer reset and applications must reconnect, because the standby translates them to a different source address. With masquerade off they are not translated, and behave differently: see [High availability with masquerade off](/manage/networks/masquerade#high-availability-with-masquerade-off). +Use different metrics when you want every peer on one known routing peer, with the others in reserve. Peers stay on the primary even when its connection falls back to relayed, but they fail back to it as soon as it returns, which resets connections a second time with masquerade on. To give routing peers different metrics, add them to the Network individually; routing peers added as a group share one metric. -**Example.** Routing Peer A has a lower metric than Routing Peer B. When Peer A goes down, all traffic fails over to Peer B. When Peer A comes back online, all traffic switches back to Peer A. +**Example.** Routing Peer A has a lower metric than Routing Peer B. Traffic uses Peer A, including while its connection is relayed. When Peer A goes down, traffic fails over to Peer B, and moves back as soon as Peer A reconnects. -### Latency switching (equal metrics) +### Nearest routing peer (equal metrics) -When two routing peers share the same metric, each client picks the one with lower latency. A switch only happens when the latency difference exceeds **20 ms**, which prevents flapping between peers that are roughly equivalent. +Use equal metrics when routing peers are spread across regions and you want each peer to use the closest one: for example, an EU-based and a US-based routing peer serving the same network, with users on both continents. Each peer picks the lowest-latency routing peer when it connects, though a direct connection still beats a relayed one whatever the latency. It does not move to another routing peer later just because latency changes, and routing peers within 10 ms of each other are effectively tied. The trade-off: a fallback to relayed can move peers to another routing peer and reset their connections. -Useful when routing peers are geographically distributed and you want each client to land on the closest one automatically — for example, an EU-based peer and a US-based peer serving the same network, with users on both continents. +### Finding switches in the client log + +The client logs each switch at the default log level as `New chosen route is …`, followed by the chosen routing peer's WireGuard public key and its score. The comparison behind the decision, including the score of the routing peer it kept, is logged only at the debug level. ### Failure domains @@ -146,6 +172,10 @@ This is the subtlest part of the model and the source of most policy mistakes. - **Forward chain.** Packets routed *through* the peer to backend resources. Network resource policies (Networks) and ACL Groups (Routes) apply here. - **Input chain.** Packets destined to the routing peer's own IP. [Peer-to-peer policies](/manage/access-control/manage-network-access) apply here. +

+ Two paths compared. Through the routing peer: a network resource policy, evaluated in the forward chain, lets a client reach a web app behind the routing peer on TCP 443. To the routing peer: a peer-to-peer policy, evaluated in the input chain, lets a client reach SSH on the routing peer's NetBird IP on TCP 22. +

+ If users need to reach both the resources behind a routing peer **and** services running on the routing peer itself (Pi-hole, monitoring, jump-host SSH), you need one policy of each kind. **Example.** A routing peer fronts the `10.10.0.0/16` office network and also runs Grafana on TCP/3000 plus SSH on TCP/22. To let the `Engineers` group reach both, create two policies: diff --git a/src/pages/manage/networks/index.mdx b/src/pages/manage/networks/index.mdx index eef84250..4ec8f9c3 100644 --- a/src/pages/manage/networks/index.mdx +++ b/src/pages/manage/networks/index.mdx @@ -21,10 +21,10 @@ Throughout this page, a capitalized **Network** means the configuration object y If you remember one thing, remember this: **a NetBird Network is a map of part of your infrastructure, drawn onto your overlay network.** The **resources** are the destinations, the **access policies** are the gatekeepers that decide who may pass, and the **routing peers** are the roads that carry traffic to each destination.

- A NetBird client reaching resources on a private network through a routing peer + Three NetBird peers on the overlay network connect through a WireGuard tunnel to a routing peer in a private network. Access policies on the routing peer allow a database on TCP 5432, a web app on TCP 443, and an RDP workstation on TCP/UDP 3389; a printer with no policy is unreachable.

-*Left to right: a peer in a source group clears an access policy, reaches the routing peer, and connects to a resource behind it.* +*Left to right: peers reach the routing peer through a WireGuard tunnel, and access policies on the routing peer decide which resources behind it each peer can reach. The printer has no policy, so nothing reaches it.* ## The four building blocks @@ -90,7 +90,7 @@ A network resource policy grants access to the network **behind** the routing pe Here is the whole path, end to end: 1. Your client encrypts the packet and sends it through the tunnel to the routing peer. -2. The routing peer forwards it onto the local network toward the resource. +2. The routing peer decrypts the packet and forwards it onto the local network toward the resource. 3. By default the routing peer **masquerades** the traffic, rewriting the source to its own LAN IP, so the destination network needs no awareness of NetBird. 4. Replies follow the reverse path. The connection is stateful, so return traffic needs no extra policy. @@ -100,13 +100,9 @@ One subtlety causes most policy mistakes. A network resource policy permits traf If a resource stays unreachable even though the policy looks right, [Troubleshooting resource connectivity](/help/troubleshooting-resource-connectivity) helps you find which layer is dropping the traffic. -``` -THROUGH the routing peer → forward chain → network resource policy - your client ──tunnel──► routing peer ──────► resource behind it - -TO the routing peer → input chain → peer-to-peer policy - your client ──tunnel──► routing peer (its own SSH, dashboard, …) -``` +

+ Two paths compared. Through the routing peer: a network resource policy, evaluated in the forward chain, lets a client reach a web app behind the routing peer on TCP 443. To the routing peer: a peer-to-peer policy, evaluated in the input chain, lets a client reach SSH on the routing peer's NetBird IP on TCP 22. +

For the full kernel-level traffic walkthrough, host requirements (IP forwarding, container capabilities), masquerade internals, and the forward-vs-input-chain rule in depth, see [How Routing Peers Work](/manage/networks/how-routing-peers-work). @@ -165,7 +161,7 @@ This single policy grants the `Development` group access to **every** resource i Before you depend on a Network in production, work through these: -- **High availability.** Add more than one routing peer to the same Network for redundancy. Added individually, each peer gets its own metric: a lower metric is the primary and a higher one the failover, while equal metrics balance traffic by latency. Added as a group, the peers share one metric, so they balance by latency only and can't act as primary and failover. Keep highly available peers in different failure domains. See [High availability](/manage/networks/how-routing-peers-work#high-availability). +- **High availability.** Add more than one routing peer to the same Network for redundancy. Added individually, each routing peer gets its own metric: the lowest is the primary and higher ones are failovers. With equal metrics, each peer picks one when it connects, by connection type and then latency; this is not load balancing. Added as a group, the routing peers share one metric, so they can't act as primary and failover. Keep highly available peers in different failure domains. See [High availability](/manage/networks/how-routing-peers-work#high-availability). - **Monitoring.** Enable the **Routing Peer Disconnected** event in [Notifications](/manage/settings/notifications) to get alerted by email, webhook, or Slack when a routing peer goes offline. - **Masquerade.** On by default and the simplest option. Turn it off only when you need source-IP visibility, and only on Linux routing peers, as that's the only platform where it can be disabled. Disabling it requires a return route in the destination network, and makes high availability something you have to arrange rather than something you get. See [Masquerade](/manage/networks/masquerade). - **Internal DNS.** Domain resources resolve on the routing peer, so it must be able to resolve the name. If it already can, nothing more is needed; if it can't, distribute a nameserver to the routing peer's group. See [Internal DNS Servers](/manage/dns/internal-dns-servers). diff --git a/src/pages/manage/peers/approve-peers.mdx b/src/pages/manage/peers/approve-peers.mdx index dd583403..772e36c5 100644 --- a/src/pages/manage/peers/approve-peers.mdx +++ b/src/pages/manage/peers/approve-peers.mdx @@ -8,7 +8,7 @@ When enabled, devices connect to the management service without network access t Administrators then can assess whether the peer is eligible to join the network. - This feature is only available in the [NetBird cloud](https://app.netbird.io/) version. + This feature is available in [NetBird Cloud](https://app.netbird.io/) on the [Business plan](https://netbird.io/pricing) and in self-hosted deployments with an [Enterprise Commercial License](/selfhosted/enterprise). The open source Community Edition does not include it. ## Related Video Content diff --git a/src/pages/manage/peers/lazy-connection.mdx b/src/pages/manage/peers/lazy-connection.mdx index 0555f745..513346e5 100644 --- a/src/pages/manage/peers/lazy-connection.mdx +++ b/src/pages/manage/peers/lazy-connection.mdx @@ -22,12 +22,14 @@ When lazy connections are enabled, the client: The default inactivity threshold is `15m`. Change it with `NB_LAZY_CONN_INACTIVITY_THRESHOLD`, using a [Go duration](https://pkg.go.dev/time#ParseDuration) such as `30m` or `1h`. The minimum is `1m`; shorter or invalid values fall back to the default. - The first request to an idle peer can take slightly longer while NetBird establishes the connection. + The first request to an idle peer can take slightly longer while NetBird establishes the connection. Since NetBird v0.74.0, the packet that triggers the connection is delivered once the connection is up instead of being dropped. ### DNS warm-up -When the local NetBird resolver returns an A or AAAA record for an idle peer, it starts waking that peer before the application sends its first packet. The resolver waits for up to two seconds by default for one matching peer to connect, reducing the chance that the application's first request races the lazy connection. +When the local NetBird resolver answers with two or more A or AAAA records that point at idle peers, it wakes those peers before the application sends its first packet. The answer waits up to two seconds by default for one of them to connect, so the first request does not race the connection. + +Warm-up only applies to names in [custom DNS zones](/manage/dns/custom-zones) and in the zones NetBird creates for [private services](/manage/reverse-proxy/authentication#net-bird-only-access-private-services). A name with a single record does not trigger it, and neither does a peer's own name, such as `peer-a.netbird.cloud`. Peer names are excluded on purpose: otherwise every lookup would wake idle connections across the network. Set `NB_DNS_LAZY_WARMUP_TIMEOUT` on the daemon to change this per-query wait. The value must be a positive Go duration, for example `5s`. Invalid, zero, or negative values fall back to the `2s` default. @@ -55,14 +57,36 @@ sudo netbird service reconfigure --service-env NB_LAZY_CONN=on sudo netbird service reconfigure --service-env NB_LAZY_CONN=off ``` -`on` and `off` override Management in both directions; boolean values such as `true`/`false` or `1`/`0` are also accepted. Leave the variable unset to follow the Management setting. See [Client Environment Variables](/client/environment-variables#ice-and-connectivity) for service configuration details. +`on` and `off` override Management in both directions; boolean values such as `true`/`false` or `1`/`0` are also accepted. Leave the variable unset to follow the Management setting. Any other value logs a warning and is treated as unset. See [Client Environment Variables](/client/environment-variables#ice-and-connectivity) for service configuration details. On MDM-managed clients, the boolean `lazyConnection` policy key provides the same local override: `true` forces lazy connections on, `false` forces them off, and an absent key defers to Management. If both are configured, `NB_LAZY_CONN` takes precedence over MDM. +To check a peer, run `netbird status` on it. The `Lazy connection` line shows whether lazy connections are on for that peer, with any override applied. + - The deprecated `NB_ENABLE_EXPERIMENTAL_LAZY_CONN` variable is no longer used. The deprecated `netbird up --enable-lazy-connection` flag is also inert in v0.75. Use the Management setting, `NB_LAZY_CONN`, or the MDM policy instead. + NetBird v0.74.1 removed the `Enable Lazy Connections` checkbox from the desktop client's `Settings` menu and made the `netbird up --enable-lazy-connection` flag inert; the flag now only prints a deprecation warning. Both could turn lazy connections on, but neither could turn them off once Management had enabled them. `NB_LAZY_CONN` replaces both. The deprecated `NB_ENABLE_EXPERIMENTAL_LAZY_CONN` variable is no longer used. +### Each peer decides for its own connections + +Whether it comes from Management or from `NB_LAZY_CONN`, the setting only controls the peer it applies to. A peer with lazy connections off connects to every peer your policies let it reach, and a lazy peer accepts that connection when asked. So turning lazy connections on for a routing peer does not make the devices that use it lazy. + +An unused connection only stays closed when both of its ends are lazy. If only one end is lazy, that end closes the connection after the inactivity threshold, the other end reopens it shortly afterwards, and the cycle repeats. This is also what happens between every lazy peer and a peer you opt out with `NB_LAZY_CONN=off` while the account has lazy connections on. Keep two limits in mind: + +- For a few seconds after each close, the non-lazy end still treats the connection as up, and traffic it sends in that window is dropped. +- A lazy peer running kernel WireGuard does not detect idle connections itself, so its connection to a non-lazy peer stays up instead of cycling. + +### Try lazy connections on a few peers + +To try lazy connections before turning them on for the whole account, leave the Management setting off and set `NB_LAZY_CONN=on` on every peer in the test, for example two peers that talk to each other: + +```bash +# On both peers +sudo netbird service reconfigure --service-env NB_LAZY_CONN=on +``` + +The connection between the two then opens on demand and closes after the inactivity threshold. Their connections to other peers keep closing and reopening, as described above, because the other end is not lazy. When the test is over, turn the Management setting on or remove `NB_LAZY_CONN` from the test peers. + ## Get started
diff --git a/src/pages/manage/peers/mdm-deployment/intune-netbird-integration.mdx b/src/pages/manage/peers/mdm-deployment/intune-netbird-integration.mdx index 89d8b6a0..30717ac9 100644 --- a/src/pages/manage/peers/mdm-deployment/intune-netbird-integration.mdx +++ b/src/pages/manage/peers/mdm-deployment/intune-netbird-integration.mdx @@ -14,6 +14,8 @@ This division of security responsibilities creates a comprehensive zero trust im In this hands-on tutorial, you'll learn how to deploy NetBird with Intune to grant tailored access permissions for different teams. +This page installs the NetBird client. To enforce its settings once it is installed, such as pinning the management server or making settings read-only, follow [Enforce NetBird Settings on Windows](/manage/peers/mdm-deployment/windows-mdm-policy), which uses an Intune configuration profile. + ## Prerequisites Before beginning this tutorial, ensure you have the following prerequisites in place: @@ -185,7 +187,7 @@ Click `Next` to configure NetBird with the following details: - **Description**: NetBird - **Publisher**: NetBird - **App install context**: Device -- **Ignore app version**: No (This ensures updates will be applied when available) +- **Ignore app version**: No if Intune owns updates (so Intune applies new MSI versions). Yes if NetBird owns updates via [Automatic Updates](/manage/peers/auto-update), so Intune only checks that NetBird is installed. Pick one owner: see [Roll Out with MDM](/use-cases/deployment/mdm-rollout#step-5-pick-one-owner-for-updates). - **Command-line arguments**: Leave empty. The v0.75 MSI has no `AUTOSTART` property; manage launch at login with the `disableAutostart` MDM setting instead. - **Category**: Select any category that fits your needs (optional) - **Show this as a featured app in the Company Portal**: Yes @@ -219,4 +221,8 @@ To verify that NetBird was added to Intune, navigate to `Home > Apps | Windows` While each platform has slightly different configuration options, adding NetBird and assigning it to groups follows the same pattern across Intune. For more information, refer to [Intune app management](https://learn.microsoft.com/en-us/intune/intune-service/apps/app-management). -With NetBird successfully deployed through Intune, your organization has the foundation for implementing a comprehensive zero trust access model that verifies user identity and device compliance before granting network access. \ No newline at end of file +With NetBird successfully deployed through Intune, your organization has the foundation for implementing a comprehensive zero trust access model that verifies user identity and device compliance before granting network access. + +## Next: enforce the client's settings + +With the client installed, use an Intune configuration profile to pin its settings, such as the management server, and to stop users changing them: see [Enforce NetBird Settings on Windows](/manage/peers/mdm-deployment/windows-mdm-policy). diff --git a/src/pages/manage/peers/mdm-deployment/ios-mdm-policy.mdx b/src/pages/manage/peers/mdm-deployment/ios-mdm-policy.mdx new file mode 100644 index 00000000..966f55a1 --- /dev/null +++ b/src/pages/manage/peers/mdm-deployment/ios-mdm-policy.mdx @@ -0,0 +1,96 @@ +import { Note, Warning } from "@/components/mdx"; + +export const description = + "Deliver NetBird MDM policy to iOS, iPadOS, and tvOS devices with Managed App Configuration: the payload, where to put it in Intune, Jamf Pro, and other MDMs, when a change reaches the tunnel, and what each key does in the app."; + +# Enforce NetBird Settings on iOS + +On iOS there is no file or registry for policy. Apple's channel for configuring a managed app is **Managed App Configuration**: your MDM sends a dictionary along with the app, and iOS hands it to that app only. The NetBird app reads it from its own preferences, so the payload must target the NetBird app's bundle id: `io.netbird.app` on iOS and iPadOS, `io.netbird.app.tv` on tvOS. + +What each key does, and how the client applies and locks a policy, is on the [MDM Integration](/client/mdm-integration) page. Read its [How the client applies a policy](/client/mdm-integration#how-the-client-applies-a-policy) section first if you have not. + +The examples use [the user device baseline](/client/mdm-integration#the-user-device-baseline): a self-hosted server at `https://netbird.example.com:443`, read-only settings, and no extra profiles. + + +**The app must be managed by the MDM that sends the configuration.** It does not have to be installed by it: an MDM can take over management of a copy the user installed from the App Store, and on an unsupervised device the user has to accept that change. Until the app is managed it receives no app configuration, and NetBird runs with no policy on that device. + + +## Push the payload + +The payload is a plain property-list dictionary, with the same key names as on desktop. The user device baseline is: + +```xml + + managementURL + https://netbird.example.com:443 + disableUpdateSettings + + disableProfiles + + +``` + +Start from netbird-ios-appconfig.plist and delete the keys you do not want to pin: a key that is present is pinned, even when its value is `false`. Then add it to the NetBird app's assignment in your MDM: + +- **Microsoft Intune**: **Apps → App configuration policies → Add → Managed devices**, platform **iOS/iPadOS**, pick the NetBird app, then set **Configuration settings format** to **Enter XML data** and paste the dictionary. +- **Jamf Pro**: **Devices → Mobile Device Apps → NetBird → App Configuration**, and paste the dictionary. +- **Kandji**, **Mosyle**, **Workspace ONE**, **JumpCloud**: the same field exists on the managed-app assignment, usually labelled *App Configuration* or *Application Configuration*. + +Booleans can be written as `` and ``, or as `1` and `0`. Integers and strings behave as on desktop. + +## When a policy takes effect + +Every key that applies to iOS is enforced: the keys that reshape the app, and the keys the client applies to the connection itself. When a change lands depends on one detail of how iOS works. + +iOS delivers the configuration to the NetBird app. The tunnel, though, runs in a Network Extension: a separate process the system starts when the tunnel comes up and stops when it goes down, and which Apple's app-configuration channel does not reach. The app therefore passes the configuration on to the extension whenever the app is running: when it becomes active, when a settings screen appears, and on a timer about every 30 seconds. When the policy has changed, the tunnel restarts on its own and the user is told their IT policy was applied. + + +**A policy lands while the app is open.** With NetBird in the foreground, a change pushed from your MDM reaches the tunnel within about 30 seconds, or at once when the user switches back to the app. Nothing has to be restarted by hand. + +**While the app is closed or in the background, iOS suspends it**, and a policy pushed or withdrawn in that time reaches the tunnel only the next time the app is opened. This matters most with VPN On Demand or the widget, where the tunnel can come up without the app ever appearing: it then runs on the last policy the app saw. Plan rollouts accordingly, and treat "applied" as "applied once the user next opens the app". + + + +**On tvOS, only the app is enforced, not the tunnel.** The hand-off described above relies on a shared app group, which does not work between the tvOS app and its extension, so the tvOS tunnel runs without the policy. Keys that lock or hide controls behave as described below; the keys the client applies to the connection (`managementURL`, `preSharedKey`, `rosenpassEnabled`, `rosenpassPermissive`, `blockInbound`, `wireguardPort`, `lazyConnection`) do not reach the tunnel on tvOS yet. + + +## What each key does in the app + +| Key | Effect in the iOS and tvOS app | +| --- | --- | +| `managementURL` | Hides **Settings → Connection** and the server step of onboarding. The profile-creation sheet shows and uses the pinned URL. | +| `preSharedKey` | The key row reads *Configured* and is locked: no Save, no Remove. The value itself never reaches the app's interface. | +| `rosenpassEnabled`, `rosenpassPermissive` | Locks its own switch and shows the pinned value. | +| `disableClientRoutes` | Hides the exit-node selector. The resource list explains why, instead of listing resources. | +| `disableAutoConnect` | VPN On Demand becomes read-only. | +| `disableProfiles` | Hides the Profiles section: no creating, switching, or removing profiles. | +| `disableNetworks` | Removes the Resources tab. | +| `disableAdvancedView` | Hides Advanced (on tvOS, the whole section). | +| `disableUpdateSettings` | Read-only mode: nothing is hidden, and every configuration control is locked. | + +A second group of keys has no control to lock, because the client applies them straight to the connection: `blockInbound`, `wireguardPort`, `lazyConnection`, `allowRemoteJobs`, and `debugBundleUploadURL` (when a remote job produces a bundle). On iOS they are enforced all the same, with nothing on screen to mark: a device whose policy sets `allowRemoteJobs: false`, for example, refuses remote jobs. On tvOS none of them reach the tunnel, for the reason in the warning above. + +The remaining keys do not apply to iOS and are ignored, so they are safe in a payload shared with desktop devices: `allowServerSSH` (there is no SSH server on iOS), `disableServerRoutes` (an iOS device does not route for other peers), `disableAutostart` (desktop app only), `disableMetricsCollection`, `enableLocalMetrics` and `localMetricsAddress` (the local endpoint is not reachable on iOS), and `splitTunnelMode` and `splitTunnelApps`. + +## Verifying on a device + +There is no CLI on iOS. A pinned setting shows a lock and a **Managed by your organization** note in the app, and a gated section disappears altogether. For the user device baseline, the Profiles section is gone and every configuration control is locked. + +That confirms the app received the policy. To confirm it reached the *tunnel*, change the policy while the tunnel is connected and bring NetBird to the foreground: the tunnel restarts and the device shows "NetBird configuration was updated by your IT policy". That notice comes from the extension, so seeing it means the tunnel applied the new policy. + +## Troubleshooting + +**Nothing is locked in the app.** +The app did not receive the configuration. Check that NetBird is a managed app on the device (installed by your MDM, or taken over by it), that the app-configuration policy is assigned to the same device group as the app, and that it targets the bundle id `io.netbird.app` (`io.netbird.app.tv` on tvOS). + +**The app shows the policy, but the tunnel still behaves as before.** +The app passes the policy to the tunnel only while it is running. Open NetBird and leave it in the foreground for half a minute; the tunnel restarts and reports that the policy was applied. On tvOS the tunnel does not receive the policy at all; see [When a policy takes effect](#when-a-policy-takes-effect). + +For problems that look the same on every platform, see [Troubleshooting](/client/mdm-integration#troubleshooting) on the MDM Integration page. + +## Recap + +- iOS delivers the policy as Managed App Configuration to the managed NetBird app, bundle id `io.netbird.app` (tvOS: `io.netbird.app.tv`). +- The user device baseline is the same three keys as on desktop: `managementURL`, `disableUpdateSettings`, and `disableProfiles`. +- A change reaches the tunnel while the app is open, within about 30 seconds; on tvOS it reaches the app only. +- Confirm in the app: pinned settings show **Managed by your organization**. diff --git a/src/pages/manage/peers/mdm-deployment/macos-mdm-policy.mdx b/src/pages/manage/peers/mdm-deployment/macos-mdm-policy.mdx new file mode 100644 index 00000000..07b5784e --- /dev/null +++ b/src/pages/manage/peers/mdm-deployment/macos-mdm-policy.mdx @@ -0,0 +1,127 @@ +import { Note, Warning } from "@/components/mdx"; + +export const description = + "Deliver NetBird MDM policy to macOS devices: a configuration profile for the io.netbird.client preference domain, with steps for Jamf Pro, Kandji, Mosyle, Intune, Workspace ONE, and JumpCloud, and how to confirm it arrived."; + +# Enforce NetBird Settings on macOS + +On macOS, the NetBird client reads its MDM policy from `/Library/Managed Preferences/io.netbird.client.plist`. Normally you do not write that file yourself: macOS writes it when your MDM installs a configuration profile with managed preferences for the preference domain (bundle id) `io.netbird.client`. This page shows how to build that profile, deliver it with the common MDMs, and confirm it arrived. + +What each key does, and how the client applies and locks a policy, is on the [MDM Integration](/client/mdm-integration) page. Read its [How the client applies a policy](/client/mdm-integration#how-the-client-applies-a-policy) section first if you have not. + +The examples use [the user device baseline](/client/mdm-integration#the-user-device-baseline): a self-hosted server at `https://netbird.example.com:443`, read-only settings, and no extra profiles. As managed preferences, the whole policy is: + +```xml +managementURL +https://netbird.example.com:443 +disableUpdateSettings + +disableProfiles + +``` + + +**macOS clears `/Library/Managed Preferences/` at every boot on a Mac that is not enrolled in an MDM.** Writing the file by hand, with `defaults write` or a script, works for a quick test, but the policy is gone after the next reboot. Use a configuration profile delivered by an MDM for anything beyond a test. + + +## Templates + +Download the template that matches your MDM: + +- netbird-macos.mobileconfig: a complete configuration profile. Use it with Kandji, Mosyle, Intune, Workspace ONE, JumpCloud, and Apple Configurator. +- io.netbird.client.plist: the bare managed-preferences dictionary, without the profile wrapper. Use it with Jamf Pro's Application & Custom Settings payload. +- netbird-macos.sh: a script that writes the policy file directly, for JumpCloud fleets that are not MDM-enrolled. + +Each template lists every key. Delete the keys you do not want to pin: a key that is present is pinned, even when its value is `false`. + +Write booleans as `` or ``. On macOS, a boolean written as `1` is listed as managed and locked, but never applied, so the setting is frozen at whatever the Mac had. + +## Build the configuration profile + +1. Open `netbird-macos.mobileconfig` in a text editor, or in a profile editor such as [iMazing Profile Editor](https://imazing.com/profile-editor) or [ProfileCreator](https://github.com/ProfileCreator/ProfileCreator). +2. Inside the `mcx_preference_settings` dictionary, keep only the keys you want to pin, with your values. For the user device baseline, that is the three keys shown at the top of this page. Keep `io.netbird.client` as the preference domain. +3. Replace each placeholder `PayloadUUID` with a freshly generated UUID (run `uuidgen`), so every deployment has unique identifiers. + +Profiles delivered through an MDM are signed by the MDM, so there is nothing to sign by hand for a managed rollout. A profile you hand out some other way, with Apple Configurator or as a file a user double-clicks, installs unsigned, and recent macOS releases ask the user for an extra confirmation. To avoid that, sign the profile as a CMS message: + +```bash +security cms -S -N "" -i netbird-macos.mobileconfig -o signed.mobileconfig +``` + +Use an identity the target Macs already trust. Configuration profiles are **not** signed with `productsign`, which is for installer packages, nor with an Apple Developer ID certificate. + +## Deliver it with your MDM + +- **Jamf Pro**: **Computers → Configuration Profiles → New → Application & Custom Settings → External Applications → Upload File (Plist file)**, with the preference domain `io.netbird.client`, and upload the edited `io.netbird.client.plist`. Jamf can also take the whole `.mobileconfig` through **Computers → Configuration Profiles → Upload**, but it may drop payload keys it does not recognize from an unsigned profile, so sign the profile first if you use that route. +- **Kandji**: add a **Custom Profile** library item and upload the `.mobileconfig`. +- **Mosyle**: **Management → Management Profiles → Certificates / Custom Profiles → Add new profile**, and upload the `.mobileconfig`. +- **Microsoft Intune**: **Devices → Configuration → Create profile → macOS → Templates → Custom**, and upload the `.mobileconfig`. +- **Workspace ONE** and other MDMs: upload the `.mobileconfig` as a custom configuration profile and scope it to the target device group. +- **Apple Configurator 2**, for testing on a connected Mac without an MDM: drag the `.mobileconfig` onto the device and install it. + +### JumpCloud + +JumpCloud can deliver the policy two ways. Use the first if your Macs are MDM-enrolled with JumpCloud. + +#### MDM Custom Configuration Profile (MDM-enrolled Macs) + +1. In the JumpCloud admin console, open **Policy Management → Policies → +** and choose the **Mac** platform. +2. Pick the **MDM Custom Configuration Profile** policy template. +3. Under **Settings**, upload your edited `netbird-macos.mobileconfig` (see [Build the configuration profile](#build-the-configuration-profile)). +4. Bind the policy to the target device group and save. + +JumpCloud delivers the profile through MDM. Removing the policy in JumpCloud removes the file at the next sync, which unpins the settings on the client. + +#### Shell command (Macs not enrolled in MDM) + +For JumpCloud-managed Macs that are not MDM-enrolled, use netbird-macos.sh: + +1. Edit the `### POLICY VALUES ###` block at the top of the script: set the keys you want to pin, and leave the rest at `$NULL`. +2. In the JumpCloud admin console, go to **Device Management → Commands → +**. Set **Type** to **Mac, Shell** and **Run as** to **root**. +3. Paste the edited script into the command body. +4. Bind the command to the target device group and run it. + +The script writes `/Library/Managed Preferences/io.netbird.client.plist`, owned by `root:wheel` with mode `644`, and restarts the NetBird daemon so the policy applies at once. On a Mac that is not MDM-enrolled, macOS deletes the file at the next reboot, so run the command again after each reboot, or enroll the Macs and use the configuration profile. + + +**Every user on the Mac can read the policy file.** From a configuration profile, macOS writes it as a binary plist owned by `root:wheel` with mode `644`, and the shell script above uses the same owner and mode. It is readable by all users, so a `preSharedKey` or `debugBundleUploadURL` in it is visible to anyone who can sign in. Only root can change it. The client refuses to read a policy file that any user can write to, and then enforces no policy at all, so never loosen the file's permissions. + + +## Verify + +On a target Mac, check that macOS wrote the policy file: + +```bash +sudo defaults read "/Library/Managed Preferences/io.netbird.client" +``` + +The output should list the keys from your profile. Then check what the client is enforcing: + +```bash +netbird debug config +``` + +The `mDMManagedFields` array in the output lists every key in the policy the client reads. For the user device baseline, it contains `disableProfiles`, `disableUpdateSettings`, and `managementURL`. The client log at `/var/log/netbird/client.log` has an `MDM enrolled with N managed key(s): [...]` line on every reload, and an `MDM policy changed: added=[...]` line once the daemon has applied a change, within a minute. See [Verifying enforcement](/client/mdm-integration#verifying-enforcement) for more. + +## Troubleshooting + +**The policy file does not exist.** +The profile was not installed, or it targets the wrong preference domain. On the Mac, open **System Settings → General → Device Management** and confirm the profile is listed, then confirm the domain in the profile is exactly `io.netbird.client`. + +**The policy file disappears after a reboot.** +The Mac is not MDM-enrolled, so macOS clears `/Library/Managed Preferences/` at boot. Enroll it and deliver the policy as a configuration profile. + +**The file exists, but `mDMManagedFields` is empty.** +Check the file's permissions with `ls -l "/Library/Managed Preferences/io.netbird.client.plist"`. If any user can write to it (a `w` in the last three permission characters), the client ignores the whole policy, and with default logging it says nothing about it. Reset the permissions with `sudo chown root:wheel` and `sudo chmod 644` on the file. If the permissions are fine, look in the client log for `MDM ignoring unknown plist key`: a key name does not match any key in the [reference](/client/mdm-integration#policy-keys-reference). + +**A key is listed in `mDMManagedFields`, but the setting did not change.** +Check the value's type. A boolean must be `` or ``; written as an ``, it is listed as managed and locked, but never applied. The client logs no warning for it. + +For problems that look the same on every platform, see [Troubleshooting](/client/mdm-integration#troubleshooting) on the MDM Integration page. + +## Recap + +- The client reads `/Library/Managed Preferences/io.netbird.client.plist`, which macOS writes from a configuration profile for `io.netbird.client`. +- The user device baseline is three keys: `managementURL`, `disableUpdateSettings`, and `disableProfiles`. +- Upload `netbird-macos.mobileconfig` to most MDMs, JumpCloud included; Jamf Pro's Application & Custom Settings takes the bare `io.netbird.client.plist`. +- Confirm with `sudo defaults read`, then `netbird debug config`. diff --git a/src/pages/manage/peers/mdm-deployment/windows-mdm-policy.mdx b/src/pages/manage/peers/mdm-deployment/windows-mdm-policy.mdx new file mode 100644 index 00000000..ef9de1ae --- /dev/null +++ b/src/pages/manage/peers/mdm-deployment/windows-mdm-policy.mdx @@ -0,0 +1,171 @@ +import { Note, Warning } from "@/components/mdx"; + +export const description = + "Enforce NetBird client settings on Windows with Microsoft Intune: import the NetBird administrative templates, build and assign a configuration profile, and verify it. Plus a registry reference for Group Policy, JumpCloud, and .reg files."; + +# Enforce NetBird Settings on Windows + +On Windows, you enforce NetBird settings with Microsoft Intune: import NetBird's administrative templates once, build a configuration profile from them, and assign it to your devices. The client then applies the settings and locks them. Behind the scenes, the profile writes values to one registry key, `HKLM\Software\Policies\NetBird`, which is what the client reads. The [registry reference](#registry-reference) at the end of this page covers that key directly, for Group Policy, JumpCloud, or a `.reg` file. + +This page enforces settings on a client that is already installed. To install the client with Intune, follow [Deploying NetBird with Intune](/manage/peers/mdm-deployment/intune-netbird-integration) first. What each setting does, and how the client applies and locks a policy, is on the [MDM Integration](/client/mdm-integration) page. + +The examples use [the user device baseline](/client/mdm-integration#the-user-device-baseline): a self-hosted server at `https://netbird.example.com:443`, read-only settings, and no extra profiles. In Intune that is three policies: **Management URL**, **Disable update settings**, and **Disable profiles**. + +## Before you start + +- An Intune admin account with at least the **Policy and Profile Manager** role. +- Windows devices enrolled in Intune, with the NetBird client installed, in a device group you can assign to. +- The NetBird templates: netbird.admx and netbird.adml. They cover every setting, have no dependency on other templates, and stay within Intune's limits for imported templates. + +## Enforce settings with Intune + +### Step 1: Import the NetBird templates + +You do this once per tenant. + +1. In the Intune admin center, go to **Devices → Manage devices → Configuration**, open the **Import ADMX** tab, and select **Import**. +2. Upload `netbird.admx` as the ADMX file and `netbird.adml` as the ADML file for the default language (`en-us`, the only language Intune accepts). +3. Select **Next**, then **Create**. +4. Select **Refresh** until the template's status is **Available**. + + +**Updating the templates later.** Intune refuses to import a template whose settings are already imported. When NetBird ships updated templates, for example with new settings, delete the configuration profiles that use the NetBird template, delete the old template on the **Import ADMX** tab, and then import the new files. Note your profile settings before you delete them. + + +### Step 2: Create the configuration profile + +1. Go to **Devices → Manage devices → Configuration → Create → New policy**. +2. Set **Platform** to **Windows 10 and later** and **Profile type** to **Templates**, then select **Imported Administrative templates (Preview)** and **Create**. +3. Give the profile a name you can find later, such as `NetBird: user device baseline`. +4. Under **Configuration settings**, open **NetBird**. Each policy is named after its setting in plain words. For the user device baseline: + - **Management URL**: **Enabled**, value `https://netbird.example.com:443`. + - **Disable update settings**: **Enabled**. + - **Disable profiles**: **Enabled**. + + Leave every other policy **Not configured**. A policy set to **Disabled** is not the same as **Not configured**: it pins the setting to `false`, out of the user's reach. See [How the client applies a policy](/client/mdm-integration#how-the-client-applies-a-policy). +5. Under **Assignments**, assign the profile to a **device group**. NetBird's policies are machine settings, so a device group is what you want. To build the profile for each device role, use [Recommended policies](/client/mdm-integration#recommended-policies), and keep routing peers out of any group that gets **Block inbound** or **Disable server routes**. +6. Select **Create**. + +### Step 3: Sync and verify + +Devices pick up the profile at their next Intune check-in. To speed up a test device, open **Settings → Accounts → Access work or school**, select the work account, select **Info**, and then **Sync**. The NetBird client applies the policy within a minute of it arriving, and restarts its connection to do so: expect a brief interruption. + +In the Intune admin center, open the profile and check its device and user check-in status. On the device itself: + +``` +reg query HKLM\Software\Policies\NetBird +netbird debug config +``` + +`reg query` shows the values Intune wrote. In `netbird debug config`, the `mDMManagedFields` array lists every setting in the policy the client reads; for the user device baseline it contains `disableProfiles`, `disableUpdateSettings`, and `managementURL`. The client log at `C:\ProgramData\Netbird\client.log` has an `MDM policy changed: added=[...]` line once the client has applied the change. See [Verifying enforcement](/client/mdm-integration#verifying-enforcement) for more. + +### If you cannot import templates: custom OMA-URI + +Imported templates are the simpler path; prefer them. Without them, a custom OMA-URI profile (**Templates → Custom**) takes two kinds of setting, following Microsoft's [ADMX-backed policy ingestion](https://learn.microsoft.com/en-us/windows/client-management/win32-and-centennial-app-policy-configuration): + +1. **Ingest the template**: OMA-URI `./Device/Vendor/MSFT/Policy/ConfigOperations/ADMXInstall/NetBird/Policy/NetBirdAdmx`, data type **String**, value: the full contents of `netbird.admx`. This only makes the policies known to the device; it sets nothing. +2. **Set each policy**: OMA-URI `./Device/Vendor/MSFT/Policy/Config/NetBird~Policy~NetBird/`, where `` is the policy's `name` in `netbird.admx`, with data type **String**. The value depends on what you want to pin: + - **On, or a value**: ``, plus a `` element for a policy that carries a value, with the element `id` from the same policy in `netbird.admx`. + - **Off**: ``. For the on/off policies the template writes `0`, which pins the setting off. `` always writes `1`, so never use it to turn a setting off. + + For the user device baseline's server, the **Management URL** policy is `.../NetBird~Policy~NetBird/ManagementURL` with the value ``. + + To stop pinning a setting, it has to go back to **Not configured**, not ``. Removing an OMA-URI from a custom profile is not guaranteed to clear the value on the device, so check with `reg query HKLM\Software\Policies\NetBird` afterwards. + +The URIs above follow Microsoft's naming rules for this template; they have not been tested in an Intune tenant. + +### Intune troubleshooting + +**The template import fails.** +If the error says the settings already exist, an earlier version of the NetBird template is imported: see **Updating the templates later** above. Otherwise, check that you uploaded `netbird.adml` with `netbird.admx`, as the `en-us` language file. + +**The profile shows an error or never reaches the device.** +Check the profile's per-device status in the admin center, and that the device is in the assigned device group. On the device, trigger a sync from **Settings → Accounts → Access work or school → Info → Sync**, then run `reg query HKLM\Software\Policies\NetBird`. If the key is empty, the profile has not been applied yet. + +**The values are in the registry, but the setting did not change.** +The client applies a change at its next reload, within a minute. If `mDMManagedFields` still does not list the setting, or lists it without the setting changing, see [Registry troubleshooting](#registry-troubleshooting). + +For problems that look the same on every platform, see [Troubleshooting](/client/mdm-integration#troubleshooting) on the MDM Integration page. + +## Registry reference + +Whatever writes it, the client reads its policy from `HKLM\Software\Policies\NetBird`: an Intune profile, a Group Policy object, a script, or `reg import` all end in the same key. This section is for working with that key directly, and for checking what Intune wrote. + +### Value types + +Each setting needs a value of the right registry type. Value names are matched without regard to case. + +| Registry type | Value names | +| --- | --- | +| `REG_SZ` (string) | `ManagementURL`, `PreSharedKey`, `DebugBundleUploadURL`, `LocalMetricsAddress`, `SplitTunnelMode`, `SplitTunnelApps` | +| `REG_DWORD`, `0` or `1` (boolean) | `AllowRemoteJobs`, `AllowServerSSH`, `BlockInbound`, `DisableAdvancedView`, `DisableAutoConnect`, `DisableAutostart`, `DisableClientRoutes`, `DisableMetricsCollection`, `DisableNetworks`, `DisableProfiles`, `DisableServerRoutes`, `DisableUpdateSettings`, `EnableLocalMetrics`, `LazyConnection`, `RosenpassEnabled`, `RosenpassPermissive` | +| `REG_DWORD` (integer) | `WireguardPort`, for example `0x0000ca6c` for `51820` | + +A boolean can also be a `REG_SZ` holding `true`/`false`, `1`/`0`, `yes`/`no`, or `on`/`off`. A string setting stored with any type other than `REG_SZ` or `REG_EXPAND_SZ` is ignored: a `ManagementURL` stored as a `REG_DWORD`, for example, has no effect. The client reads `REG_SZ`, `REG_EXPAND_SZ`, `REG_DWORD`, `REG_QWORD`, and `REG_MULTI_SZ` values. Any other type, such as `REG_BINARY`, is skipped with a warning in its log and is not listed in `mDMManagedFields`. + + +**Standard users can read these values.** By default, the NetBird policy key inherits read access for Authenticated Users, so a `PreSharedKey` or `DebugBundleUploadURL` stored there is visible to them. Only administrators can change the values. An administrator on the device can always edit or delete the key, so give users standard accounts. + + +The user device baseline as raw registry values: + +``` +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\Software\Policies\NetBird] +"ManagementURL"="https://netbird.example.com:443" +"DisableUpdateSettings"=dword:00000001 +"DisableProfiles"=dword:00000001 +``` + +### Group Policy + +For domain-joined devices, follow [Deploying NetBird with Group Policy (GPO)](/manage/peers/mdm-deployment/windows-gpo-deployment). It walks through the Central Store, creating the policy GPO, installing the MSI silently, and scoping, end to end. The same `netbird.admx` and `netbird.adml` templates appear under **Computer Configuration → Administrative Templates → NetBird**. + +To test the templates on a single machine with the local Group Policy editor: + +1. Copy `netbird.admx` to `C:\Windows\PolicyDefinitions\` and `netbird.adml` to `C:\Windows\PolicyDefinitions\en-US\`. +2. Open `gpedit.msc` and go to **Computer Configuration → Administrative Templates → NetBird**. +3. Open a policy, such as **Management URL**, set it to **Enabled**, enter the value, and click **OK**. +4. Run `gpupdate /force`. + +### A `.reg` file + +For a device without an MDM, or a quick test, carry the whole policy in one `.reg` file, such as the user device baseline above, and apply it from an elevated prompt with `reg import netbird-policy.reg`. To build one from a reference machine that already has the policy you want, export the key with `reg export "HKLM\Software\Policies\NetBird" netbird-policy.reg /y`. + +netbird-policy.reg is a sample that sets every setting, including `PreSharedKey` and `DisableClientRoutes`, which cut a laptop off from most of the network. Delete every line you do not mean to pin before you use it. + +`reg import` adds and overwrites values, but never removes them. To take a setting out of the policy, delete its value with `reg delete "HKLM\Software\Policies\NetBird" /v /f`. + +### JumpCloud + +NetBird ships a companion script for JumpCloud, netbird-policy.reg.ps1, that applies a `.reg` file as the complete policy for the device. + +1. In the JumpCloud admin console, go to **Device Management → Commands → +**. +2. Set **Type** to **Windows PowerShell** and **Run as** to **SYSTEM**. +3. Paste `netbird-policy.reg.ps1` into the command body, unchanged. +4. Attach your `.reg` file to the same command. JumpCloud copies attached files into the command's working directory before it runs the script. +5. Bind the command to the target device group and run it. + +The script deletes the existing `HKLM\Software\Policies\NetBird` key before it imports the `.reg` file, so the file is the **complete** policy for the device: any setting it does not contain is no longer pinned. To remove the whole policy, attach a `.reg` file that contains only the header line. + +### Registry troubleshooting + +**`reg query` shows no values.** +Nothing wrote them. For Group Policy, run `gpresult /h report.html` and look for errors on the NetBird GPO. For Intune, see [Intune troubleshooting](#intune-troubleshooting). + +**The values are in the registry, but `mDMManagedFields` does not list them.** +Look for `MDM ignoring unknown registry value` in the client log: the value name does not match a setting. Then look for `MDM ignoring unsupported registry value type`: the value has a registry type the client does not read. + +**A setting is listed in `mDMManagedFields`, but it did not change.** +The value has the wrong type for its setting, such as a `ManagementURL` stored as a `REG_DWORD`. Check it against [Value types](#value-types). + +**A setting stays pinned after you removed it from a `.reg` file.** +`reg import` does not delete values. Delete the value with `reg delete`, or use the [JumpCloud](#jump-cloud) script, which replaces the whole key. + +## Recap + +- Import `netbird.admx` and `netbird.adml` into Intune once, build a configuration profile from **Imported Administrative templates**, and assign it to a device group. +- The user device baseline enables three policies: **Management URL**, **Disable update settings**, and **Disable profiles**. Everything else stays **Not configured**. +- Confirm in Intune, then on the device with `reg query` and `netbird debug config`. +- Without Intune, the same settings are registry values under `HKLM\Software\Policies\NetBird`, written by Group Policy, JumpCloud, or a `.reg` file. diff --git a/src/pages/manage/peers/ssh.mdx b/src/pages/manage/peers/ssh.mdx index e44a0514..8fa00d37 100644 --- a/src/pages/manage/peers/ssh.mdx +++ b/src/pages/manage/peers/ssh.mdx @@ -77,7 +77,7 @@ On the machine you want to access via SSH, enable the NetBird SSH server. On a machine where you do not have those rights, which is the normal case for a managed workstation, you cannot enable the SSH server yourself: an administrator has to run the command. For the SSH server itself there is also - [`allowServerSSH`](/client/mdm-integration#policy-keys-reference), which the client + [`allowServerSSH`](/client/mdm-integration#allowServerSSH), which the client applies from MDM policy and so needs nothing from you; root login and SSH authentication have no policy key and must be set on the machine. A switch already in its safe state stays operable: you can turn the SSH server off, turn root login off, or diff --git a/src/pages/manage/reverse-proxy/access-logs.mdx b/src/pages/manage/reverse-proxy/access-logs.mdx index bd412614..cd5c70f3 100644 --- a/src/pages/manage/reverse-proxy/access-logs.mdx +++ b/src/pages/manage/reverse-proxy/access-logs.mdx @@ -37,9 +37,10 @@ Every log entry (HTTP and L4) shares a common set of fields. Some fields are onl | **Bytes Downloaded** | Bytes sent from backend to client | Yes | Yes | | **Source IP** | The client's IP address | Yes | Yes | | **Location** | Country, city, and subdivision based on source IP geolocation | Yes | Yes | -| **Auth Method** | Raw `auth_method_used` value: `oidc` (shown as SSO in the dashboard), `password`, `pin`, or `header`. For denied requests, carries the restriction code instead (e.g. `ip_restricted`, `crowdsec_ban`). Omitted from the API response when empty | Yes | Restriction code on denials | +| **Auth Method** | Raw `auth_method_used` value: `oidc` (shown as SSO in the dashboard), `password`, `pin`, `header`, or a target access action such as `path_bypass` or `path_block`. For denied requests, carries the restriction code instead (e.g. `ip_restricted`, `crowdsec_ban`). Omitted from the API response when empty | Yes | Restriction code on denials | | **User** | The authenticated user's ID, set when `oidc` authentication was used. Omitted from the API response when empty | Yes | N/A | | **Reason** | Exactly one of two values: `Authentication failed` when authentication or an access restriction rejected the request, or `Request failed` when an authenticated request returned `4xx`/`5xx`. Set for HTTP entries only and omitted from the API response when empty. Never a specific denial code: see the note under [Deny reasons](#deny-reasons) | Yes | Omitted | +| **Metadata** | Additional decision details. Target access records `access_action` as `bypass`, `block`, or `invalid_path` (an ambiguous request path rejected with `400`); CrowdSec records its mode and verdict here | Yes | Yes | ## Understanding log entries @@ -47,8 +48,8 @@ Every log entry (HTTP and L4) shares a common set of fields. Some fields are onl HTTP log entries fall into three categories based on the status code: -- **Allowed requests**: successful requests show a `2xx` status code along with the authentication method used to access the service. -- **Denied requests**: failed authentication or access restriction blocks show `401` or `403` status codes with `reason` set to `Authentication failed`. The specific cause (invalid password, missing SSO session, IP restricted, country restricted, CrowdSec verdict) is carried in `auth_method_used`, not in `reason`. +- **Allowed requests**: successful requests show a `2xx` status code along with the authentication method used to access the service. A target that bypasses authentication records `path_bypass`. The dashboard marks these entries with a small open-lock icon in the **User** column, at the right end of the row, labeled **Auth Bypassed** on hover. +- **Denied requests**: failed authentication, access restriction blocks, and blocked target paths show `401` or `403` status codes with `reason` set to `Authentication failed`. The specific cause (invalid password, missing SSO session, blocked path, IP restricted, country restricted, CrowdSec verdict) is carried in `auth_method_used`, not in `reason`. A blocked target path records `path_block`. The dashboard marks these entries with a small shield icon in the **User** column, at the right end of the row, labeled **Path Blocked** on hover. - **Errors**: backend errors or proxy issues show `5xx` status codes. These typically indicate that the target service is unreachable or returned an error. ### L4 log entries @@ -63,6 +64,7 @@ The following deny reasons identify why a connection was rejected. Note that for | Reason | Description | |--------|-------------| +| `path_block` | The matching HTTP target is configured to block access | | `ip_restricted` | The client IP was blocked by a CIDR access restriction | | `country_restricted` | The client's country was blocked by a country access restriction | | `geo_unavailable` | Country restrictions are configured but the GeoIP database is unavailable (fail-closed) | diff --git a/src/pages/manage/reverse-proxy/authentication.mdx b/src/pages/manage/reverse-proxy/authentication.mdx index 64f086a9..15ec0c3a 100644 --- a/src/pages/manage/reverse-proxy/authentication.mdx +++ b/src/pages/manage/reverse-proxy/authentication.mdx @@ -1,11 +1,11 @@ import {Note, Warning} from "@/components/mdx" export const description = - 'Configure SSO, password, PIN, and header authentication methods, plus IP and country access restrictions, for NetBird Reverse Proxy services.' + 'Configure service authentication, per-target access actions, and IP, country, and reputation restrictions for NetBird Reverse Proxy services.' # Reverse Proxy Authentication and Access Restrictions -NetBird Reverse Proxy supports multiple authentication methods and connection-level access restrictions to control who can access your exposed services. You can enable one or more methods on each service, or leave a service completely public. Authentication and access restrictions are configured per service in the **Authentication** tab when creating or editing a service. +NetBird Reverse Proxy supports multiple authentication methods and connection-level access restrictions to control who can access your exposed services. You can enable one or more methods on each service, leave a service completely public, or choose a different authentication action for an individual HTTP target path. Authentication and access restrictions are configured when creating or editing a service.

Authentication tab showing all available authentication methods @@ -175,6 +175,59 @@ Services can also be configured without any authentication. When no authenticati **Best for:** Public-facing websites, APIs that handle their own authentication internally, or services that are intentionally open to the internet. +## Per-target access actions + +HTTP targets can override the service authentication behavior for their matching path. Open or add a target, expand **Optional Settings**, and choose **Access**: + +| Action | Behavior | +|--------|----------| +| **Use service authentication** | Apply the authentication configured on the service. This is the default. | +| **Bypass authentication** | Skip SSO, password, PIN, and header authentication for requests routed to this target. Service-level access restrictions still apply. | +| **Block access** | Return `403` for requests routed to this target without contacting its backend. | + +

+ Edit Target modal with Optional Settings expanded, showing the Access dropdown and its three options: Use service authentication, Bypass authentication, and Block access +

+ +For example, a service can require SSO on its `/` target, use **Bypass authentication** for `/public/`, and use **Block access** for `/admin/`. Target selection uses the longest matching, case-sensitive literal prefix. It does not support glob patterns or regular expressions. See [Path-based routing](/manage/reverse-proxy#path-based-routing) for matching examples and forwarding behavior. + +The action only controls reverse proxy authentication for the selected target. **Bypass authentication** does not bypass CIDR, country, or CrowdSec restrictions configured on the service. It also does not change path rewriting; the existing **Preserve Full Path** setting continues to control whether the matched prefix is sent to the backend. + +Targets that override the default are marked in target lists. Hover the access indicator to see its action. + +

+ Services list with a service expanded to show its three targets: / with no indicator, /public/ with an open-lock indicator whose tooltip reads Bypass authentication, and /admin/ with a red shield indicator for Block access +

+ +### When to use per-target access actions + +Most services need one rule for every path, so leave targets on **Use service authentication** unless a specific path has to behave differently from the rest of the service. + +Use **Bypass authentication** when clients that cannot complete a login must reach a path, and the path either holds nothing sensitive or the backend authorizes requests itself: + +- **Webhook receivers**, such as `/webhooks/`, where a third-party service posts events and the backend verifies the request signature. +- **Health checks**, such as `/healthz`, polled by an external uptime monitor. +- **Public assets**, such as `/public/` or `/static/`, that hold nothing sensitive. + +Use **Block access** when a path should never be reachable through the public domain, even by users who pass service authentication, such as `/admin/` or `/metrics`. Reach those paths over NetBird instead, for example through a [NetBird-Only](#net-bird-only-access-private-services) service or a [Network resource](/manage/networks). + +For **Block access**, prefer the broader prefix. A target on `/admin/` blocks `/admin/users` but not `/admin` itself, which falls through to the next matching target, such as the `/` target with service authentication. To block both, use `/admin`, which also matches `/administrator`. + +Do not use **Bypass authentication** to make a whole application public. If every path should be public, create the service without authentication, so the service settings show its real exposure. + + + **Bypass authentication makes the path public.** Anyone who can reach the service domain and passes its CIDR, country, and CrowdSec restrictions can request every path under the bypassed prefix. Requests arrive at the backend with no NetBird user identity, so the backend must authorize them on its own. + + - Bypass the narrowest prefix that works. Prefixes match as text, so `/public` also matches `/public-admin`; end the path with `/` to match a single segment. + - Disabling or deleting a **Block access** target removes its rule. Requests for that path then fall through to the next matching target, which may be a bypassed one. + - **Custom Headers** configured on the target are still sent to the backend. Do not attach credentials to a bypassed target through headers. + - When any target uses an access action, the proxy rejects ambiguous request paths, such as those with `..` segments, `//`, or encoded separators, with `400` before matching. This stops a request like `/public/../admin/` from reaching a protected path through a bypassed prefix. + + + + Per-target access actions are available only for HTTP services. Upgrade all proxy instances in the selected cluster before using them. Older proxies ignore these actions and continue applying the service's authentication settings. **Bypass authentication** is unavailable for NetBird-Only services and Agent Network targets. + + ## Combining authentication methods You can enable multiple **operator auth methods** on a single service simultaneously. When more than one is active, users authenticate using **any** of the enabled methods — they choose which one to use when accessing the service. @@ -201,6 +254,8 @@ Access restrictions control which connections are allowed to reach your service Access restrictions are evaluated **before** authentication. If a connection is blocked by an access restriction rule, it is rejected immediately without any authentication check. +These restrictions also apply to targets set to **Bypass authentication**. A target action cannot relax a service-level CIDR, country, or CrowdSec denial. +

Authentication tab showing all available authentication methods

diff --git a/src/pages/manage/reverse-proxy/bring-your-own-proxy.mdx b/src/pages/manage/reverse-proxy/bring-your-own-proxy.mdx index a414771a..2ab3a8d2 100644 --- a/src/pages/manage/reverse-proxy/bring-your-own-proxy.mdx +++ b/src/pages/manage/reverse-proxy/bring-your-own-proxy.mdx @@ -141,7 +141,7 @@ Switching to the **Run the Proxy** tab automatically generates a one-time, accou ```shell docker run -d \ - -v /var/lib/certs:/certs \ + -v proxy_certs:/certs \ -e NB_PROXY_CERTIFICATE_DIRECTORY=/certs \ -e NB_PROXY_ALLOW_INSECURE=true \ -e NB_PROXY_MANAGEMENT_ADDRESS=https://api.netbird.io \ @@ -149,6 +149,8 @@ docker run -d \ -e NB_PROXY_DOMAIN=proxy.company.com \ -e NB_PROXY_LOG_LEVEL=info \ -e NB_PROXY_TOKEN=nbx_... \ + -e NB_PROXY_PRIVATE=true \ + -e NB_PROXY_ADDRESS=:443 \ -p 80:80 -p 443:443 \ netbirdio/reverse-proxy:latest ``` @@ -167,6 +169,10 @@ The wizard substitutes `NB_PROXY_MANAGEMENT_ADDRESS` based on the dashboard's co The `-p 80:80` mapping is only needed if you switch to the `http-01` ACME challenge (see [TLS configuration](#tls-configuration)). With the default `tls-alpn-01` challenge you can drop it and publish only `-p 443:443`. + + Keep `NB_PROXY_ADDRESS=:443`. The `netbirdio/reverse-proxy` image listens on port `8443` by default, so without this variable nothing answers on the published port `443`. The certificates go in a named volume because the proxy runs as an unprivileged user (uid `1000`). If you bind-mount a host directory instead, make it writable by that user first (`sudo chown 1000:1000 /var/lib/certs`); otherwise the proxy cannot write to it and logs `permission denied`. + + Run the command on your server. The container will: 1. Connect to NetBird's management server (`https://api.netbird.io` for NetBird Cloud, or the management URL of your self-hosted deployment) over gRPC and authenticate with the token. The wizard fills this in based on the dashboard's configured management endpoint. @@ -201,6 +207,52 @@ Once the cluster is connected, your BYOP domain shows up in the service creation Traffic to `subdomain.proxy.company.com` is now received by your BYOP proxy, terminated locally with a Let's Encrypt certificate, and forwarded over WireGuard to the target peer or network resource. +## Expose TCP and UDP services + +HTTP services, and TLS services on port `443`, share the proxy's main port, so the command above is all they need. Every other listen port is different. When you create a TCP, UDP or TLS service on a port other than `443`, the proxy opens a listener on that port inside its container, and clients cannot reach it until Docker publishes the port: TCP connections to it are refused. + +For every such listen port: + +1. Allow the port through the firewall or security group in front of the server. +2. Publish it on the container: `-p 5432:5432` for TCP, `-p 5353:5353/udp` for UDP. + +Docker fixes a container's published ports when it creates the container, so publishing another port means recreating it (`docker rm -f` and `docker run` again, or `docker compose up -d` after editing `ports:`). A recreate briefly interrupts every service on the proxy, not only the new one. Publishing ports this way is the default, and it keeps Docker as a second filter in front of the proxy. If you add L4 services often, there are two ways to avoid a recreate for each one. + +**Publish a range up front.** For example, add `-p 20000-20099:20000-20099 -p 20000-20099:20000-20099/udp` and choose listen ports inside that range. Ranges cost memory: with Docker's default userland proxy, Docker starts helper processes for every published port and protocol, and a 100-port range for TCP and UDP uses about 500 MB. Keep the range as small as your plans allow. A host firewall such as UFW does not filter ports that Docker publishes ([Docker and ufw](https://docs.docker.com/engine/network/packet-filtering-firewalls/#docker-and-ufw)), so restrict the range with a firewall or security group in front of the server, or with rules in Docker's `DOCKER-USER` chain, and open only the ports you use. + +**Run the proxy on the host network.** This suits a server dedicated to the proxy, because it needs a host-wide setting (below) and leaves the firewall as the only filter. The proxy then binds each listen port on the server itself, so a new TCP or UDP service is reachable as soon as you create it, with no change to the container and no interruption to other services. The firewall becomes the only thing that decides which ports are reachable, so keep it closed by default and open each listen port as you add its service. + +The image runs as an unprivileged user, and on the host network an unprivileged process cannot bind ports below `1024`. `NB_PROXY_ADDRESS=:443` then fails with `bind: permission denied`, and `--cap-add NET_BIND_SERVICE` does not change that. Before you start the proxy, lower the host's unprivileged port floor to the lowest port you need: + +```shell +sudo sysctl -w net.ipv4.ip_unprivileged_port_start=443 +echo 'net.ipv4.ip_unprivileged_port_start=443' | sudo tee /etc/sysctl.d/90-netbird-proxy.conf +``` + +This setting applies to every process on the server, so give the proxy a host of its own. Then start it with the wizard's command, replacing the `-p` flags with `--network host`: + +```shell +docker run -d \ + --network host \ + -v proxy_certs:/certs \ + -e NB_PROXY_CERTIFICATE_DIRECTORY=/certs \ + -e NB_PROXY_ALLOW_INSECURE=true \ + -e NB_PROXY_MANAGEMENT_ADDRESS=https://api.netbird.io \ + -e NB_PROXY_ACME_CERTIFICATES=true \ + -e NB_PROXY_DOMAIN=proxy.company.com \ + -e NB_PROXY_LOG_LEVEL=info \ + -e NB_PROXY_TOKEN=nbx_... \ + -e NB_PROXY_PRIVATE=true \ + -e NB_PROXY_ADDRESS=:443 \ + netbirdio/reverse-proxy:latest +``` + +An L4 service on a port below the floor still fails to bind: its status becomes `error`, and the proxy logs `bind: permission denied` for that port. + + + Do not run the container as root (`--user 0:0`) to get around the port limit. As root, the proxy's embedded NetBird client does not connect to management, and HTTP services return `504`. + + ## Private services (NetBird-Only Access) A BYOP cluster whose proxy runs embedded as a NetBird peer (`netbird proxy` mode — the default for the `netbirdio/reverse-proxy` image) reports the `Private` capability if started with the flag `--private` or environment variable `NB_PROXY_PRIVATE=true`. With that capability set, the cluster unlocks two new options anywhere it is selected: @@ -283,12 +335,13 @@ curl -X POST /api/reverse-proxies/proxy-tokens \ ```shell docker run -d \ - -v /var/lib/certs:/certs \ + -v proxy_certs:/certs \ -e NB_PROXY_CERTIFICATE_DIRECTORY=/certs \ -e NB_PROXY_MANAGEMENT_ADDRESS= \ -e NB_PROXY_ACME_CERTIFICATES=true \ -e NB_PROXY_DOMAIN=proxy.company.com \ -e NB_PROXY_TOKEN= \ + -e NB_PROXY_ADDRESS=:443 \ -p 80:80 -p 443:443 \ netbirdio/reverse-proxy:latest ``` @@ -340,3 +393,4 @@ For deeper debugging, raise the log level to `debug` or `trace` (`NB_PROXY_LOG_L - [Authentication](/manage/reverse-proxy/authentication) — SSO, password, PIN, header, and access restrictions. - [Access Logs](/manage/reverse-proxy/access-logs) — auditing for traffic going through your proxies. - [Running Multiple Proxy Instances](/selfhosted/maintenance/scaling/multiple-proxy-instances) — operational patterns for HA proxy clusters. +- [Reverse Proxy on OpenShift](/manage/reverse-proxy/openshift) — deploy the proxy on OpenShift under the `restricted-v2` SCC with a TCP LoadBalancer or TLS-passthrough Routes. diff --git a/src/pages/manage/reverse-proxy/index.mdx b/src/pages/manage/reverse-proxy/index.mdx index d8ab9e10..d0977a8a 100644 --- a/src/pages/manage/reverse-proxy/index.mdx +++ b/src/pages/manage/reverse-proxy/index.mdx @@ -53,16 +53,27 @@ The service mode determines how the proxy handles traffic between clients and yo | Mode | Layer | Description | |------|-------|-------------| | **HTTP** | L7 | TLS termination at the proxy, HTTP-level forwarding. Supports path-based routing, host header forwarding, redirect rewriting, and browser-based authentication (SSO, password, PIN). | -| **TCP** | L4 | Raw TCP relay. The proxy accepts TCP connections on a dedicated port and forwards them to your backend. | -| **UDP** | L4 | UDP relay with session tracking. The proxy accepts UDP packets on a dedicated port and forwards them to your backend. Sessions are reaped after an idle timeout. | +| **TCP** | L4 | Raw TCP relay. The proxy accepts TCP connections on the service's listen port and forwards them to your backend. | +| **UDP** | L4 | UDP relay with session tracking. The proxy accepts UDP packets on the service's listen port and forwards them to your backend. Sessions are reaped after an idle timeout. | | **TLS** | L4 | TLS passthrough with SNI-based routing. The proxy inspects the TLS ClientHello to read the SNI hostname and forwards the encrypted connection to your backend without terminating TLS. | -L4 services (TCP, UDP, TLS) listen on a dedicated port on the proxy cluster. Depending on the cluster, the port may be auto-assigned or you can specify one manually. The proxy cluster's `supports_custom_ports` capability determines whether manual port selection is available. +L4 services (TCP, UDP, TLS) listen on a port on the proxy cluster, and several services can share a port. TCP and UDP services can have their port auto-assigned or, on clusters that support custom ports (`supports_custom_ports`), chosen by you. TLS services always need a port you choose. See [Port allocation for L4 services](#port-allocation-for-l4-services) and [How services share ports](#how-services-share-ports). L4 services do not support browser-based authentication (SSO, password, PIN) or header authentication because there is no HTTP layer. You can use [access restrictions](/manage/reverse-proxy/authentication#access-restrictions) (IP CIDR, country, and CrowdSec rules) to protect L4 services. +### Protocol support + +HTTP services carry WebSocket connections and Server-Sent Events streams, and serve clients over HTTP/2 or HTTP/1.1. HTTP/3 is not advertised. + +Toward the backend, the proxy uses HTTP/2 only over TLS: a target with protocol `http` is always reached over HTTP/1.1, even if the backend supports HTTP/2 without TLS (h2c). This matters for gRPC, which runs over HTTP/2: + +- **gRPC backend with TLS**: use an HTTP service with an `https` target. +- **gRPC backend without TLS**: use a TCP service, which relays the connection unchanged. + +TCP services relay other TCP protocols the same way, such as SSH and RDP. + ### Targets A target defines where proxied traffic is sent within your NetBird network. Every target specifies a type and port. HTTP services additionally support path-based routing. @@ -81,6 +92,7 @@ Target properties vary by service mode: - **Path** (optional) - a URL path prefix for path-based routing (e.g., `/api`). See [Path-based routing](#path-based-routing) below. - **Protocol** - `HTTP` or `HTTPS`, depending on what the backend service speaks - **Port** - the port on the target machine (defaults to `80` for HTTP, `443` for HTTPS) +- **Access** (optional) - use the service authentication, bypass authentication, or block access for the target's matching path. Configure this under **Optional Settings**. See [Per-target access actions](/manage/reverse-proxy/authentication#per-target-access-actions). - **Enabled/Disabled toggle** - individually enable or disable targets without removing them **L4 services (TCP, UDP, TLS):** @@ -252,6 +264,7 @@ In the **Details** tab: 6. In the target configuration, select the **type** (Peer, Host, Domain, Subnet, or Proxy Cluster), then choose the specific peer, resource, or cluster. 7. For HTTP services, set the **protocol** (HTTP or HTTPS) and **port** for the target. Optionally, enter a **path** for path-based routing. For L4 services, set the target **host/IP** and **port**. For **Proxy Cluster** targets, the host field accepts any hostname or IP the cluster's embedded proxy can resolve from its own host stack — see [Private services](/manage/reverse-proxy/bring-your-own-proxy#private-services-net-bird-only-access) for details. +8. For an HTTP target, expand **Optional Settings** and choose an **Access** action if this path should differ from the service default. **Use service authentication** is the default. See [Per-target access actions](/manage/reverse-proxy/authentication#per-target-access-actions).

Add Target configuration modal showing the unified Peer / Resource / Proxy Cluster picker @@ -349,15 +362,17 @@ Within a service, you can: ## Port allocation for L4 services -L4 services (TCP, UDP, TLS) require a dedicated port on the proxy cluster. How ports are assigned depends on whether the proxy cluster supports custom port selection: +L4 services (TCP, UDP, TLS) listen on a port on the proxy cluster. How ports are assigned depends on whether the proxy cluster supports custom port selection: - **Custom port selection available**: you choose the exact port the proxy listens on. This is useful when clients expect a well-known port (e.g., 5432 for PostgreSQL, 3306 for MySQL). Self-hosted proxy clusters support this when configured to allow it. -- **Auto-assigned ports only**: the proxy cluster automatically assigns an available port. This is the case for NetBird's shared cloud proxy clusters, where port allocation is managed to avoid conflicts between accounts. The assigned port is shown in the service details after creation. +- **Auto-assigned ports only**: the proxy cluster automatically assigns an available port to TCP and UDP services. This is the case for NetBird's shared cloud proxy clusters, where port allocation is managed to avoid conflicts between accounts. The assigned port is shown in the service details after creation. -The dashboard indicates whether a proxy cluster supports custom ports when you select the domain. If custom ports are not supported, the listen port field is read-only and populated after creation. +TLS services are the exception: they always need a listen port that you choose, including on clusters without custom port selection, because TLS services share ports by SNI hostname. Creating a TLS service without a listen port fails. + +The dashboard indicates whether a proxy cluster supports custom ports when you select the domain. If custom ports are not supported, the listen port field of a TCP or UDP service is read-only and populated after creation. For a TLS service, the field stays editable. - Each port on a proxy cluster can only be used by one service at a time. If you specify a port that is already in use by another service, creation will fail. L4 listen ports also cannot conflict with the proxy's tunnel port. + A port on a proxy cluster can be shared by any number of TLS services, told apart by their SNI hostname, plus at most one TCP service and at most one UDP service. The TCP service receives every connection that matches no TLS service (or, on the main port, no HTTP service). Creating a TCP or UDP service fails if the port already has one of that protocol, and creating any service fails if another service already uses its domain. L4 listen ports also cannot conflict with the proxy's tunnel port. @@ -365,7 +380,7 @@ The dashboard indicates whether a proxy cluster supports custom ports when you s - **Self-hosted Docker deployments:** The default Docker Compose configuration only routes port 443 (via Traefik TLS passthrough) to the proxy container. L4 services that listen on additional TCP or UDP ports require you to manually expose those ports in your `docker-compose.yml`. See the [migration guide](/selfhosted/migration/enable-reverse-proxy#exposing-l4-ports) for instructions. + **Self-hosted Docker deployments:** The default Docker Compose configuration only routes port 443 (via Traefik TLS passthrough) to the proxy container, where the proxy's main port is `8443` (`NB_PROXY_ADDRESS`). A TLS service given listen port `443` in such a deployment therefore listens on a custom port inside the container, not on the main port. L4 services that listen on additional TCP or UDP ports require you to manually expose those ports in your `docker-compose.yml`. See the [migration guide](/selfhosted/migration/enable-reverse-proxy#exposing-l4-ports) for instructions. A Bring Your Own Proxy deployment has the same requirement; see [Expose TCP and UDP services](/manage/reverse-proxy/bring-your-own-proxy#expose-tcp-and-udp-services). In both cases, running the proxy on the host network removes the per-port Docker step. ### How services share ports @@ -376,32 +391,38 @@ The proxy's main port always runs an SNI router that peeks at the TLS ClientHell - Each incoming connection is matched against configured domains. - If the SNI hostname matches an **HTTP** service, the connection is forwarded to the HTTP reverse proxy for L7 handling. - If the SNI hostname matches a **TLS** service, the encrypted connection is passed through directly to the backend without TLS termination. -- If the SNI hostname does not match any service, the connection falls through to the HTTP reverse proxy, which returns an error since no matching service exists. -- Connections with no SNI (e.g., TLS 1.3 with Encrypted Client Hello) follow the same fallback path. +- If the SNI hostname does not match any service, the connection goes to the TCP service on that port if there is one, and otherwise falls through to the HTTP reverse proxy, which returns an error since no matching service exists. +- Connections with no SNI, and connections that are not TLS at all, follow the same fallback path. - Do not configure an HTTP and a TLS service with the same hostname. The HTTP route takes priority and the TLS service becomes unreachable. Use different domain names for each. + A domain can belong to only one service, so an HTTP and a TLS service cannot share a hostname: creating the second one fails with `domain already taken`. Use a different domain for each. -**On custom ports**, TLS and TCP services can coexist: +**On custom ports**, any number of TLS services and at most one TCP service can share the same port: - TLS connections are matched by SNI and passed through to the backend. - Connections that do not match any SNI route (or are not TLS at all) fall back to the TCP relay. - This is useful for running a TLS passthrough alongside a plain TCP catch-all on the same port. +- If the port has no TCP service, connections that match no TLS service are dropped. +- HTTP services are served only on the main port. On a custom port, a request for an HTTP service's hostname matches no route, so it goes to the TCP service if there is one and is dropped otherwise. ## Path-based routing -When a service has multiple targets, you can assign each target a unique path prefix to route different URL paths to different backends. For example: +When a service has multiple targets, you can assign each target a unique path prefix to route different URL paths to different backends. Each HTTP target can also choose what happens after its path matches: -| Path | Target | Description | -|------|--------|-------------| -| `/` | Peer A (port 3000) | Main web application | -| `/api` | Peer B (port 8080) | API service | -| `/docs` | Resource C (port 80) | Documentation server | +| Path | Target | Access | Example result | +|------|--------|--------|----------------| +| `/` | Peer A (port 3000) | Use service authentication | `/account` uses the authentication configured on the service | +| `/public/` | Peer B (port 8080) | Bypass authentication | `/public/logo.svg` reaches Peer B without service authentication | +| `/admin/` | Resource C (port 80) | Block access | `/admin/users` returns `403`; Resource C is not contacted | -Incoming requests are matched against the configured path prefixes and forwarded to the corresponding target. Each path must be unique within a service. +Incoming requests are matched against the configured path prefixes. When an enabled target uses **Bypass authentication** or **Block access**, all enabled target paths must be unique within the service. If more than one prefix matches, the **longest matching prefix** wins. In the example above, `/public/logo.svg` matches both `/` and `/public/`, so the `/public/` target and its access action are selected. This is useful for consolidating multiple internal services under a single public domain, reducing the number of domains and TLS certificates you need to manage. +Prefix matching is literal and case-sensitive. Glob patterns such as `*` or `**` and regular expressions are not supported. Prefixes match the request path as text, not by path segment, so a target on `/api` also receives `/api-docs`. To match a whole segment, end the path with a slash: a target on `/api/` receives `/api/x`, but not `/api-docs` or `/api` itself. Likewise, `/Public/x` does not match `/public/`. + +The access action does not change how paths are forwarded. By default, the matched prefix is removed before the request reaches the backend: `/api/x` arrives as `/x`, and `/api-docs` as `/-docs`. To keep the full path, use the existing **Preserve Full Path** target setting (`path_rewrite: preserve` in the API). + ## Integration with Networks If you have already configured [Networks](/manage/networks) with resources and routing peers, you can expose a network resource directly from the Networks page. @@ -430,6 +451,7 @@ For the complete guide including setup, authentication options, session lifecycl - [Expose from CLI](/manage/reverse-proxy/expose-from-cli) - expose local services from the command line using `netbird expose` - [Multiple Proxy Instances](/selfhosted/maintenance/scaling/multiple-proxy-instances) - deploy multiple proxy instances for high availability and redundancy +- [Reverse Proxy on OpenShift](/manage/reverse-proxy/openshift) - run the rootless UBI proxy image on OpenShift with automatic certificates - [Custom Domains](/manage/reverse-proxy/custom-domains) - configure your own domain names for reverse proxy services - [Authentication](/manage/reverse-proxy/authentication) - SSO, password, PIN, header authentication, and access restrictions - [Access Logs](/manage/reverse-proxy/access-logs) - monitor and audit traffic to your reverse proxy services diff --git a/src/pages/manage/reverse-proxy/openshift.mdx b/src/pages/manage/reverse-proxy/openshift.mdx new file mode 100644 index 00000000..8af6e314 --- /dev/null +++ b/src/pages/manage/reverse-proxy/openshift.mdx @@ -0,0 +1,580 @@ +import {Note, Warning} from "@/components/mdx" + +export const description = + 'Run the NetBird reverse proxy on Red Hat OpenShift with the rootless UBI image, automatic Let\'s Encrypt certificates, and either a TCP LoadBalancer or TLS-passthrough Routes for public exposure.' + +# Reverse Proxy on OpenShift + +This guide deploys the NetBird reverse proxy on OpenShift so an external client can reach a backend Service over the NetBird overlay. The proxy obtains and renews its own TLS certificates with ACME, runs under the stock `restricted-v2` security context constraint, and needs no custom SCC, privileged mode, host networking, or elevated capabilities. + +It uses the rootless proxy image built on Red Hat Universal Base Image (UBI) 9 and published for Linux AMD64 and ARM64. The image runs as a non-root user and keeps its writable directories group-writable so OpenShift can assign an arbitrary UID. The manifest in [Step 2](#step-2-deploy-the-proxy) pins the image tag; pin a version or digest for reproducible deployments and see [NetBird releases](https://github.com/netbirdio/netbird/releases) for new versions. + +By the end of this guide, the namespace contains: + +| Resource | Name | Purpose | +|----------|------|---------| +| Secret | `netbird-proxy-config` | Proxy token, cluster domain, and management address | +| PersistentVolumeClaim | `netbird-proxy-certs` | ACME account key and issued certificates | +| Deployment | `netbird-proxy` | The NetBird reverse proxy, one replica | +| Service | `netbird-proxy` | `ClusterIP`, or `LoadBalancer` for the preferred exposure | +| Route | One per service hostname | Only for the TLS-passthrough fallback | +| Deployment and Service | `nginx-server` | Optional test backend | + +## How it works + +The proxy is an ordinary NetBird reverse proxy instance: it registers with your management server over gRPC, receives the service mappings for its cluster domain, requests a certificate from Let's Encrypt for each service hostname, terminates TLS, and forwards traffic through the NetBird overlay to a routing peer that reaches the backend Service. + +```mermaid +flowchart LR + internet[Internet] -->|TCP 443| entry[Public TCP entrypoint] + entry -->|TLS intact| proxy[NetBird reverse proxy] + proxy ==>|NetBird overlay| peer[Routing peer] + peer -->|HTTP| app[Backend Service] + proxy -.->|gRPC: token, service mappings| mgmt[NetBird management] + proxy -.->|ACME| le[Let's Encrypt] +``` + +Only the proxy is reachable from the Internet. The backend Service stays `ClusterIP` and is reached through the routing peer only. + +The public TCP entrypoint must forward TCP 443 to the proxy **without terminating TLS**, so the proxy can answer the Let's Encrypt `tls-alpn-01` challenge and present the issued certificate itself. OpenShift offers two ways to do that. Decide which one applies to your cluster before you start, because it determines your DNS records and your ongoing work: + +| | [TCP LoadBalancer](#preferred-exposure-a-tcp-load-balancer) (preferred) | [TLS-passthrough Routes](#fallback-without-a-load-balancer-tls-passthrough-routes) (fallback) | +|---|---|---| +| Requires | A LoadBalancer provider that allocates a publicly reachable address | The public OpenShift ingress router | +| DNS | Wildcard `CNAME` for service hostnames | Wildcard `CNAME` for service hostnames | +| Work per new service | None | Create one Route for the service hostname | +| Custom domains | DNS records only | DNS records and one Route per hostname | + +## Prerequisites + +Before you start, make sure you have: + +- An OpenShift login with permission to create Deployments, Services, Secrets, PersistentVolumeClaims and, for the Route fallback, Routes in the target namespace. No additional SCC is needed. +- A default StorageClass that can provision a 1 GiB PVC. +- A **proxy access token** from your NetBird management server. This is not a client setup key or a personal access token: + - **NetBird Cloud** - create an account-scoped token in the dashboard under **Reverse Proxy** > **Clusters** > **Setup Self-Hosted Cluster**, or through the API. See [Bring Your Own Proxy](/manage/reverse-proxy/bring-your-own-proxy). The management address is `https://api.netbird.io`. + - **Self-hosted** - create a management-wide token with `netbird-server admin token create` (combined container) or `netbird-mgmt admin token create` (multi-container). See [Enable Reverse Proxy](/selfhosted/migration/enable-reverse-proxy). The management address is your management server's public URL. Because this proxy runs outside the management host's Docker network, the management server's Traefik must also route the `ProxyService` gRPC path and have its idle timeout disabled. Follow [Prepare the management server for cross-host proxies](/selfhosted/maintenance/scaling/multiple-proxy-instances#prepare-the-management-server-for-cross-host-proxies) first. This [Traefik requirement](/manage/reverse-proxy#traefik-requirement) applies to the management server, not to the proxy deployed here. +- Public DNS names you control, and inbound TCP 443 connectivity from the Internet to the proxy. +- Outbound access from the cluster to your NetBird management server, the configured signal and relay services, Let's Encrypt, and `pkgs.netbird.io`, from which the proxy downloads its geolocation database at startup. +- A NetBird routing peer that can reach the backend Service. [Step 4](#step-4-connect-a-backend) uses the rootless UBI client from the [OpenShift installation guide](/get-started/install/openshift) deployed in the same cluster. + + + If the namespace has a default-deny NetworkPolicy, allow egress from the proxy pod to DNS, the management server, signal, relays, Let's Encrypt, and `pkgs.netbird.io` on TCP 443. Also allow ingress to the proxy pod on TCP 8443: from the OpenShift router for the Route fallback (namespaces labeled `policy-group.network.openshift.io/ingress: ""`), or from the load balancer's source addresses for the preferred exposure. Allow the kubelet to reach TCP 8080 for the health probes if your policy restricts host-network traffic. + + +The guide uses three values. Replace the examples with your own: + +| Variable | Example | Purpose | +|----------|---------|---------| +| `NB_PROXY_DOMAIN` | `proxy.example.com` | The **cluster domain**. The proxy registers under this domain, and services are created as subdomains of it. It is also the value of the proxy's `NB_PROXY_DOMAIN` environment variable. | +| `NB_PROXY_MANAGEMENT_ADDRESS` | `https://netbird.example.com:443` | The management server that issued your token, `https://api.netbird.io` for NetBird Cloud. It is also the value of the proxy's `NB_PROXY_MANAGEMENT_ADDRESS` environment variable. | +| `SERVICE_HOSTNAME` | `nginx.proxy.example.com` | The **service hostname** users visit. Shell variable only; the proxy learns service hostnames from management. Here it is a subdomain of the cluster domain; a verified [custom domain](/manage/reverse-proxy/custom-domains) works the same way. | + +Export them for the commands in this guide, then create the namespace: + +```bash +export NB_PROXY_DOMAIN=proxy.example.com +export NB_PROXY_MANAGEMENT_ADDRESS=https://netbird.example.com:443 +export SERVICE_HOSTNAME=nginx.proxy.example.com +export NAMESPACE=netbird-proxy + +oc create namespace "$NAMESPACE" --dry-run=client -o yaml | oc apply -f - +``` + +## Step 1: Create the configuration Secret + +The Deployment reads its token, cluster domain, and management address from a Secret named `netbird-proxy-config`. Create it with your proxy access token: + +```bash +export NB_PROXY_TOKEN=nbx_replace_with_your_proxy_token + +oc create secret generic netbird-proxy-config -n "$NAMESPACE" \ + --from-literal=proxy-token="$NB_PROXY_TOKEN" \ + --from-literal=proxy-domain="$NB_PROXY_DOMAIN" \ + --from-literal=management-address="$NB_PROXY_MANAGEMENT_ADDRESS" \ + --dry-run=client -o yaml | oc apply -f - +``` + +The command is safe to re-run to change a value. The proxy picks up changes only after a restart: `oc rollout restart deployment/netbird-proxy -n "$NAMESPACE"`. + +## Step 2: Deploy the proxy + +Self-issued ACME certificates and their account keys live on a single writable volume. The Deployment therefore runs one replica with the `Recreate` strategy, uses filesystem locking for the certificate cache, and mounts a PersistentVolumeClaim at `/certs` so the ACME account and issued certificates survive pod replacement. + +Save the following as `netbird-proxy.yaml`. It needs no edits; every environment-specific value comes from the Secret, and the namespace comes from the `oc apply` command. + +```yaml +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: netbird-proxy-certs +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: netbird-proxy + labels: + app.kubernetes.io/name: netbird-proxy +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + app.kubernetes.io/name: netbird-proxy + template: + metadata: + labels: + app.kubernetes.io/name: netbird-proxy + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: proxy + image: ghcr.io/netbirdio/reverse-proxy:0.80.0-ubi + imagePullPolicy: IfNotPresent + env: + - name: NB_PROXY_TOKEN + valueFrom: + secretKeyRef: + name: netbird-proxy-config + key: proxy-token + - name: NB_PROXY_DOMAIN + valueFrom: + secretKeyRef: + name: netbird-proxy-config + key: proxy-domain + - name: NB_PROXY_MANAGEMENT_ADDRESS + valueFrom: + secretKeyRef: + name: netbird-proxy-config + key: management-address + - name: NB_PROXY_ADDRESS + value: :8443 + - name: NB_PROXY_HEALTH_ADDRESS + value: :8080 + - name: NB_PROXY_ACME_CERTIFICATES + value: "true" + - name: NB_PROXY_ACME_CHALLENGE_TYPE + value: tls-alpn-01 + - name: NB_PROXY_CERTIFICATE_DIRECTORY + value: /certs + - name: NB_PROXY_CERT_LOCK_METHOD + value: flock + - name: NB_PROXY_SUPPORTS_CUSTOM_PORTS + value: "false" + ports: + - name: https + containerPort: 8443 + - name: health + containerPort: 8080 + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + startupProbe: + httpGet: + path: /healthz/startup + port: health + periodSeconds: 5 + timeoutSeconds: 3 + failureThreshold: 60 + readinessProbe: + httpGet: + path: /healthz/ready + port: health + periodSeconds: 10 + timeoutSeconds: 3 + livenessProbe: + httpGet: + path: /healthz/live + port: health + periodSeconds: 10 + timeoutSeconds: 3 + resources: + requests: + cpu: 100m + memory: 256Mi + volumeMounts: + - name: certificates + mountPath: /certs + volumes: + - name: certificates + persistentVolumeClaim: + claimName: netbird-proxy-certs +--- +apiVersion: v1 +kind: Service +metadata: + name: netbird-proxy + labels: + app.kubernetes.io/name: netbird-proxy +spec: + type: ClusterIP + selector: + app.kubernetes.io/name: netbird-proxy + ports: + - name: https + port: 443 + targetPort: https +``` + +Apply it and wait for the rollout: + +```bash +oc apply -n "$NAMESPACE" -f netbird-proxy.yaml +oc rollout status deployment/netbird-proxy -n "$NAMESPACE" --timeout=5m +``` + +The Service is `ClusterIP` on purpose. [Step 3](#step-3-expose-the-proxy) either switches it to `LoadBalancer` or leaves it internal and puts Routes in front of it. + + + On OpenShift, leave `runAsUser`, `runAsGroup`, and `fsGroup` unset. The `restricted-v2` admission applies the namespace's allowed identity and volume group for you, and the image is built to work under that arbitrary UID. + + +**Checkpoint.** The startup probe passes only after the proxy has connected to management and completed its initial mapping sync, so a finished rollout confirms the token and management address are correct. Check the PVC and the logs: + +```bash +oc get pvc netbird-proxy-certs -n "$NAMESPACE" +oc logs deployment/netbird-proxy -n "$NAMESPACE" --tail=50 +``` + +The PVC is `Bound`, the logs contain `Initial mapping sync complete`, and the cluster appears in the dashboard under **Reverse Proxy** > **Clusters** with an **Online** badge. If the rollout times out, see [Troubleshooting](#troubleshooting). + +### Configuration explained + +| Setting | Why | +|---------|-----| +| `replicas: 1` and `strategy: Recreate` | One process owns the certificate cache on the PVC. A rolling update would briefly run two writers against the same volume. See [Running more than one replica](#running-more-than-one-replica). | +| `NB_PROXY_CERT_LOCK_METHOD=flock` | Filesystem locking on the PVC. The `auto` default may select the Kubernetes lease backend, which needs a service account with lease RBAC. This Deployment mounts no service account token. | +| `NB_PROXY_ADDRESS=:8443` | The proxy listens on an unprivileged port. The Service maps port 443 to it. | +| `NB_PROXY_HEALTH_ADDRESS=:8080` | Binds the health endpoint to all interfaces so kubelet can reach the startup, readiness, and liveness probes. The same endpoint serves [Prometheus metrics](/selfhosted/observability/proxy). | +| `NB_PROXY_ACME_CERTIFICATES=true` and `NB_PROXY_ACME_CHALLENGE_TYPE=tls-alpn-01` | The proxy issues and renews certificates itself over port 443. Port 80 is not required. `tls-alpn-01` issues certificates for individual hostnames, not wildcards. See [TLS-ALPN-01 requirements](/manage/reverse-proxy#tls-alpn-01-requirements) and, if you need `http-01`, [Using http-01 instead of tls-alpn-01](#using-http-01-instead-of-tls-alpn-01). | +| `NB_PROXY_CERTIFICATE_DIRECTORY=/certs` on a PVC | Retains the ACME account key and certificates across pod replacement. Keep the PVC; deleting it forces fresh issuance and counts against Let's Encrypt rate limits. | +| `NB_PROXY_SUPPORTS_CUSTOM_PORTS=false` | Neither the Service nor the Routes expose arbitrary TCP or UDP ports, so the cluster does not advertise the **Custom Ports** capability. | +| `automountServiceAccountToken: false` and `capabilities: drop: [ALL]` | The proxy needs no Kubernetes API access and no Linux capabilities. | + +For the full list of variables, see the [environment variable reference](/selfhosted/migration/enable-reverse-proxy#environment-variable-reference). + +## Step 3: Expose the proxy + +Pick the option you chose in [How it works](#how-it-works). In both cases, public TCP 443 must reach the proxy's port 8443 for the initial certificate issuance as well as every renewal. + +### Preferred exposure: a TCP LoadBalancer + +If your cluster has a LoadBalancer provider, change the Service type. No Route is needed in this mode: + +```bash +oc patch service netbird-proxy -n "$NAMESPACE" -p '{"spec":{"type":"LoadBalancer"}}' +oc get service netbird-proxy -n "$NAMESPACE" +``` + +A working provider allocates an externally reachable address in the `EXTERNAL-IP` column and forwards TCP 443 to the proxy as plain TCP. A cloud network load balancer or MetalLB with public routing both qualify. MetalLB alone does not make a private address reachable from the Internet. + +Point the cluster domain at the provisioned address with an `A`/`AAAA` record for an IP, or a `CNAME` for a load balancer hostname. Then point the service hostnames at the cluster domain with a wildcard `CNAME`: + +| Record type | Name | Value | +|-------------|------|-------| +| `A` or `CNAME` | `proxy.example.com` | The load balancer's IP or hostname | +| `CNAME` | `*.proxy.example.com` | `proxy.example.com` | + +Publish an `AAAA` record only if the IPv6 path also works end to end. The NetBird proxy routes by the hostname in the TLS handshake and handles every configured service hostname itself, so new services need no further OpenShift changes. + +**Checkpoint.** `EXTERNAL-IP` shows an address instead of ``, and `dig "$SERVICE_HOSTNAME"` resolves to it. + +### Fallback without a LoadBalancer: TLS-passthrough Routes + +If the cluster has no LoadBalancer provider, use the existing public OpenShift ingress router instead. Leave the `netbird-proxy` Service as `ClusterIP`. If you already switched it to `LoadBalancer` and `EXTERNAL-IP` stayed ``, switch it back: + +```bash +oc patch service netbird-proxy -n "$NAMESPACE" -p '{"spec":{"type":"ClusterIP"}}' +``` + +The OpenShift router picks a backend by matching the Route's hostname against the SNI in the TLS handshake, not by Service name. **Every service hostname NetBird knows about therefore needs its own Route with TLS passthrough, pointing at the proxy Service.** It is one Route per hostname, not one Kubernetes Service per hostname. + +```mermaid +flowchart LR + internet[Internet] -->|TCP 443| router[OpenShift router] + router -->|SNI match, TLS passthrough| svc[proxy Service] + svc -->|TCP 8443| proxy[NetBird reverse proxy] + proxy ==>|NetBird overlay| peer[Routing peer] + peer -->|HTTP| app[Backend Service] +``` + +Create the Route for the example service hostname: + +```bash +oc create route passthrough nginx-proxy -n "$NAMESPACE" \ + --hostname="$SERVICE_HOSTNAME" \ + --service=netbird-proxy \ + --port=https +``` + +The equivalent manifest, applied with `oc apply -n "$NAMESPACE" -f`: + +```yaml +apiVersion: route.openshift.io/v1 +kind: Route +metadata: + name: nginx-proxy +spec: + host: nginx.proxy.example.com + to: + kind: Service + name: netbird-proxy + port: + targetPort: https + tls: + termination: passthrough +``` + +Then configure DNS. The cluster domain points at the public OpenShift ingress endpoint, and the service hostnames point at the cluster domain: + +| Record type | Name | Value | +|-------------|------|-------| +| `CNAME` | `proxy.example.com` | The router's public endpoint, for example `router-default.apps..example.com` | +| `CNAME` | `*.proxy.example.com` | `proxy.example.com` | + +DNS gets the connection to the router. The Route's hostname match gets it from the router to the NetBird proxy. A `CNAME` does not change the hostname sent in the TLS handshake, so the Route must match the service hostname exactly. A Route for the bare cluster domain or for a parent domain does not match `nginx.proxy.example.com`. + + + Use `passthrough` termination only. With `edge` or `reencrypt`, the router terminates TLS, clients receive the cluster's wildcard certificate, and the `tls-alpn-01` challenge never completes. + + +**Checkpoint.** The router admitted the Route: + +```bash +oc get route nginx-proxy -n "$NAMESPACE" \ + -o jsonpath='{range .status.ingress[*].conditions[*]}{.type}={.status}: {.message}{"\n"}{end}' +``` + +The output shows `Admitted=True`, and `dig "$SERVICE_HOSTNAME"` resolves to the router's public endpoint. + +#### Adding more services + +Each service you add in NetBird later, including services on a custom domain, needs its own Route before its certificate can be issued. Create it with a unique name, keeping `--service=netbird-proxy --port=https`: + +```bash +oc create route passthrough app-proxy -n "$NAMESPACE" \ + --hostname=app.proxy.example.com \ + --service=netbird-proxy \ + --port=https +``` + +If a Route for the hostname already exists, update it rather than creating a duplicate. + +## Step 4: Connect a backend + +The backend proves end-to-end connectivity. It should be reachable only through the proxy and the NetBird overlay, never directly from the Internet. + +### Deploy a test backend + +Deploy an unprivileged nginx listening on port 8080, fronted by a `ClusterIP` Service named `nginx-server` that maps HTTP port 80 to the container's port 8080. Save it as `nginx-server.yaml` and apply it with `oc apply -n "$NAMESPACE" -f nginx-server.yaml`. Keep this Service internal; the public entrypoint targets the NetBird proxy, not nginx. + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: nginx-server +spec: + replicas: 1 + selector: + matchLabels: + app: nginx-server + template: + metadata: + labels: + app: nginx-server + spec: + automountServiceAccountToken: false + securityContext: + runAsNonRoot: true + seccompProfile: + type: RuntimeDefault + containers: + - name: nginx + image: docker.io/nginxinc/nginx-unprivileged:stable-alpine + ports: + - name: http + containerPort: 8080 + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: [ALL] + readinessProbe: + httpGet: + path: / + port: http + resources: + requests: + cpu: 10m + memory: 16Mi + limits: + memory: 64Mi + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: nginx-server +spec: + type: ClusterIP + selector: + app: nginx-server + ports: + - name: http + port: 80 + targetPort: http +``` + +### Add the backend to NetBird + +The proxy reaches the backend through a routing peer, so the backend has to exist as a network resource first: + +1. **Deploy a routing peer** in the cluster. Follow [OpenShift Installation](/get-started/install/openshift) to run the rootless UBI client, preferably with a [persistent peer identity](/get-started/install/openshift#persistent-peer-identity) so the routing peer assignment survives restarts. +2. **Create a network and a domain resource** with the address `nginx-server.netbird-proxy.svc.cluster.local` (replace `netbird-proxy` if you changed `NAMESPACE`), and assign the UBI client as its routing peer. Any pod in the cluster can resolve and reach this Service. See [Networks](/manage/networks). +3. **Add an access policy** that lets the proxy's embedded peer reach the resource. The proxy joins your NetBird network as a peer when it receives its first service mapping. See [Access Control](/manage/access-control/manage-network-access). +4. **Create the reverse proxy service** under **Reverse Proxy** > **Services** > **Add Service**. See the [Reverse Proxy quick start](/manage/reverse-proxy#quick-start) for the full dialog. + + | Field | Value | + |-------|-------| + | Subdomain | `nginx` | + | Base domain | Your cluster domain, `proxy.example.com` | + | Mode | **HTTP** | + | Target | The `nginx-server` **Domain** resource | + | Protocol | **HTTP** | + | Port | `80` | + | Authentication | As needed. See [Authentication](/manage/reverse-proxy/authentication). | + +The proxy requests a certificate for a service hostname only when management sends it the service mapping, so this last step is what triggers issuance. A DNS record or a Route alone does not register a service. + +**Checkpoint.** The service under **Reverse Proxy** > **Services** moves from `certificate_pending` to `active`, and the proxy logs contain `certificate for domain "nginx.proxy.example.com" ready`. + + + Until the first certificate is ready, the proxy logs `TLS handshake error ... acme/autocert: missing certificate` for connections that arrive through the router or load balancer, and may log a single `orderNotReady` error while issuance completes. These warnings are expected and stop once the certificate is issued. If they continue for more than a few minutes, see [Troubleshooting](#troubleshooting). + + +## Step 5: Verify end to end + +From outside the cluster, check HTTPS without disabling certificate verification: + +```bash +curl --head --show-error --connect-timeout 10 "https://$SERVICE_HOSTNAME/" +``` + +HTTPS must present a trusted certificate for the service hostname. An authentication redirect or denial is expected if you enabled authentication on the service; after authenticating in a browser, the nginx welcome page confirms the backend is reachable over the overlay. + +Optionally, confirm the pod was admitted under `restricted-v2` with an OpenShift-assigned UID: + +```bash +oc get pods -n "$NAMESPACE" -l app.kubernetes.io/name=netbird-proxy \ + -o 'custom-columns=NAME:.metadata.name,SCC:.metadata.annotations.openshift\.io/scc,UID:.spec.containers[0].securityContext.runAsUser' +``` + +## Operating the proxy + +### Upgrading + +Change the image tag in `netbird-proxy.yaml` and re-apply it. Because the Deployment uses `Recreate`, the old pod stops before the new one starts, so expect a short interruption for every service on this cluster. The PVC keeps the issued certificates, so the new pod does not request new ones. + +### Running more than one replica + +This configuration runs a single replica because the certificate cache is on a `ReadWriteOnce` volume. Do not raise `replicas` as-is. For high availability, run additional instances with the same cluster domain, each with its own certificate volume, or switch to a shared wildcard certificate as described in [Using cert-manager instead of ACME](#using-cert-manager-instead-of-acme). See [Running Multiple Proxy Instances](/selfhosted/maintenance/scaling/multiple-proxy-instances#tls-certificate-management) for the trade-offs. + +### Using cert-manager instead of ACME + +If cert-manager already issues certificates in your cluster, you can mount its TLS Secret at `/certs` as a read-only volume, set `NB_PROXY_ACME_CERTIFICATES=false`, and drop the PVC and lock settings. The certificate must cover the cluster domain and `*.{cluster domain}` for the proxy to serve every service hostname. Choose one certificate-management mode for the Deployment rather than combining both. See [TLS certificate configuration](/manage/reverse-proxy#tls-certificate-configuration). + +### Using http-01 instead of tls-alpn-01 + +This guide uses `tls-alpn-01` because it needs only TCP 443. Switch to `http-01` only if your ACME CA or network policy requires it. + +With `http-01`, Let's Encrypt validates each service hostname by connecting to it on **public port 80**. The proxy answers on a separate plain-HTTP listener set by `NB_PROXY_ACME_ADDRESS`. The UBI image sets it to `:8081`, because a non-root container under `restricted-v2` cannot bind ports below 1024. **Public port 80 must reach container port 8081.** If only TCP 443 is exposed, as in the rest of this guide, every validation fails and services stay in `certificate_pending`. + +1. In the Deployment, change the challenge type, set the challenge address explicitly, and expose the port: + + ```yaml + env: + - name: NB_PROXY_ACME_CHALLENGE_TYPE + value: http-01 + - name: NB_PROXY_ACME_ADDRESS + value: :8081 + ports: + - name: acme-http + containerPort: 8081 + ``` + +2. Add port 80 to the `netbird-proxy` Service, next to the existing `https` port: + + ```yaml + ports: + - name: https + port: 443 + targetPort: https + - name: http + port: 80 + targetPort: acme-http + ``` + +3. Re-apply the manifest and confirm the load balancer forwards both TCP 80 and TCP 443 from the Internet. + +The challenge listener answers only ACME requests and redirects all other HTTP requests to HTTPS, so opening port 80 does not expose services over plain HTTP. + + + `http-01` requires the [TCP LoadBalancer](#preferred-exposure-a-tcp-load-balancer) exposure. It does not work with the TLS-passthrough Route fallback: a passthrough Route only carries TLS traffic on port 443, and the router does not forward plain HTTP on port 80 to the proxy for that hostname. Use `tls-alpn-01` with Routes. + + +### Removing the deployment + +Delete the NetBird services that use this cluster in the dashboard first, then remove the OpenShift resources: + +```bash +oc delete namespace "$NAMESPACE" +``` + +This also deletes the certificate PVC. If you plan to redeploy with the same hostnames soon, delete the Deployment, Service, and Routes instead and keep the PVC to avoid re-issuing certificates. + +## Troubleshooting + +The rows follow the order in which things happen: pod start, management connection, public exposure, certificate issuance, and backend traffic. + +| Symptom | What to check | +|---------|---------------| +| Pod fails with a permissions error under `/certs` or `/var/lib/netbird` | Confirm the pod runs under `restricted-v2` with no fixed `runAsUser` or `fsGroup`, that the PVC is `Bound` and mounted read-write at `/certs`, and that the StorageClass supports pod volume permissions. Do not add privileged init containers or relax the SCC to work around it. | +| Proxy logs `Unauthenticated` right after start | Wrong or revoked token, or a token from a different management server than the `management-address` in the Secret. Update the Secret and restart the Deployment. | +| Rollout times out; management gRPC logs HTML `404`/`502` responses | The management server's reverse proxy is not routing the `ProxyService` gRPC path. See [Prepare the management server for cross-host proxies](/selfhosted/maintenance/scaling/multiple-proxy-instances#prepare-the-management-server-for-cross-host-proxies). | +| Logs repeat `management connection failed ... PROTOCOL_ERROR` followed by `Initial mapping sync complete`, about once a minute | The rollout succeeds, but the management server's reverse proxy or load balancer cuts long-lived HTTP/2 streams after an idle timeout. Disable or raise that timeout for the `ProxyService` gRPC path. See [Prepare the management server for cross-host proxies](/selfhosted/maintenance/scaling/multiple-proxy-instances#prepare-the-management-server-for-cross-host-proxies). | +| Rollout times out with no management errors | Check egress from the namespace to the management server, signal, and relays, including any NetworkPolicy or egress firewall. | +| `EXTERNAL-IP` stays `` on the LoadBalancer Service | The cluster has no working LoadBalancer provider. Use the [Route fallback](#fallback-without-a-load-balancer-tls-passthrough-routes) instead, and switch the Service back to `ClusterIP` first. | +| The cluster's wildcard certificate is returned for the service hostname | Traffic reaches the router but not the proxy. Check the exact Route `host`, its admission status, the target Service and port, and that termination is `passthrough`. | +| Service stays in `certificate_pending`, or ACME reports `no viable challenge type found` | Validation failed; the error does not only mean an unsupported challenge type. Check the public `A`/`AAAA`/`CNAME` records, end-to-end TCP 443 to the proxy, and, for the Route fallback, that a Route exists for this exact hostname. Also confirm the hostname is under the cluster domain or a verified [custom domain](/manage/reverse-proxy/custom-domains) attached to this cluster, and that any [CAA records](/manage/reverse-proxy/custom-domains#check-your-caa-records) allow `letsencrypt.org`. | +| With `http-01`, services stay in `certificate_pending` | Let's Encrypt could not reach public port 80. Check that the Service maps port 80 to container port 8081, that the load balancer forwards TCP 80, and that you are not using the Route fallback. If the logs show `ACME HTTP-01 challenge server failed` with a permission error, `NB_PROXY_ACME_ADDRESS` is a port below 1024; set it to `:8081`. See [Using http-01 instead of tls-alpn-01](#using-http-01-instead-of-tls-alpn-01). | +| Cannot write certificates or acquire a lock | Check that the PVC is bound and mounted read-write at `/certs`, and that `NB_PROXY_CERT_LOCK_METHOD` is `flock`. | +| HTTPS works but nginx is unreachable, or the service stays in `tunnel_not_created` | Check the routing peer is connected, the access policy covers the proxy's embedded peer, the resource address resolves from the routing peer, and the target uses HTTP on port 80. See [Reverse Proxy Troubleshooting](/manage/reverse-proxy/troubleshooting). | + +Do not repeatedly delete the certificate PVC or restart the proxy to retry issuance before fixing DNS or public routing. Each attempt counts against Let's Encrypt rate limits. + +## Related pages + +- [OpenShift Installation](/get-started/install/openshift) - run the rootless UBI NetBird client on OpenShift, including as a routing peer +- [Bring Your Own Proxy](/manage/reverse-proxy/bring-your-own-proxy) - account-scoped proxy tokens and clusters on NetBird Cloud +- [Enable Reverse Proxy](/selfhosted/migration/enable-reverse-proxy) - management-wide proxy tokens and the full environment variable reference +- [Running Multiple Proxy Instances](/selfhosted/maintenance/scaling/multiple-proxy-instances) - management server preparation for remote proxies and HA patterns +- [Custom Domains](/manage/reverse-proxy/custom-domains) - use your own domain for service hostnames +- [Reverse Proxy Troubleshooting](/manage/reverse-proxy/troubleshooting) - diagnose backend connectivity problems diff --git a/src/pages/manage/reverse-proxy/troubleshooting.mdx b/src/pages/manage/reverse-proxy/troubleshooting.mdx index 188e1653..dc409ce0 100644 --- a/src/pages/manage/reverse-proxy/troubleshooting.mdx +++ b/src/pages/manage/reverse-proxy/troubleshooting.mdx @@ -308,6 +308,20 @@ Once you regain access to the dashboard, you can re-enable geo-restrictions on t - Leave geo-restrictions disabled on services that the management server needs to reach internally (such as your IdP). - Use a separate network path for the IdP that doesn't pass through the reverse proxy's geo-restriction layer. +### Issue 4: TCP service is active but connections get no response + +**Symptoms**: +- A TCP service shows as active and its listen port accepts connections +- Clients connect but receive no response + +**Cause**: + +A service's status reflects the proxy's listener, not the backend: a TCP service stays active and keeps accepting connections even when nothing listens on its target port. + +**Solution**: + +Check the backend, not the service status: on the target host, confirm that the application is listening on the target port and binds to an interface the proxy can reach (step 6 of the [Quick Diagnostics Checklist](#quick-diagnostics-checklist), and Issue 2). + ## Advanced Debugging with Packet Capture If the checks above don't reveal the issue, you can use packet capture tools to verify whether traffic is actually arriving on the routing peer or target machine. This is especially useful for diagnosing routing, firewall, or NAT issues. diff --git a/src/pages/manage/settings/clients.mdx b/src/pages/manage/settings/clients.mdx index ef70039d..938c2b76 100644 --- a/src/pages/manage/settings/clients.mdx +++ b/src/pages/manage/settings/clients.mdx @@ -1,5 +1,5 @@ export const description = - 'Configure account-wide client behavior: automatic updates, lazy connections, and exposing services from the CLI.' + 'Configure account-wide client behavior: automatic updates, lazy connections, exposing services from the CLI, and unique peer hostnames.' # Client Settings @@ -29,6 +29,24 @@ Instead of maintaining always-on connections, NetBird can activate peer connecti See [Lazy Connections](/manage/peers/lazy-connection) for how activation works, inactivity thresholds, and per-client overrides. +## Unique Peer Hostnames + +Two machines in one account can register with the same hostname, for example similar VMs that a provisioning tool all named `laptop`. Every peer still needs its own DNS name, so by default NetBird keeps the registration and adds the last two octets of the peer's NetBird IP to the DNS label: a second `laptop` that gets the IP `100.64.12.7` becomes `laptop-12-7.netbird.cloud`. Renaming a peer to a name another peer already uses gets the same suffix. + +The suffix keeps every peer reachable, but it also hides the duplicate. If you want a registration with a duplicate hostname to fail loudly instead, enable the `Reject Hostname Collisions` toggle. With the toggle on: + +- A peer registering with a hostname that another peer in the account already uses is rejected. The client reports `peer with hostname "laptop" is already registered`, stops retrying, and stays disconnected until it logs in again with a different hostname. +- Renaming a peer in the dashboard or through the API to a name another peer already uses fails with the same error. +- Ephemeral peers are treated exactly like other peers. + +Collisions are matched on the DNS label, not the raw hostname. The label is the hostname lowercased with characters that are not valid in DNS replaced by `-`, so `Laptop` and `laptop` collide, and so do `my_laptop` and `my-laptop`. + +Enabling the toggle does not touch existing peers: labels that already carry a suffix keep it, and a peer logging in again under its own name is not a collision. Deleting a peer frees its name for the next registration. To let a rejected peer join, give the machine a unique hostname or start the client with one: `netbird up --hostname `. + +Keep the toggle off if your fleet registers machines with identical hostnames on purpose, such as autoscaled VMs or containers that join through a [setup key](/manage/peers/register-machines-using-setup-keys). The default suffix exists for that case, and with the toggle on every such machine after the first fails to join. + +This setting is available on NetBird Cloud and on self-hosted management servers running v0.81.0 or later. In the API it is the `peer_hostname_collision_rejected` field of the [account settings](/ipa/resources/accounts). + ## Agent Network-focused view Accounts using [Agent Network](/agent-network) see an additional `Agent Network focused view` toggle. When enabled, the dashboard shows only the Agent Network-related sections and hides parts that are not relevant for it, such as Networks, DNS, and Reverse Proxy. Disable it to bring back the full dashboard. diff --git a/src/pages/manage/settings/delete-account.mdx b/src/pages/manage/settings/delete-account.mdx index a5001940..e63e0143 100644 --- a/src/pages/manage/settings/delete-account.mdx +++ b/src/pages/manage/settings/delete-account.mdx @@ -19,7 +19,7 @@ To delete your NetBird organization account, you must be a user with the [owner After you delete your account, your session will end, and you will be redirected to the login page. - If you log in again, a new account will be created. + If you log in again, a new account will be created. The exception on NetBird Cloud is an email domain that already belongs to another account, either as its signup domain or as a verified [sign-in domain](/manage/team/sign-in-domains). In that case you join that account instead, and may need an admin to approve you. ## Get started

diff --git a/src/pages/manage/settings/plans-and-billing.mdx b/src/pages/manage/settings/plans-and-billing.mdx index b4976517..eadfb7f5 100644 --- a/src/pages/manage/settings/plans-and-billing.mdx +++ b/src/pages/manage/settings/plans-and-billing.mdx @@ -11,6 +11,10 @@ It features peer-to-peer connections and encryption, access control, routing, an - **Business Plan:** At **€12 per user per month**, the Business Plan offers enhanced network security with a Zero Trust approach. It supports unlimited users and includes features like device approvals and integrations (MDM and EDR) for comprehensive control, device posture checks and activity events streaming, making it an excellent choice for organizations seeking advanced security solutions. + + These plans apply to NetBird Cloud. If you self-host NetBird, the paid features (such as IdP provisioning, device approvals, MDM and EDR integrations, traffic events logging, and event streaming) come from an [Enterprise Commercial License](/selfhosted/enterprise) instead. See the [on-prem pricing page](https://netbird.io/pricing#on-prem). + +

pricing-overview diff --git a/src/pages/manage/team/add-users-to-your-network.mdx b/src/pages/manage/team/add-users-to-your-network.mdx index b4615b2f..b8ca19d1 100644 --- a/src/pages/manage/team/add-users-to-your-network.mdx +++ b/src/pages/manage/team/add-users-to-your-network.mdx @@ -24,6 +24,8 @@ The domain can fall into one of the following categories - `public`, `private`, The domains of the private category are the ones that are automatically grouped under the same account. Public domains are the ones of the public email providers like Gmail. +Users on any other domain your organization uses, such as `company.co.uk` or a subsidiary's domain, don't join automatically. Add and verify those domains as [sign-in domains](/manage/team/sign-in-domains) so their users join your account too. + It might happen (unlikely) that the domain classification system didn't classify your company's domain as private. Our system was unsure about your domain and assigned an unclassified or public category to be on the safe side. @@ -43,8 +45,9 @@ After logging in to the system, they will join your network automatically.

- If a user already has a NetBird account, you can't invite them. - This is a limitation that is likely to be removed in future versions. + On NetBird Cloud, you can't invite someone who already has a NetBird account, for example one they created by signing in before you invited them. The invite fails with a `409` error. See [User already belongs to another NetBird account](/help/troubleshooting-user-in-another-account) to move them into your account. + + On self-hosted NetBird with the embedded identity provider, a `409` means the email address already belongs to a user in your account. Look for them under **Team → Users** instead. ## Identity Provider (IdP) Sync diff --git a/src/pages/manage/team/approve-users.mdx b/src/pages/manage/team/approve-users.mdx index e96ed584..ad9f3952 100644 --- a/src/pages/manage/team/approve-users.mdx +++ b/src/pages/manage/team/approve-users.mdx @@ -4,6 +4,10 @@ import {Note} from "@/components/mdx"; The user approval feature enhances security by requiring manual administrator approval before a user can join your NetBird organization via domain matching. New NetBird Cloud accounts have user approval enabled by default. + +Signed in and waiting for approval yourself? See [Pending approval and account invitations](/help/troubleshooting-account-access) for what the Waiting for Approval screen means and what to do next. + + ## Require user approval Navigate to the Dashboard's `Settings` page and the `Authentication` tab and enable or disable `User Approval Required`. @@ -17,6 +21,8 @@ Navigate to the Dashboard's `Settings` page and the `Authentication` tab and ena ## Approve or reject user To approve a user, navigate to the [Users Page](https://app.netbird.io/team/users) and click the `Approve` or `Reject` button on the right side of the users table. +While they wait, the user sees a **Waiting for Approval** screen that names the account owner's email address, partly hidden, when NetBird can look it up. Otherwise it just asks them to contact the account owner. Any admin can approve them, not only the owner. Once approved, the user clicks **Refresh** on that screen to join the account. +

netbird-user-approval

diff --git a/src/pages/manage/team/idp-sync/index.mdx b/src/pages/manage/team/idp-sync/index.mdx index 9d3867b3..af7765f5 100644 --- a/src/pages/manage/team/idp-sync/index.mdx +++ b/src/pages/manage/team/idp-sync/index.mdx @@ -20,7 +20,11 @@ or update network configurations like [DNS](/manage/dns#distribution-groups), eliminating the need for manual grouping. - This feature is only available in the cloud version of NetBird in the [Team plan](https://netbird.io/pricing) and above. + This feature is available in NetBird Cloud on the [Team plan](https://netbird.io/pricing) and above, and in self-hosted deployments with an [Enterprise Commercial License](/selfhosted/enterprise). The open source Community Edition does not include it. + + + + **Before you roll out on NetBird Cloud**: IdP sync can't add a user who already belongs to another NetBird account, for example because they signed in early with an email domain your account doesn't know yet. Add every domain your users sign in with as a [sign-in domain](/manage/team/sign-in-domains) first. If a user still doesn't sync, see [User already belongs to another NetBird account](/help/troubleshooting-user-in-another-account). This video guide walks you through an example integration with Microsoft Entra ID, covering both user onboarding and diff --git a/src/pages/manage/team/idp-sync/okta-sync.mdx b/src/pages/manage/team/idp-sync/okta-sync.mdx index 26379f10..b9c243b6 100644 --- a/src/pages/manage/team/idp-sync/okta-sync.mdx +++ b/src/pages/manage/team/idp-sync/okta-sync.mdx @@ -18,9 +18,7 @@ to synchronize users and groups smoothly. ## Get Started with NetBird-Okta Integration -To set up SSO, go to `Integrations` in the NetBird admin console's left menu to access the Identity Provider integration page. Click the `Connect Okta` button to get started with the Okta-NetBird integration. This will open a pop-up window with detailed instructions on synchronizing NetBird and Okta. - -![The Okta card on the Identity Provider Sync tab with its Connect Okta button](/docs-static/img/manage/team/idp-sync/okta-sync/nwutb3Z.png) +To set up SSO, open `Integrations` in the NetBird admin console's left menu and select the `Single Sign-On` tab. On the Okta card, click `Connect Okta`. This opens the `Connect NetBird with Okta SSO` wizard, which walks you through the rest of the SSO setup. Its first screen lists the Okta permissions you need, covered in the prerequisites below. You set up SCIM later, from the `Identity Provider Sync` tab. ## Prerequisites @@ -41,7 +39,7 @@ Confirm that you have one of the required roles before proceeding with the integ ## Installing the NetBird Integration -Once you have the necessary permissions, you can set up the NetBird application. First, on NetBird, click `Continue →` to show a summary of the necessary steps. +Once you have the necessary permissions, you can set up the NetBird application. In the NetBird wizard, click `Get Started →`. The wizard shows the `Install NetBird application for Okta` steps. Let's go through them one by one: @@ -57,15 +55,15 @@ You will see a list of users. Find your user account, click `Assign`, and save t ## Configuring SSO in Okta -The next step is to configure Okta-NetBird SSO integration. +The next step is to collect Okta's OpenID Connect details and enter them in NetBird. SSO setup is self-service: you don't need to send these details to the NetBird team. -In NetBird, click the `Continue →` button. A new wizard screen will appear, offering the instructions for retrieving Okta’s OpenID Connect credentials. You can click `Close` and navigate to Okta. +In NetBird, click the `Continue →` button. The wizard shows the `Enter your Okta details` form. Keep it open and switch to Okta to collect the values. * Click on the `Sign On` tab on Okta. Look for `OpenID Connect` under `Sign on methods` in the `Settings` section. * Copy the `Client ID` value. * Copy the `Client Secret` value. -Store these credentials securely, as you will need them soon. +Treat the `Client Secret` like a password. Paste it straight into the NetBird wizard, and don't share it over email or chat. ![The Sign On tab of the NetBird app in Okta, showing the OpenID Connect Client ID and Client Secret](/docs-static/img/manage/team/idp-sync/okta-sync/rl5Gelc.png) @@ -78,16 +76,26 @@ Store these credentials securely, as you will need them soon. * On the top right, click on your username * Copy your [Okta account domain](https://developer.okta.com/docs/guides/find-your-domain/main/), shown under your email address in that menu, for example `trial-1234567.okta.com`. -The final step is to [send an email to the NetBird team](support@netbird.io) with the authentication information you just retrieved: +Back in the NetBird wizard, fill in the form with the values you collected: -* Okta `Client ID` -* Okta `Client secret` -* Okta account domain -* Okta primary email domain (usually your username) +* `Client ID` and `Client Secret`: the values from the Okta `Sign On` tab. +* `Okta account domain`: for example `trial-1234567.okta.com`. +* `Primary E-Mail Domain`: the domain of the email addresses your users sign in with, for example `mycompany.com`. -You will receive an email once the NetBird team enables authentication for your account. +The Enter your Okta details step of the Connect NetBird with Okta SSO wizard, with fields for Client ID, Client Secret, Okta account domain, and Primary E-Mail Domain, and the Connect button -This completes the first stage, enabling Single Sign-On (SSO) from NetBird's login page using Okta credentials. Now, you can navigate to [app.netbird.io](app.netbird.io) and log in using [Okta Verify](https://help.okta.com/eu/en-us/content/topics/end-user/ov-overview.htm). +Click `Connect`. If your primary email domain isn't verified yet, NetBird asks you to prove you own it. The `Verify Domain Ownership` dialog shows a TXT record to add to your DNS: + +* `Host`: your primary email domain, for example `mycompany.com`. +* `Value`: `nb-verification=` followed by a token unique to your account. Copy it from the dialog. + +Sign in to your DNS provider, add the TXT record, then click `Start Verification`. + +DNS changes can take a while to apply. If NetBird doesn't find the record straight away, click `Verify Later` and try again once the record has propagated: on the `Single Sign-On` tab, click `Settings` on the Okta card, open the `Domains` tab, and click `Verify` next to the domain. If you can't edit DNS for the domain, you can verify it by emailing [support@netbird.io](mailto:support@netbird.io) from an address on that domain instead. + +When the domain shows `Ownership Verified`, the Okta card on the `Single Sign-On` tab shows `Active`. + +This completes the first stage, enabling Single Sign-On (SSO) from NetBird's login page using Okta credentials. Now, you can navigate to [app.netbird.io](https://app.netbird.io) and log in with your Okta account. ## Enabling Okta SCIM in NetBird diff --git a/src/pages/manage/team/sign-in-domains.mdx b/src/pages/manage/team/sign-in-domains.mdx new file mode 100644 index 00000000..faa24739 --- /dev/null +++ b/src/pages/manage/team/sign-in-domains.mdx @@ -0,0 +1,85 @@ +import {Note, Warning} from "@/components/mdx" + +export const description = + 'Users on your business email domain join your account automatically. Add and verify the other domains your business uses, so their users join the same account instead of creating one of their own.' + +# Allow Users from Other Domains to Join Your Account + +By default, users who sign in with the same business email domain as yours join the same account. +For example, everyone on `@company.com` lands in your network, with the same peers and policies. + +In most cases a business uses more than one domain: a country domain like `company.co.uk`, a subsidiary like `company-berlin.de`, or a second brand. +A colleague signing in as `jane@company.co.uk` is not recognized as yours unless you [invite them manually](/manage/team/add-users-to-your-network), so they +get an account of their own. + +**Sign-in domains** allow users from other domains to join your account without manually inviting them beforehand. +You prove ownership of those domains, and everyone across your organization joins the same account automatically. + + + **Availability**: Sign-in Domains is only available on NetBird Cloud. + + + **Permissions**: Only an Owner or Admin can manage sign-in domains. Learn more about [user roles](/manage/team/user-roles). + + + + Verifying a domain does not move users who already have an account of their own. It decides where **new** users land, so add your domains before onboarding a team from another domain rather than after. + + If someone already has an account of their own, inviting or syncing them fails. See [User already belongs to another NetBird account](/help/troubleshooting-user-in-another-account) to move them into yours. If they don't know where that account came from, have them reach out to [NetBird Support](/help/netbird-support). The team verifies their identity and points them to whoever administers that account. + + +## Add Domain + +Go to **Settings → Authentication**, find **Sign-in Domains**, then click **Add** and enter the domain, e.g. `company.co.uk`. + +

+ The Sign-in Domains section of the Authentication tab, listing the account's domains with their verification status +

+ +The domain starts as **Pending** until you prove you own it. + +## Verify Domain + +Click **Verify** on the pending domain. NetBird shows a TXT record to publish at the root of the domain, so on `company.co.uk` itself rather than `www.company.co.uk`. + +

+ The verification dialog showing the TXT record name and value to publish in DNS +

+ +Add the record, then click **Start Verification**. NetBird keeps checking on its own, so you can leave the page. The domain becomes **Verified** once the record is found. If it never appears the domain ends up **Unverified**, and **Verify** starts the checks again. + +If you cannot change DNS, contact [NetBird Support](/help/netbird-support) from an email address on the domain instead, and the team will verify it for you. + + + A domain can only be verified by one account. If another account already holds it, your checks keep failing — contact [NetBird Support](/help/netbird-support). + + +Once the domain is Verified, a user signing in for the first time with an address on it joins your account. + +

+ The NetBird login page, where a user signing in with an email address on a verified domain joins the existing account +

+ +## Remove Domain + +Click the delete icon on the domain's row. The domain you signed up with cannot be removed, and neither can a domain an SSO integration still uses. + +Removing a domain does not remove or lock out users who already joined through it. It only stops matching new sign-ins, who get accounts of their own again. + +## Recap + +- A sign-in domain tells NetBird that an email domain belongs to your account, so new users join your network instead of creating one of their own. +- Your signup domain is there from the start and cannot be removed; add and verify any others yourself. +- Verification is a TXT record at the root of the domain, re-checked automatically until it is found. +- It changes where new users land, not where existing ones already are. + +## Related Docs + +- [Authenticate to NetBird with Single Sign-On](/manage/team/single-sign-on) +- [Add users to your network](/manage/team/add-users-to-your-network) +- [User roles](/manage/team/user-roles) +- [Approve users](/manage/team/approve-users) +- [Provision users and groups from your IdP](/manage/team/idp-sync) diff --git a/src/pages/manage/team/single-sign-on/authentik.mdx b/src/pages/manage/team/single-sign-on/authentik.mdx index 0abcc99d..84fbf01e 100644 --- a/src/pages/manage/team/single-sign-on/authentik.mdx +++ b/src/pages/manage/team/single-sign-on/authentik.mdx @@ -7,6 +7,7 @@ You can use Authentik as your Identity Provider with NetBird, but it will requir Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). 1. You need to create a new Application and Provider. diff --git a/src/pages/manage/team/single-sign-on/aws-cognito.mdx b/src/pages/manage/team/single-sign-on/aws-cognito.mdx index d12149e3..ae7a3908 100644 --- a/src/pages/manage/team/single-sign-on/aws-cognito.mdx +++ b/src/pages/manage/team/single-sign-on/aws-cognito.mdx @@ -7,6 +7,7 @@ You can use [AWS Cognito](https://aws.amazon.com/cognito/) as your Identity Prov Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Prerequisites diff --git a/src/pages/manage/team/single-sign-on/cidaas.mdx b/src/pages/manage/team/single-sign-on/cidaas.mdx index dd9dd64c..b2428605 100644 --- a/src/pages/manage/team/single-sign-on/cidaas.mdx +++ b/src/pages/manage/team/single-sign-on/cidaas.mdx @@ -7,6 +7,7 @@ You can use [cidaas](https://www.cidaas.com/) as your Identity Provider with Net Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Step 1: Create a New Application diff --git a/src/pages/manage/team/single-sign-on/duo-security.mdx b/src/pages/manage/team/single-sign-on/duo-security.mdx index 62479f74..1428ed0f 100644 --- a/src/pages/manage/team/single-sign-on/duo-security.mdx +++ b/src/pages/manage/team/single-sign-on/duo-security.mdx @@ -7,6 +7,7 @@ You can use Duo Security as your Identity Provider with NetBird, but it will req Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Prerequisites diff --git a/src/pages/manage/team/single-sign-on/iij-id.mdx b/src/pages/manage/team/single-sign-on/iij-id.mdx index d36a879e..b98931a4 100644 --- a/src/pages/manage/team/single-sign-on/iij-id.mdx +++ b/src/pages/manage/team/single-sign-on/iij-id.mdx @@ -9,6 +9,7 @@ NetBird integrates with IIJ ID as a custom OpenID Connect application. Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Step 1: Create a Custom OpenID Connect Application diff --git a/src/pages/manage/team/single-sign-on/index.mdx b/src/pages/manage/team/single-sign-on/index.mdx index 1af0ae40..4f30e0db 100644 --- a/src/pages/manage/team/single-sign-on/index.mdx +++ b/src/pages/manage/team/single-sign-on/index.mdx @@ -44,6 +44,7 @@ to integrate with NetBird. Below are the steps to set up different OIDC-complian Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ### Authentik diff --git a/src/pages/manage/team/single-sign-on/zitadel.mdx b/src/pages/manage/team/single-sign-on/zitadel.mdx index 3d8cca3b..262f4106 100644 --- a/src/pages/manage/team/single-sign-on/zitadel.mdx +++ b/src/pages/manage/team/single-sign-on/zitadel.mdx @@ -7,6 +7,7 @@ You can use Zitadel as your Identity Provider with NetBird, but it will require Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Step 1: Create a New Project (Optional) diff --git a/src/pages/manage/team/single-sign-on/zoho.mdx b/src/pages/manage/team/single-sign-on/zoho.mdx index fca619b2..8d023821 100644 --- a/src/pages/manage/team/single-sign-on/zoho.mdx +++ b/src/pages/manage/team/single-sign-on/zoho.mdx @@ -9,6 +9,7 @@ NetBird integrates with Zoho as a custom OIDC application. Support for OIDC-compliant IdPs is available on the Team plan and higher. The Free plan supports Google, Microsoft, and social logins. + Self-hosted deployments support OIDC-compliant IdPs in both the open source Community Edition and the [Enterprise Commercial License](/selfhosted/enterprise). See [self-hosted identity providers](/selfhosted/identity-providers). ## Step 1: Create a Custom OIDC Application diff --git a/src/pages/manage/team/user-roles.mdx b/src/pages/manage/team/user-roles.mdx index a9a31f1d..fc6aa014 100644 --- a/src/pages/manage/team/user-roles.mdx +++ b/src/pages/manage/team/user-roles.mdx @@ -1,10 +1,10 @@ -import {Note} from "@/components/mdx" +import {Note, Warning} from "@/components/mdx" -export const description = "Understand NetBird's user roles — Owner, Admin, Network Admin, Billing Admin, Auditor, and User — what each can access, and how to assign them." +export const description = "Understand NetBird's user roles: Owner, Admin, Network Admin, Billing Admin, Auditor, User, Agent Network Admin, and Usage Viewer. Learn what each can access and how to assign them." # User Roles -NetBird has six user roles - `Owner`, `Admin`, `Network Admin`, `Billing Admin`, `Auditor`, and `User`. A user's role controls the level of access they have to your account, both in the dashboard and through the management API. +NetBird has eight user roles - `Owner`, `Admin`, `Network Admin`, `Billing Admin`, `Auditor`, `User`, and two roles scoped to [Agent Network](/agent-network), `Agent Network Admin` and `Usage Viewer`. A user's role controls the level of access they have to your account, both in the dashboard and through the management API. ## Permissions at a glance @@ -31,6 +31,23 @@ Rows are grouped by what a `Network Admin` can do, from full access down to no a 2 A `Billing Admin`'s Settings access is limited to **Plans & Billing** and **Invoices**. +## Agent Network permissions + +| Area | Owner | Admin | Auditor | Agent Network Admin | Usage Viewer | User | +| --: | :--: | :--: | :--: | :--: | :--: | :--: | +| Providers | ✅ | ✅ | 📖 | ✅ | 📖3 | ❌ | +| Policies & Guardrails | ✅ | ✅ | 📖 | ✅ | ❌ | ❌ | +| Global Limits | ✅ | ✅ | 📖 | ✅ | ❌ | ❌ | +| Usage | ✅ | ✅ | 📖 | ✅ | 📖 | 📖4 | +| Access Logs | ✅ | ✅ | 📖 | ✅ | 📖 | 📖4 | +| Configuration | ✅ | ✅ | 📖 | ✅ | ❌ | ❌ | + +`Network Admin` and `Billing Admin` have no Agent Network access beyond what every user gets. + +3 A `Usage Viewer` sees the provider list with connection config redacted: no upstream URLs and no operator-supplied header values. + +4 Every user, whatever their role, sees their own usage and their own requests on **Usage & Logs**. + ## Owner The `Owner` has full access to the account and can manage every aspect of it. There can be only one account owner in NetBird. Owners are the only users who can delete the organization account - see [Delete NetBird account](/manage/settings/delete-account) for more. @@ -49,8 +66,21 @@ An `Auditor` can read every configuration in the account but can't modify anythi ## User A `User` has limited access: they can view the peers they own and other peers they're allowed to connect to. +## Agent Network Admin +An `Agent Network Admin` has full control over Agent Network: providers, policies and guardrails, global limits, usage, access logs, and the configuration pages. They have read-only access to users, groups, peers, and account information, which they need to build policies. They have no access to any other part of the account, such as Control Center, Access Control, Network Routing, DNS, Setup Keys, or Reverse Proxy. The **Clusters** tab under Agent Network configuration stays hidden for this role because it requires Reverse Proxy permissions. + +## Usage Viewer +A `Usage Viewer` has read-only access to the Agent Network **Usage** overview and to the account-wide **Access Logs**. They can also read the provider list, with connection config redacted, and users, groups, and peers, which the filters and name columns rely on. They have no access to policies, guardrails, global limits, or Agent Network configuration. + + + When [prompt collection](/agent-network/usage-and-logs/log-collection) is enabled, access logs contain the captured prompts and completions of every user. Granting `Usage Viewer` lets that user read other users' prompts. + + +## Agent Network access for every user +Any signed-in user, whatever their role, sees their own usage and their own requests on **Usage & Logs**. Once an Agent Network policy covers them, they also see the **Connect** page, which lists the providers and models their policies allow. + ## Roles and the API -Roles apply the same way whether a user works in the dashboard or through the [NetBird management API](/api) - a user's permissions over API resources match their role. Every role except `User` can create a personal access token that carries the same permissions as that user's role, so they can interact with the API programmatically. +Roles apply the same way whether a user works in the dashboard or through the [NetBird management API](/api) - a user's permissions over API resources match their role. Every role except `User`, `Agent Network Admin`, and `Usage Viewer` can create a personal access token that carries the same permissions as that user's role, so they can interact with the API programmatically. ## Roles and identity provider sync @@ -62,7 +92,7 @@ To change a user's role, go to the `Team` tab, select the `Users` tab, and click

user list in the Team tab

-Select the desired role from the dropdown: +Select the desired role from the dropdown. `Agent Network Admin` and `Usage Viewer` are under the **Agent Network** tab:

user role dropdown

diff --git a/src/pages/selfhosted/enterprise/external-relays.mdx b/src/pages/selfhosted/enterprise/external-relays.mdx new file mode 100644 index 00000000..f5a7da96 --- /dev/null +++ b/src/pages/selfhosted/enterprise/external-relays.mdx @@ -0,0 +1,541 @@ +export const description = 'Move relay and STUN off your NetBird Enterprise server onto dedicated hosts, step by step, without breaking traffic event logging.' + +# External Relays on a Commercial License + +import {Note, Warning} from "@/components/mdx"; + +Your NetBird Enterprise server runs management, signal, relay and STUN in one `netbird-server` container. That works until relayed traffic starts competing with the control plane for the same host, or until users in another region relay through a server on the wrong continent. This guide moves the relay and STUN services onto dedicated hosts and points your server at them. + +One thing makes Enterprise different, and it is the reason this page exists rather than the [community guide](/selfhosted/maintenance/scaling/set-up-external-relays). When traffic flow is enabled, your deployment has a single shared secret doing **two** jobs: authenticating peers to the relay, and authenticating peers to the traffic-flow receiver. That is true however the deployment was built, whether installed fresh or migrated up from the community edition. Generate a new secret while following a guide that only knows about the first job, and your peers keep working perfectly while traffic event logging silently stops. Step 2 is where that is handled. + +## Before you start + +For each relay server you need: + +- A Linux VM with at least **1 CPU** and **1 GB RAM** +- A public IP address +- A domain name pointing at it, for example `relay-us.example.com` +- Docker and the Compose plugin installed +- These inbound ports open: **443/tcp** and **443/udp** (relay), **3478/udp** (STUN) + + +**No inbound port 80 is needed.** A relay proves its domain to Let's Encrypt over 443 itself, using the TLS-ALPN-01 challenge. Your main NetBird server is different and does still need 80, and so may a proxy in front of the relay. + +**Both 443 rules matter.** The relay serves two transports on that port: WebSocket over TCP and QUIC over UDP. Clients race both and keep whichever answers first. With 443/udp closed the QUIC attempt runs until it times out, WebSocket carries the connection, and nothing reports an error, so the missing rule is easy to overlook. + + +On the main server you need shell access to your deployment directory, which this guide calls `~/netbird-enterprise`, and the ability to restart containers there. + +This guide uses two relay servers, `relay-us.example.com` and `relay-eu.example.com`, and a main server at `netbird.example.com`. Substitute your own names throughout. One relay works fine; two is the smallest setup that shows the failover behavior. + +### Which names your deployment uses + +A deployment installed fresh with `getting-started-enterprise.sh` and one migrated up from the community edition with `migrate-to-enterprise.sh` use different names for the same things, and traffic flow is optional in both. On the main server, run: + +```bash +cd ~/netbird-enterprise +ls config.yaml.enterprise +docker compose config --services | grep -E '^(receiver|flow-receiver)$' +``` + +| | Fresh install | Migrated from the community edition | +|---|---|---| +| Server configuration | `config.yaml` | `config.yaml.enterprise` if `ls` lists it, otherwise `config.yaml` | +| Traffic-flow receiver service | `receiver` | `flow-receiver` | +| Receiver container | `netbird-receiver` | `netbird-flow-receiver` | +| Relay secret in `.env` | `NETBIRD_RELAY_AUTH_SECRET` | `NB_FLOW_AUTH_SECRET`, only if traffic flow was enabled | + +The commands on this page use the fresh-install names. On a migrated deployment, use the names from the second column instead. The configuration file matters most. `migrate-to-enterprise.sh` writes `config.yaml.enterprise` whenever it moves storage to PostgreSQL or enables traffic flow, and starts the server with that file instead of `config.yaml`. On such a deployment an edit to `config.yaml` has no effect. + +If the `grep` prints nothing, your deployment has no traffic-flow receiver. Skip the traffic event check in Step 1, all of Step 8, and the receiver commands in [Rotating to a new secret](#rotating-to-a-new-secret). Everything else on this page applies unchanged. + +## Step 1: Before you change anything + +**This is a cutover, not a gradual migration.** The moment you restart the server with the new configuration, the embedded relay stops and every peer that was using it reconnects to the external relays. There is no window where both are serving. So build and test the relay hosts completely, through the end of Step 5, before you touch the main server's configuration in Step 6. + +**Record whether traffic events are working now.** Step 8 checks that they survived the change, and that check can only tell you something if you know the answer beforehand. Generate a little traffic between two peers, for example `ping` one peer's NetBird IP from another, wait about 90 seconds, then on the main server run: + +```bash +cd ~/netbird-enterprise +docker compose logs --since 120s receiver | grep -c 'sending event' +``` + +Use `flow-receiver` on a migrated deployment, and skip this check if your deployment has no receiver ([Which names your deployment uses](#which-names-your-deployment-uses)). + +If it is `0` and the receiver still logs at its default level (`NB_FLOW_LOG_LEVEL` unset or `debug`), traffic events are not flowing **yet**, and that has nothing to do with relays. Traffic event logging is disabled by default at the account level even when the installer enabled traffic flow on the server: in the dashboard, go to `Settings > Networks` and turn on `Enable Traffic Events` under the Experimental section. See [Traffic Events Logging](/manage/activity/traffic-events-logging). Either enable it and re-run the check, or accept that Step 8 will have nothing to compare against. + +## Step 2: Read your existing shared secret + +**Do not generate a new secret.** Your deployment already has one, and the traffic-flow receiver, if you have one, uses the same value. Read it from the server configuration on the main server. The first line below picks `config.yaml.enterprise` when a migration created it, because that is the file the server reads, and the two files stop matching as soon as the secret is rotated: + +```bash +cd ~/netbird-enterprise +CONFIG=config.yaml; [ -f config.yaml.enterprise ] && CONFIG=config.yaml.enterprise +grep 'authSecret:' "$CONFIG" +``` + +Read it from the server configuration and not from `.env`. The server configuration carries this value on every deployment. `.env` does not: a deployment created by `getting-started-enterprise.sh` has it there as `NETBIRD_RELAY_AUTH_SECRET`, one migrated up from the community edition with traffic flow has `NB_FLOW_AUTH_SECRET` instead, and one migrated without traffic flow has neither, because the community installer never wrote the relay secret to `.env` in the first place. + +That value goes in two places later: `NB_AUTH_SECRET` on every relay host in Step 3, and `relays.secret` on the main server in Step 6. + + +`config.yaml` and `.env` are mode `600` and hold every secret in your deployment. Read the single line you need rather than opening the whole file, and keep the value out of shared terminal history. + + +If your policy requires a fresh secret, that works too, but it has more places to change than you would expect. Use [Rotating to a new secret](#rotating-to-a-new-secret) at the end of this page rather than improvising here. + +## Step 3: Create the relay configuration + +Do this on each relay server. + +```bash +mkdir -p ~/netbird-relay +cd ~/netbird-relay +``` + +Create `relay.env`. The relay can obtain and renew its own TLS certificate through Let's Encrypt: + +```bash +NB_LOG_LEVEL=info +NB_LISTEN_ADDRESS=:443 +NB_EXPOSED_ADDRESS=rels://relay-us.example.com:443 +NB_AUTH_SECRET=the-value-from-step-2 + +# TLS via Let's Encrypt +NB_LETSENCRYPT_DOMAINS=relay-us.example.com +NB_LETSENCRYPT_EMAIL=admin@example.com +NB_LETSENCRYPT_DATA_DIR=/data/letsencrypt + +# Embedded STUN (comma-separated for multiple ports, e.g. 3478,3479) +NB_ENABLE_STUN=true +NB_STUN_PORTS=3478 +``` + +Replace `relay-us.example.com` with this host's own domain and `the-value-from-step-2` with the secret you read. The file holds that secret, so make it readable by root only: + +```bash +chmod 600 relay.env +``` + +The relay reads `relay.env` when its container is created. If you change the file later on a running relay, apply it with `docker compose up -d`. `docker compose restart` keeps the old values. + +Create `docker-compose.yml`: + +```yaml +services: + relay: + image: netbirdio/relay:latest + container_name: netbird-relay + restart: unless-stopped + ports: + # Both transports. Docker reads a bare '443:443' as TCP only, + # so the UDP line is required for QUIC. + - '443:443/tcp' + - '443:443/udp' + # Expose every port listed in NB_STUN_PORTS + - '3478:3478/udp' + env_file: + - relay.env + volumes: + - relay_data:/data + logging: + driver: "json-file" + options: + max-size: "500m" + max-file: "2" + +volumes: + relay_data: +``` + +The relay image is the open source `netbirdio/relay`. A relay host takes no license key. + +### If you already have TLS certificates + +To use your own certificate instead of Let's Encrypt, delete the three `NB_LETSENCRYPT_*` lines from `relay.env` and add: + +```bash +NB_TLS_CERT_FILE=/certs/fullchain.pem +NB_TLS_KEY_FILE=/certs/privkey.pem +``` + +Then replace the relay service's `volumes:` list in `docker-compose.yml` with this one. Do not add it as a second `volumes:` key, which makes the file invalid: + +```yaml + volumes: + - /path/to/certs:/certs:ro + - relay_data:/data +``` + +`fullchain.pem` is your certificate followed by any intermediates, and the certificate must cover the relay's domain. Four things catch people out: + +- **Set both variables, and remove every `NB_LETSENCRYPT_*` line.** If a Let's Encrypt line remains, the relay uses Let's Encrypt and ignores your files. If only one `NB_TLS_*` variable is set, the relay starts with no TLS at all. Either way, the sign is a missing QUIC line in Step 4. +- **Nothing in the startup log names your certificate.** Step 5's `curl -v` is how you confirm it is the one being served. +- **The files are read once, at startup.** Restart the relay after every renewal: `docker compose restart relay`. +- **A private CA must be trusted by every peer.** NetBird checks the relay's certificate against each device's operating-system trust store, and has no setting for a separate CA. A device that does not trust yours reports the relay as `Unavailable` with `x509: certificate signed by unknown authority`, and cannot relay to other peers. Distribute the CA to every device before you switch. + +### If a load balancer or proxy sits in front of the relay + +In this shape your proxy holds the certificate and the relay serves plain HTTP behind it. + +Use this shape only because a load balancer or proxy is already in front of your relays. Do not add one just to terminate TLS on the relay host: the relay already does that itself, and a proxy in front of it costs you QUIC. + +**On the relay host**, `relay.env` has no `NB_LETSENCRYPT_*` lines, listens on an internal port, and names the proxy's address: + +```bash +NB_LOG_LEVEL=info +NB_LISTEN_ADDRESS=:8080 +NB_EXPOSED_ADDRESS=rels://relay-us.example.com:443 +NB_AUTH_SECRET=the-value-from-step-2 +NB_TRUSTED_PROXIES=10.20.0.10 +NB_ENABLE_STUN=true +NB_STUN_PORTS=3478 +``` + +Keep `rels://` written out in `NB_EXPOSED_ADDRESS`. With no TLS of its own, the relay would otherwise advertise `rel://`. + +In `docker-compose.yml`, remove the two `443` port lines and keep `3478:3478/udp` for STUN. Make port 8080 reachable from the proxy and nothing else. If the proxy is on another machine, publish the port with `'8080:8080/tcp'` and allow it at your firewall from the proxy's address only. + +**Your proxy must:** + +- hold a certificate for the relay's domain, and accept connections on 443/tcp; +- forward WebSocket upgrades over HTTP/1.1 to the relay's port 8080; +- set both `X-Real-Ip` and `X-Real-Port` to the address and source port of the connection it received, overwriting anything the client sent. + +The relay uses those two headers only when the connection comes from an address in `NB_TRUSTED_PROXIES`, and only when both are present. A proxy that sets only `X-Real-Ip` gets its own address logged for every peer. A proxy that passes the client's headers through lets any client write the address of its choice into your relay logs. + +`NB_TRUSTED_PROXIES` is the address the relay sees the proxy's connections come from, which is not always the proxy's public address. When the proxy and relay share a Docker network, it is the proxy container's address on that network. When the proxy is another machine on the same private network, it is that machine's private address: a published Docker port keeps the original source address. + + +Trust only the smallest proxy ranges you control. Never `0.0.0.0/0`, `::/0`, or a shared network where an untrusted system could reach the relay directly and forge these headers. Entries are IP addresses or CIDRs separated by commas. Hostnames are rejected, and a prefix such as `10.20.0.10/24` trusts the whole `/24`. + + +These headers decide only which client address the relay records, including in the `invalid signature` lines used for troubleshooting. Authentication does not depend on them. + +Apply a change to `NB_TRUSTED_PROXIES` with `docker compose up -d relay`. An invalid entry stops the relay: it exits with `failed to parse trusted proxies: ...` and restarts in a loop until you fix it. To confirm the setting works, check that the relay logs your peers' addresses rather than the proxy's: + +```bash +docker compose logs relay | grep 'WS client connected from' +``` + +The CLI equivalent of `NB_TRUSTED_PROXIES` is `--trusted-proxies`, available since **v0.75.0**. + +At startup the relay reports that it cannot serve QUIC: + +``` +WARN relay/server/server.go:78: Not starting QUIC listener: valid TLS config is required for QUIC listener +``` + +That is expected here. Peers connect over WebSocket, so neither the proxy nor the relay host needs 443/udp. The proxy needs 443/tcp, plus whatever it uses to obtain its own certificate, often 80/tcp. The relay host needs 3478/udp for STUN. + +## Step 4: Start each relay + +```bash +docker compose up -d +docker compose logs -f +``` + +You should see the relay announce its address, both listeners, and the STUN server: + +``` +INFO relay/cmd/root.go:242: server will be available on: rels://relay-us.example.com:443 +INFO relay/server/listener/ws/listener.go:51: WS server listening address: :443 +INFO relay/server/listener/quic/listener.go:39: QUIC server listening on address: :443 +INFO [component: stun] stun/server.go:71: STUN server listening on [::]:3478 +``` + +Other lines appear alongside these, including the Let's Encrypt setup, the health check server and the metrics server. The order changes from one start to the next, because the listeners come up concurrently. What matters is that all four lines are present, not where they sit. + +A missing QUIC line means the relay has no TLS configuration of its own, which is expected only behind a TLS-terminating proxy. + +## Step 5: Check each relay from outside + +Certificates are issued lazily on the first request, so this both provisions and verifies: + +```bash +curl -v https://relay-us.example.com/ +``` + +A `404 page not found` is the correct response. What matters is that the TLS handshake succeeded: + +``` +* Server certificate: +* subject: CN=relay-us.example.com +* issuer: C=US; O=Let's Encrypt; CN=E8 +* SSL certificate verify ok. +``` + +The issuer's `CN` names whichever intermediate signed your certificate, so yours will often differ. + +Two things can go wrong here, and they look different. If the first attempt fails with a TLS error such as `SSL_ERROR_SYSCALL`, wait a few seconds and run it again: that first request is what triggers issuance, and it can time out while that happens. + +If instead the command hangs with no output and no error, issuance is stuck rather than slow. The relay reports the reason and `curl` cannot: + +```bash +docker compose logs relay | grep -i acme +``` + +A rate limit is the likeliest cause if you have rebuilt the same relay host several times. Let's Encrypt allows five certificates per week for one exact set of domain names, and the error names the time it frees up again. + +Repeat Steps 3 to 5 on every relay server, using the **same** `NB_AUTH_SECRET` and that host's own domain name. + +Do not continue until every relay answers. Step 6 is the cutover. + +## Step 6: Point the main server at the relays + +On the main server, take a copy of the current configuration first, then edit it: + +```bash +cd ~/netbird-enterprise +cp config.yaml config.yaml.bak +nano config.yaml +``` + +On a migrated deployment that has `config.yaml.enterprise`, copy and edit that file instead. It is the one the server reads, and changes made to `config.yaml` never reach it. + +The backup matters because a mistake here does not announce itself: the deployment keeps working and only traffic event logging stops. + +Add `stuns` and `relays` inside the existing `server:` block, at the same indentation as the keys already there. Anywhere inside that block works; the example below puts them after `authSecret` to keep the relay settings together. Everything else already under `server:` stays exactly as it is, including the `auth`, `reverseProxy`, `store`, `activityStore` and `trafficFlow` sections a `getting-started-enterprise.sh` deployment carries. Setting `relays.addresses` is what turns the embedded relay off, and it turns the embedded STUN server off too, which is why `stuns` is required: + +```yaml +server: + listenAddress: ":80" + exposedAddress: "https://netbird.example.com:443" + + # Leave authSecret exactly as it is. relays.addresses below is what + # disables the embedded relay. + authSecret: "your-existing-secret" + + # External STUN servers, one per relay host + stuns: + - uri: "stun:relay-us.example.com:3478" + proto: "udp" + - uri: "stun:relay-eu.example.com:3478" + proto: "udp" + + # External relay servers + relays: + addresses: + - "rels://relay-us.example.com:443" + - "rels://relay-eu.example.com:443" + secret: "the-value-from-step-2" + credentialsTTL: "24h" + + # ... the rest of your existing configuration is unchanged +``` + + +`relays.secret` must be the value you read in Step 2. It must match `NB_AUTH_SECRET` on every relay host and, if your deployment has a traffic-flow receiver, the receiver's `NB_FLOW_AUTH_SECRET`. The two mismatches fail differently: + +- **With a relay:** that relay rejects peers, and its log says `invalid signature` (see [Troubleshooting](#troubleshooting)). +- **With the receiver:** relaying keeps working, and traffic events stop. Only the receiver's log shows it. Step 8 checks for exactly this. + + +Leave `server.authSecret` in place. It is required only when the embedded relay is running, and removing it gains you nothing. + +`stuns` and `relays` are available since **v0.65.0**. + +## Step 7: Restart the server + +`config.yaml` is a bind mount, so editing it changes nothing Docker Compose compares. `docker compose up -d` reports the service as already up to date and keeps serving the old configuration. Restart it explicitly: + +```bash +cd ~/netbird-enterprise +docker compose restart netbird-server +``` + + +Do not run `docker compose down` to apply this change. On an Enterprise deployment that also stops Traefik, PostgreSQL, NATS and the traffic-flow services. Restarting the one container you changed is enough. + + +Confirm the embedded relay is off and your addresses are reaching clients: + +```bash +docker compose logs --tail 400 netbird-server | grep -E 'Relay:|Relay addresses' | tail -3 +``` + +If the server has been restarted before, its log holds a block like this for every restart. The `tail` keeps you looking at the most recent one, which is the only one that reflects the change you just made. + +``` +INFO combined/cmd/root.go:691: Relay: false (log level: ) +INFO combined/cmd/root.go:731: Relay addresses: [rels://relay-us.example.com:443 rels://relay-eu.example.com:443] +INFO combined/cmd/config.go:806: Relay addresses: [rels://relay-us.example.com:443 rels://relay-eu.example.com:443] +``` + +The address list is logged twice, from two different places in the server. That is normal. + + +Then check a peer: + +```bash +netbird status -d +``` + +``` +Relays: + [stun:relay-us.example.com:3478] is Available + [stun:relay-eu.example.com:3478] is Available + [rels://relay-eu.example.com:443] is Available via ws +``` + +Every STUN server you configured appears. Relays behave differently: the client dials them in parallel and keeps the first to answer, normally the nearest, and that one becomes its **home relay**. A second `rels://` line is also normal, because a client connects to another peer's home relay when that peer picked a different one. + +The suffix names the transport that won the race, and it is `ws` or `quic` depending on which answered first. Both are normal, the winner can differ between two peers on the same deployment, and neither is a sign of a problem. What 443/udp buys you is that the QUIC attempt can win or lose on merit rather than sitting until it times out. + +## Step 8: Confirm traffic events survived + +**Do not skip this.** It is the check that belongs to Enterprise, and the failure it catches is invisible everywhere else: peers stay connected, the dashboard shows no error, and only the traffic event stream stops. + +It applies only to a deployment with a traffic-flow receiver. Without one there are no traffic events to lose, and you are done. + +Generate a little traffic between two peers again, `ping` being enough, wait about 90 seconds for the client's reporting interval, then look for rejected tokens on the main server: + +```bash +cd ~/netbird-enterprise +docker compose logs --since 120s receiver | grep -c 'invalid signature' +``` + +On a migrated deployment the service is `flow-receiver`, here and in the next command. + +This must be `0`. + +If it is not, `relays.secret` and the receiver's `NB_FLOW_AUTH_SECRET` do not match. Either side can be the stale one: + +- **`relays.secret` is wrong.** Go back to Step 2, re-read the value from the server configuration, and correct `relays.secret` and `NB_AUTH_SECRET` on every relay host to match it. +- **The receiver still holds an old value**, typically after a rotation applied with `restart`. Check the receiver's value as shown in [Rotating to a new secret](#rotating-to-a-new-secret). Make sure `.env` holds the same value as `relays.secret`, then recreate the receiver with `docker compose up -d receiver` (`flow-receiver` on a migrated deployment). + +While they differ, the receiver log shows: + +``` +ERRO server/auth.go:127: invalid token validation: invalid signature +``` + +and your peers retry in a log nobody watches: + +``` +ERRO flow/client/client.go:287: flow receiver sent no headers +ERRO flow/client/client.go:143: failed to establish flow stream, retrying: check header: should have headers +ERRO client/internal/netflow/manager.go:228: failed to send flow event to server: stream not initialized +``` + +For a positive signal, count the events the receiver accepted: + +```bash +docker compose logs --since 120s receiver | grep -c 'sending event' +``` + +Greater than zero confirms events are arriving. The two checks answer different questions. `0` for `invalid signature` proves the secret is right, which is the part this page can break, but not that events arrive: the receiver can stop accepting them for other reasons. Only a count above zero proves that. The receiver logs these lines at debug level, which is its default (`NB_FLOW_LOG_LEVEL`). If you have raised its log level, this count stays at `0` even when events arrive, so the check is not complete until the events show up on the Traffic Events page in the dashboard. + + +Check the receiver's log rather than the Traffic Events page in the dashboard. Events pass through the enricher and PostgreSQL before they appear there, so the dashboard lags well behind and cannot tell you whether events are arriving right now. + + +## Testing failover + +Stop the relay a peer is actually using, which is the one on its `rels://` line, otherwise nothing observable changes. Then check that peer again: + +```bash +netbird status -d +``` + +**Read the `rels://` line, not the `stun:` lines.** It is the one that shows failover working: the stopped relay drops out of it and the surviving relay carries the traffic. + +``` + [rels://relay-eu.example.com:443] is Available via ws +``` + +The `stun:` entries are not a dependable failover signal. Depending on circumstances they either report the stopped server as `Unavailable` with a reason, or sit at `Checking...` for every configured STUN server, including ones that are perfectly healthy, without clearing on their own or after a client restart. Both have been observed. Either way they return to `Available` once the stopped relay is back, so do not read `Checking...` as a second failure. + +To prove relayed traffic end to end, force a peer to relay instead of connecting directly: + +```bash +sudo netbird service reconfigure --service-env NB_FORCE_RELAY=true +``` + +Test connectivity to another peer, then put it back: + +```bash +sudo netbird service reconfigure --service-env NB_FORCE_RELAY=false +``` + +## Rotating to a new secret + +If you would rather not reuse the existing secret, generate one and set it **everywhere that value appears**, not only where the relay needs it: + +```bash +openssl rand -base64 32 +``` + +1. `NB_AUTH_SECRET` in `relay.env` on every relay host. +2. `relays.secret` in the server configuration on the main server (`config.yaml`, or `config.yaml.enterprise` on a migrated deployment that has it). +3. `server.authSecret` in the same file. +4. In `.env` on the main server, under whichever name your deployment uses: `NETBIRD_RELAY_AUTH_SECRET` on a fresh install, `NB_FLOW_AUTH_SECRET` on one migrated from the community edition with traffic flow. That is the value that reaches the traffic-flow receiver. A migrated deployment without traffic flow has no entry here. + +All four must end up holding the same value. Relaying needs 1 and 2 to match, and traffic events need 2 and 4 to match. Entry 3 is not used by either, but the product's own migration tooling treats `server.authSecret` as the value the receiver's secret must equal, so leaving it stale stores up a failure for a later upgrade. + +Then apply the change. On every relay host: + +```bash +cd ~/netbird-relay +docker compose up -d +``` + +and on the main server, before re-running Step 8: + +```bash +cd ~/netbird-enterprise +docker compose restart netbird-server +docker compose up -d receiver +``` + +Use `flow-receiver` on a migrated deployment. With no receiver, the first command is all you need. + +The main server's two commands differ on purpose. `netbird-server` reads `config.yaml` through a bind mount, so restarting it is enough. The receiver takes its secret from `.env`, and a container's environment is fixed when the container is created, exactly as the relay's `relay.env` is: `docker compose restart receiver` brings back the **old** value and reports nothing wrong. Only `up -d`, which recreates the container, picks up the new one. + +Get that second command wrong and the symptom is exactly the one Step 8 is designed to catch, which will send you back to re-check four values that are already correct. If Step 8 still shows `invalid signature` after a rotation, confirm the receiver really took the new value: + +```bash +docker exec netbird-receiver env | grep NB_FLOW_AUTH_SECRET +``` + +On a migrated deployment the container is `netbird-flow-receiver`. + +Miss the fourth entry, or apply it with `restart`, and you are back to silently losing traffic events. + +## Troubleshooting + +**A relay shows as `Unavailable` on a peer.** The reason string tells you which problem you have. + +``` +[rels://relay-us.example.com:443] is Unavailable, reason: failed to get reader: failed to read frame header: EOF +``` + +That one is a secret mismatch. Over QUIC the same rejection reads `reason: closed by server` instead, and you will see it on a peer only once every relay is rejecting: a peer lists only the relays it is using, so one bad relay among several is simply absent from the list rather than shown as failed. The relay host says so outright either way, in `docker compose logs relay`: + +``` +ERRO relay/server/relay.go:141: failed to handshake: validate sha-... (203.0.113.10:50907): invalid signature +``` + +Make `relays.secret` and every host's `NB_AUTH_SECRET` identical. If the relay logs no connection attempt at all, the problem is reachability instead: confirm the domain resolves, test with `nc -zv relay-us.example.com 443`, and check the certificate is valid. + +Other reason strings point away from the secret and towards reachability or the certificate. `relay client not connected` and `connect to relay server: context deadline exceeded` both mean the peer never completed a TLS session with the relay, so check Step 5 again on that host before touching any secret. + +`x509: certificate signed by unknown authority` in the reason means the peer does not trust the relay's certificate, which happens with a private CA the device has not been given. See [If you already have TLS certificates](#if-you-already-have-tls-certificates). + +When every relay is failing, each relay's entry can carry the errors of all of them, so a reason quoting one host's error may appear against another. The relay host's own log is the reliable record of which host rejected what. + +Either way the `stun:` entry for the same host stays `Available`, because STUN is unauthenticated, so it cannot tell you anything here. + +**STUN is not working.** Confirm 3478/udp is open and that `NB_ENABLE_STUN=true` is set. Some networks block outbound UDP, so try from a different network before changing the relay. + +**Traffic events stopped.** That is the secret mismatch in Step 8, not a relay fault. + +## When not to do this + +Splitting relays out costs hosts to patch, certificates to renew, and a secret to keep in step across machines. Do it when relayed traffic is loading your server, or when users are far enough away that a nearer relay is a real improvement. Do not do it just to make the deployment look tidier. + +If your aim is surviving the loss of the main server rather than spreading relay load, external relays do not get you there: management and signal are still on one host. That is what [Running a Highly Available Self-Hosted Deployment](/selfhosted/maintenance/scaling/high-availability) is for. + +## Summary + +Read the secret your deployment already has, build each relay host with it, prove every relay answers from outside, then point the server configuration at them and restart `netbird-server` alone. Finally, if your deployment has a traffic-flow receiver, check that it is still accepting flow events, because your peers will look perfectly healthy even when it is not. diff --git a/src/pages/selfhosted/enterprise/getting-started.mdx b/src/pages/selfhosted/enterprise/getting-started.mdx index 1f2b9807..0f6ba1b2 100644 --- a/src/pages/selfhosted/enterprise/getting-started.mdx +++ b/src/pages/selfhosted/enterprise/getting-started.mdx @@ -39,6 +39,7 @@ curl -fsSL https://pkgs.netbird.io/getting-started-enterprise.sh | bash The script prompts for: - Whether to enable traffic flow — required for traffic event logging and streaming. +- Whether to enable the [NetBird Proxy](/manage/reverse-proxy), and optionally [CrowdSec](/selfhosted/maintenance/crowdsec). - The public NetBird domain. - A single license key (used for all enabled products and features). @@ -48,6 +49,10 @@ It then generates the deployment files, pulls the required images, starts Postgr Enabling traffic flow adds NATS, a flow receiver, and a flow enricher to the stack. Traffic flow is required for traffic event logging and streaming.
+ +The NetBird Proxy needs a wildcard DNS record for your service domains (for example `*.netbird.example.com`) pointing at the host. + + ### 1.3 Generated files The script writes the generated files into the current directory: @@ -57,8 +62,9 @@ The script writes the generated files into the current directory: | `.env` | Runtime configuration, license key, and generated secrets | `600` | | `docker-compose.yml` | Compose stack for the NetBird server and optional traffic-flow services | `644` | | `config.yaml` | NetBird server configuration (YAML) | `600` | +| `traefik/` | Traefik dynamic configuration, including the proxy's `proxy.yaml` | `755` | -Reverse proxy and automatic HTTPS are configured through Traefik labels and command flags inside `docker-compose.yml`, so there is no separate proxy configuration file. +Reverse proxy and automatic HTTPS are configured through Traefik labels and command flags inside `docker-compose.yml`. The script aborts if generated files already exist in the directory. This avoids overwriting secrets or replacing an existing deployment by accident. @@ -94,6 +100,24 @@ Enabling traffic flow adds: | `receiver` | `ghcr.io/netbirdio/flow-receiver-cloud:latest` | Traffic flow ingest | | `enricher` | `ghcr.io/netbirdio/flow-enricher-cloud:latest` | Traffic flow enrichment | +Enabling the NetBird Proxy adds: + +| Service | Image | Notes | +| --- | --- | --- | +| `proxy` | `ghcr.io/netbirdio/reverse-proxy:latest` | NetBird Proxy | +| `crowdsec` | `crowdsecurity/crowdsec:v1.7.7` | Only when CrowdSec is enabled | + +### 1.7 Enable features on an existing installation + +Run the script with a flag from the installation directory: + +```bash +curl -fsSL https://pkgs.netbird.io/getting-started-enterprise.sh | bash -s -- --enable-traffic-events +curl -fsSL https://pkgs.netbird.io/getting-started-enterprise.sh | bash -s -- --enable-proxy +``` + +The script shows the changes to `docker-compose.yml` and asks before applying them. If a step fails, it restores the previous files. After `--enable-traffic-events`, turn on traffic events in the dashboard settings. + ## 2. Connect Identity Providers We recommend using SCIM provisioning where possible. In the following setup guides, you may **skip JWT group settings** and use our group syncing integration instead. @@ -135,6 +159,8 @@ We recommend using SCIM provisioning where possible. In the following setup guid Use `migrate-to-enterprise.sh` to convert an existing community combined-server deployment to the enterprise images. The same script also migrates SQLite data to Postgres and enables traffic flow, so no manual Compose or `config.yaml` edits are required for the supported migration path. +For installations created with `getting-started-enterprise.sh`, use [1.7 Enable features on an existing installation](#1-7-enable-features-on-an-existing-installation) instead. + The script targets an existing combined-server deployment that has a `docker-compose.yml`, a bind-mounted `config.yaml`, and a persistent `/var/lib/netbird` data volume. ### 3.1 Prerequisites @@ -268,7 +294,7 @@ Each entry follows the same structure: **Symptom → Cause → Resolution → Ve - **Resolution:** Sign in with the existing owner credentials if available. To start over from scratch: ```bash docker compose down --volumes # removes containers and the postgres/embedded-IdP data - rm -f .env docker-compose.yml config.yaml + rm -rf .env docker-compose.yml config.yaml traefik curl -fsSL https://pkgs.netbird.io/getting-started-enterprise.sh | bash ``` - **Verification:** `curl -s https:///api/instance | jq .` returns `setupRequired: true`; the dashboard now shows the owner-setup flow on first visit. @@ -317,6 +343,8 @@ If your Traefik already has a file provider, add this `tls` block to its existin ### Update `docker-compose.yml` +Installations created by the current script already have the file provider and the `./traefik` mount, so only add the certificate mount and remove ACME. + **Enable the file provider.** Add this to the `traefik` service's `command:` list: ```yaml @@ -370,6 +398,26 @@ echo | openssl s_client -connect :443 -servername 2>/ - The certificate must cover `NETBIRD_DOMAIN` from `.env`. A wildcard like `*.example.com` works for `netbird.example.com`. - **If the certificate is signed by a private CA, every peer must trust the issuing CA.** Install the CA bundle in each peer's system trust store. A peer that does not trust it fails to connect at all, logging `x509: certificate signed by unknown authority`. + + The NetBird Proxy container is a peer too. Without the CA it still registers as online, but it cannot connect to the Signal service, so requests to its services fail. + + Give the proxy a CA bundle that holds the host's public roots and your CA. Add it in `docker-compose.override.yml`, not `docker-compose.yml`: running `getting-started-enterprise.sh --enable-proxy` or `--enable-traffic-events` regenerates `docker-compose.yml`, so a hand edit there does not survive. On Debian and Ubuntu hosts: + + ```bash + cat /etc/ssl/certs/ca-certificates.crt /path/to/your-ca.pem | sudo tee /etc/netbird/certs/proxy-ca-bundle.pem > /dev/null + ``` + + ```yaml + services: + proxy: + environment: + - SSL_CERT_FILE=/etc/ssl/netbird/ca-bundle.pem + volumes: + - /etc/netbird/certs/proxy-ca-bundle.pem:/etc/ssl/netbird/ca-bundle.pem:ro + ``` + + Then run `docker compose up -d proxy`. - **Renewals.** Replace both files at the same paths, then `touch traefik/dynamic.yaml`. Traefik watches the dynamic configuration, not the certificates it references, so replacing them alone has no effect. Touching it reloads them with no container restart. - You can leave the `netbird_traefik_letsencrypt` volume in place. With no resolver configured, the stored certificate is inert. +- `--enable-proxy` and `--enable-traffic-events` keep this setup. - Dropping the `80:80` mapping does not affect certificates: this deployment validates over TLS-ALPN-01 on port 443 and never uses port 80 for ACME. You do lose the HTTP→HTTPS redirect. diff --git a/src/pages/selfhosted/enterprise/index.mdx b/src/pages/selfhosted/enterprise/index.mdx index 61298ff3..37d4ca20 100644 --- a/src/pages/selfhosted/enterprise/index.mdx +++ b/src/pages/selfhosted/enterprise/index.mdx @@ -33,6 +33,8 @@ The core connectivity, access control, and routing features are the same in the | SCIM user and group provisioning | No | Yes | | EDR and MDM integrations (CrowdStrike, SentinelOne, Intune, and others) | No | Yes | | Traffic-flow event logging and streaming | No | Yes | +| Peer approval | No | Yes | +| Audit event streaming and notifications | No | Yes | | Support | Community (Slack, GitHub) | Standard support included | ## Frequently asked questions @@ -73,7 +75,7 @@ To serve multiple isolated customers you have two options: You can start evaluating today for free: the **open source Community Edition is free to self-host for as long as you like**, with no license and no time limit. That is the right way to try core NetBird (the control plane, peers, policies, and routing) while you decide whether it fits. -To evaluate the commercial-only features (active-active HA, SCIM, traffic-flow logging), contact the sales team through the [on-prem pricing page](https://netbird.io/pricing#on-prem) to run an assisted proof of concept with the commercial license. You install and run the stack; NetBird provides the license and guidance. A commercial proof of concept runs for 30 days by default. +To evaluate the commercial-only features, contact the sales team through the [on-prem pricing page](https://netbird.io/pricing#on-prem) to run an assisted proof of concept with the commercial license. A proof of concept includes every feature of the commercial license, including everything marked commercial in the table above. You install and run the stack; NetBird provides the license and guidance. A commercial proof of concept runs for 30 days by default. ### What does the server share with the NetBird license service? diff --git a/src/pages/selfhosted/environment-variables.mdx b/src/pages/selfhosted/environment-variables.mdx index 9bf985b1..246e7d30 100644 --- a/src/pages/selfhosted/environment-variables.mdx +++ b/src/pages/selfhosted/environment-variables.mdx @@ -54,9 +54,9 @@ The default quickstart deployment uses the relay service's embedded STUN server. | Variable | Default | Description | |----------|---------|-------------| -| `NETBIRD_STORE_CONFIG_ENGINE` | `sqlite` | Store engine: `sqlite`, `postgres`, or `mysql` | +| `NETBIRD_STORE_CONFIG_ENGINE` | `sqlite` | Store engine: `sqlite`, `postgres`, or `mysql` (deprecated) | | `NETBIRD_STORE_ENGINE_POSTGRES_DSN` | - | PostgreSQL connection string | -| `NETBIRD_STORE_ENGINE_MYSQL_DSN` | - | MySQL connection string | +| `NETBIRD_STORE_ENGINE_MYSQL_DSN` | - | MySQL connection string. Deprecated, see [Migrate from MySQL to PostgreSQL](/selfhosted/migration/mysql-to-postgresql) | | `NETBIRD_DATASTORE_ENC_KEY` | Auto-generated | Encryption key for sensitive data | diff --git a/src/pages/selfhosted/identity-providers/managed/advanced/google-workspace.mdx b/src/pages/selfhosted/identity-providers/managed/advanced/google-workspace.mdx index a9bb0bc1..b8832d03 100644 --- a/src/pages/selfhosted/identity-providers/managed/advanced/google-workspace.mdx +++ b/src/pages/selfhosted/identity-providers/managed/advanced/google-workspace.mdx @@ -3,26 +3,30 @@ import {Note, Warning} from "@/components/mdx"; # Google Workspace SSO with NetBird Self-Hosted (Legacy) -The client secret has been removed from the authentication flow. The secret was originally included to support providers like Google Workspace that don't offer a proper PKCE flow, but this is no longer necessary with the embedded IdP. +**Using Google directly as NetBird's identity provider is not recommended.** Use the [embedded IdP with Google as an external provider](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended) instead. If you already run this standalone setup, migrate with the [External IdP to Embedded IdP migration guide](/selfhosted/migration/external-to-embedded-idp). -If you are using this standalone Google Workspace setup, we strongly recommend migrating to the [embedded IdP setup with Google as an external provider](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended). The embedded IdP approach is simpler, fully supported, and does not rely on the deprecated client secret. +Google requires a client secret to issue tokens, even with PKCE, so this setup relies on the Management service sending the client secret to every NetBird client. Sending the client secret to clients is deprecated and will be removed in a future release. With the embedded IdP, clients authenticate against NetBird's own IdP, which brokers the login to Google and keeps the client secret on the server. Use Google accounts for authentication with NetBird. This supports both personal Google accounts and Google Workspace (formerly G Suite) organizations. -## Standalone Setup (Advanced) +## Standalone Setup (Not Recommended) NetBird includes built-in [local user management](/selfhosted/identity-providers/local) powered by an embedded IdP, allowing you to create and manage users directly without requiring an external identity provider. You can also add **multiple external identity providers** alongside local users, giving users multiple login options. We highly recommend using the simpler setup that adds Google as an external IdP directly in the NetBird Management Dashboard. This approach requires minimal configuration, works alongside local users, and doesn't require replacing your embedded IdP. See the [Management Setup (Recommended)](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended) section in the main Google Workspace documentation. -The standalone setup below replaces your embedded IdP entirely and is only recommended for experienced Google Workspace administrators who need full control over authentication and user management. +The standalone setup below replaces your embedded IdP entirely and is not recommended for new or existing deployments. -Use Google Workspace as your primary identity provider instead of NetBird's embedded IdP. This option gives you full control over authentication and user management, is recommended for experienced Google Workspace administrators as it also requires additional setup and ongoing maintenance. +Use Google Workspace as your primary identity provider instead of NetBird's embedded IdP. This option requires additional setup and ongoing maintenance, depends on the deprecated client secret delivery described above, and has the limitations listed below. -For most deployments, the [embedded IdP](/selfhosted/identity-providers/local) is the simpler choice — it's built into NetBird, fully integrated, and requires minimal configuration to get started. For this implementation, go back up to the [Management Setup (Recommended)](#management-setup-recommended) section above. +For most deployments, the [embedded IdP](/selfhosted/identity-providers/local) is the simpler choice — it's built into NetBird, fully integrated, and requires minimal configuration to get started. For this implementation, see the [Management Setup (Recommended)](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended) section in the main Google Workspace documentation. + +### Limitation: No Device Authorization Flow + +Google's device flow requires a client secret when polling for the token, and NetBird clients do not send one, so this setup disables the device flow (`NETBIRD_AUTH_DEVICE_AUTH_PROVIDER="none"` in [Step 7](#step-7-configure-net-bird)). Linux and FreeBSD peers without a graphical session, such as servers or SSH sessions, use only the device flow, so they cannot log in with SSO (`netbird login`, `netbird up`) or authenticate with `netbird ssh`. Register those peers with [setup keys](/manage/peers/register-machines-using-setup-keys), or migrate to the embedded IdP, which supports the device flow. Beginning with NetBird version v0.23.6 and onwards, the Google Workspace IdP manager no longer requires the custom admin role called `User and Schema Management`. We now use a read-only role for user information. diff --git a/src/pages/selfhosted/identity-providers/managed/google-workspace.mdx b/src/pages/selfhosted/identity-providers/managed/google-workspace.mdx index 8c0445e5..1f5b13ba 100644 --- a/src/pages/selfhosted/identity-providers/managed/google-workspace.mdx +++ b/src/pages/selfhosted/identity-providers/managed/google-workspace.mdx @@ -1,4 +1,4 @@ -import {Note} from "@/components/mdx"; +import {Note, Warning} from "@/components/mdx"; # Google Workspace SSO with NetBird Self-Hosted @@ -138,13 +138,15 @@ Domain restrictions are configured in Google Cloud Console, not in NetBird. --- -## Standalone Setup (Advanced) +## Standalone Setup (Not Recommended) -Use Google Workspace as your primary identity provider instead of NetBird's embedded IdP. This option gives you full control over authentication and user management, is recommended for experienced Google Workspace administrators as it also requires additional setup and ongoing maintenance. + +Using Google directly as NetBird's identity provider, instead of the embedded IdP, is not recommended. It relies on the Management service sending the client secret to every NetBird client, which is deprecated and will be removed in a future release, and it does not support the device authorization flow used by headless Linux and FreeBSD peers. Use the [Management Setup (Recommended)](#management-setup-recommended) above instead. -For most deployments, the [embedded IdP](/selfhosted/identity-providers/local) is the simpler choice — it's built into NetBird, fully integrated, and requires minimal configuration to get started. For this implementation, go back up to the [Management Setup (Recommended)](#management-setup-recommended) section above. +If you already run this standalone setup, migrate to the embedded IdP with the [External IdP to Embedded IdP migration guide](/selfhosted/migration/external-to-embedded-idp). Existing users keep signing in with their Google accounts. + -For detailed instructions on the standalone setup, see the [Google Workspace SSO with NetBird Self-Hosted (Legacy)](/selfhosted/identity-providers/managed/advanced/google-workspace) documentation. +For reference, the standalone setup instructions are kept in the [Google Workspace SSO with NetBird Self-Hosted (Legacy)](/selfhosted/identity-providers/managed/advanced/google-workspace) documentation. If you prefer to have full control over authentication, consider self-hosted alternatives like [PocketID](/selfhosted/identity-providers/pocketid). diff --git a/src/pages/selfhosted/maintenance/activity-mysql-store.mdx b/src/pages/selfhosted/maintenance/activity-mysql-store.mdx deleted file mode 100644 index dd686f15..00000000 --- a/src/pages/selfhosted/maintenance/activity-mysql-store.mdx +++ /dev/null @@ -1,15 +0,0 @@ -# Activity Events MySQL store - -## Using MySQL for fresh installations - -To enable MySQL add to your management service the following environmental variable: -```bash -NB_ACTIVITY_EVENT_STORE_ENGINE=mysql -NB_ACTIVITY_EVENT_MYSQL_DSN="mysql://:@:/" -``` - -You can switch back to sqlite storage by setting the `NB_ACTIVITY_EVENT_STORE_ENGINE` variable to `sqlite`. - - Switching between storage options requires migration steps to prevent data loss. - - diff --git a/src/pages/selfhosted/maintenance/configuration-files.mdx b/src/pages/selfhosted/maintenance/configuration-files.mdx index e3c66a7b..0a9cd275 100644 --- a/src/pages/selfhosted/maintenance/configuration-files.mdx +++ b/src/pages/selfhosted/maintenance/configuration-files.mdx @@ -333,10 +333,10 @@ Configures the database backend for storing all NetBird management data includin - Database engine. Options: `sqlite`, `postgres`, `mysql`. Default: `sqlite`. + Database engine. Options: `sqlite`, `postgres`, `mysql`. Default: `sqlite`. `mysql` is deprecated, see [Migrate from MySQL to PostgreSQL](/selfhosted/migration/mysql-to-postgresql). - Connection string for postgres or mysql engines. For postgres: `host=localhost user=netbird password=secret dbname=netbird port=5432`. Alternatively, use the `NETBIRD_STORE_ENGINE_POSTGRES_DSN` or `NETBIRD_STORE_ENGINE_MYSQL_DSN` environment variables. + Connection string for the postgres engine, or the deprecated mysql engine. For postgres: `host=localhost user=netbird password=secret dbname=netbird port=5432`. Alternatively, use the `NETBIRD_STORE_ENGINE_POSTGRES_DSN` environment variable (`NETBIRD_STORE_ENGINE_MYSQL_DSN` for mysql). base64 string of a 32-byte (256-bit) encryption key for sensitive data at rest. Used to encrypt setup keys, API tokens, and other secrets stored in the database. Auto-generated by the setup script. @@ -358,9 +358,9 @@ Configures the database backend for storing all NetBird management data includin |--------|---------|-------| | SQLite (default) | `/var/lib/netbird/` volume | File-based database stored in the `netbird_data` Docker volume. Zero configuration required, but does not support concurrent writes or running multiple management instances. Best for testing or small deployments. | | PostgreSQL | External database server | Recommended for production deployments. Supports concurrent access, enabling multiple management instances for high availability. | -| MySQL | External database server | Alternative to PostgreSQL for organizations that have standardized on MySQL/MariaDB. Provides similar benefits including concurrent access. | +| MySQL | External database server | Deprecated and will be removed in an upcoming release. See [Migrate from MySQL to PostgreSQL](/selfhosted/migration/mysql-to-postgresql). | -For PostgreSQL or MySQL, set the connection string via the `server.store.dsn` field in `config.yaml` or environment variables on the `netbird-server` container. See [Using an External Database](#using-an-external-database) below. +For PostgreSQL, or the deprecated MySQL engine, set the connection string via the `server.store.dsn` field in `config.yaml` or environment variables on the `netbird-server` container. See [Using an External Database](#using-an-external-database) below. See [Management Postgres Store](/selfhosted/maintenance/postgres-store) for detailed PostgreSQL setup. @@ -499,8 +499,6 @@ Alternatively, you can use an environment variable instead of putting the DSN in netbird-server: environment: - NETBIRD_STORE_ENGINE_POSTGRES_DSN=host=db-server user=netbird password=secret dbname=netbird port=5432 - # Or for MySQL: - # - NETBIRD_STORE_ENGINE_MYSQL_DSN=user:password@tcp(host:3306)/netbird ``` See [Management Postgres Store](/selfhosted/maintenance/postgres-store) for detailed setup. diff --git a/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx b/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx index 8d1afb65..452e8660 100644 --- a/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx +++ b/src/pages/selfhosted/maintenance/crowdsec-dashboard.mdx @@ -11,6 +11,8 @@ The configuration reuses the CrowdSec LAPI container created by the NetBird quic For the NetBird Proxy IP reputation integration, see [CrowdSec IP Reputation](/selfhosted/maintenance/crowdsec). +For updates to Traefik, CrowdSec, or the bouncer plugin, see [Upgrading Traefik and CrowdSec](/selfhosted/maintenance/upgrade#upgrading-traefik-and-crowd-sec). + ## Prerequisites - A self-hosted NetBird deployment using the internal Traefik reverse proxy. @@ -86,25 +88,22 @@ EOF Start from the `docker-compose.yml` generated by the quickstart with proxy CrowdSec enabled. The file already includes a `crowdsec` service, and `proxy.env` already points NetBird Proxy to `http://crowdsec:8080`. -Under `services.traefik.command`, add the Traefik CrowdSec bouncer plugin static arguments: +Set the Traefik image to the validated version below and add the header handling and bouncer plugin arguments to the existing `services.traefik.command` list: ```yaml services: traefik: + image: traefik:v3.7.14 command: + - '--entrypoints.web.http.aliasHeadersStrategy=delete' + - '--entrypoints.websecure.http.aliasHeadersStrategy=delete' - '--experimental.plugins.bouncer.modulename=github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin' - - '--experimental.plugins.bouncer.version=v1.6.0' + - '--experimental.plugins.bouncer.version=v1.7.1' ``` -The generated Compose file already sets `--providers.docker.network` in the Traefik command block, normally to `netbird`, matching the network key in the Compose file. Leave it as generated and do not add a second copy of the flag. +The header settings remove request headers with names containing characters other than letters, digits, and hyphens. If you use custom headers or already set `underscoreHeadersStrategy`, review [Traefik header handling](/selfhosted/maintenance/upgrade#traefik-header-handling) before applying them. -Only change it if Traefik logs warnings about a missing Docker network. Compose prefixes the network name with the project name, which defaults to the install directory name, so the actual name varies by deployment. Check it before editing: - -```bash -docker network ls | grep netbird -``` - -An install in `/root` produces `root_netbird`; one in `/opt/netbird` produces `netbird_netbird`. +Verify that the existing `--providers.docker.network` value matches the actual Docker network name, which can include a Compose project prefix. Follow [Traefik Docker network](/selfhosted/maintenance/upgrade#traefik-docker-network) to check it and replace a mismatched value. Update the existing flag rather than adding a second copy. Under `services.traefik.depends_on`, make Traefik wait until CrowdSec is healthy: @@ -164,7 +163,7 @@ Replace `netbird.example.com` with your NetBird domain. Traefik labels do not re middleware to the `proxy-passthrough` TCP router. -Merge these fields into `services.crowdsec`. This adds the AppSec collections, registers the dashboard bouncer key, and updates the health check so CrowdSec reports healthy only after both LAPI and AppSec are available. Keep the generated fields that already exist in your Compose file, such as `image`, `container_name`, `restart`, `networks`, `labels`, `logging`, and `volumes`. +Merge these fields into `services.crowdsec`. This sets the validated image version, adds the AppSec collections, registers the dashboard bouncer key, and updates the health check so CrowdSec reports healthy only after both LAPI and AppSec are available. Keep the other generated fields, including `container_name`, `restart`, `networks`, `labels`, `logging`, and `volumes`. The installer-created health check only verifies the CrowdSec Local API: @@ -189,7 +188,7 @@ Dashboard protection also needs the AppSec listener on port `7422`, so replace t ```yaml services: crowdsec: - ... + image: crowdsecurity/crowdsec:v1.8.1 environment: COLLECTIONS: "crowdsecurity/linux crowdsecurity/appsec-virtual-patching crowdsecurity/appsec-generic-rules" BOUNCER_KEY_netbird_dashboard: "${CROWDSEC_DASHBOARD_BOUNCER_KEY}" @@ -241,6 +240,12 @@ A known malicious probe should return `403`: curl -ks -A 'Mozilla/5.0 NetBirdDashboardCheck' -o /dev/null -w '%{http_code}\n' "https://$NETBIRD_DOMAIN/.env" ``` + + Use only the `/.env` and `/api/.env` probes when testing from your admin IP. Triggering two different AppSec rules within about a minute can ban that IP for 4 hours, blocking the dashboard, `/api`, and `/oauth2`. To lift a ban, [recover access over SSH](#recovering-from-a-dashboard-lockout). + + The default `crowdsecurity/whitelists` parser exempts loopback and private IPv4 ranges (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`) from this ban. Requests from those addresses are still blocked individually with `403`, so testing from a LAN client does not show whether a public IP would be banned. + + Check AppSec metrics and bouncer registration: ```bash @@ -346,7 +351,7 @@ First identify which of the two mechanisms is blocking you, because they are han docker compose exec crowdsec cscli decisions list ``` -If your address is listed, an **IP decision** is blocking you. If it is not, the block is an **AppSec** match on the request itself: AppSec inspects requests and does not create decisions, so `cscli decisions delete` has no effect in that case. Confirm with `cscli metrics show appsec` and `cscli alerts list`, where AppSec matches appear with `kind` set to `waf`. +If your address is listed, an **IP decision** is blocking you. This includes bans created by the `crowdsecurity/appsec-vpatch` scenario after repeated AppSec matches, which appear with that reason. If it is not, the block is an **AppSec** match on the request itself: an individual AppSec match does not create a decision, so `cscli decisions delete` has no effect in that case. Confirm with `cscli metrics show appsec` and `cscli alerts list`, where AppSec matches appear with `kind` set to `waf`. Allowlisting the address resolves both cases. `cscli allowlists add` fails with `allowlist '' not found` if the list does not already exist, so create it first if you have not set one up: @@ -399,13 +404,7 @@ If CrowdSec is stopped or unreachable while `crowdsecAppsecUnreachableBlock=true Separately, a reverse proxy service in `enforce` mode denies all connections while its bouncer has not completed its initial sync, for example when the proxy starts with the LAPI unavailable. See [Enforcement modes](/selfhosted/maintenance/crowdsec#enforcement-modes). -If Traefik logs warnings about a missing Docker network, check the actual network name: - -```bash -docker network ls | grep netbird -``` - -Then set `--providers.docker.network` to the actual network name, commonly `netbird_netbird`. +If Traefik logs warnings about a missing Docker network, follow [Traefik Docker network](/selfhosted/maintenance/upgrade#traefik-docker-network) to correct the configured name. If AppSec metrics do not increment, confirm that the middleware is attached to the dashboard router and that Traefik can reach `crowdsec:8080` and `crowdsec:7422`. diff --git a/src/pages/selfhosted/maintenance/crowdsec.mdx b/src/pages/selfhosted/maintenance/crowdsec.mdx index cf6d1f08..a24ff544 100644 --- a/src/pages/selfhosted/maintenance/crowdsec.mdx +++ b/src/pages/selfhosted/maintenance/crowdsec.mdx @@ -10,6 +10,8 @@ export const description = 'Enable CrowdSec IP reputation blocking for self-host This page covers how the integration works and how to operate it. For full setup instructions, see [Step 7: Enable CrowdSec IP reputation](/selfhosted/migration/enable-reverse-proxy#step-7-optional-enable-crowdsec-ip-reputation) in the Enable Reverse Proxy guide. +For version selection and updates to an existing deployment, see [Upgrading Traefik and CrowdSec](/selfhosted/maintenance/upgrade#upgrading-traefik-and-crowd-sec). + If you're running the [quickstart script](/selfhosted/selfhosted-quickstart) for a fresh installation, it offers to enable CrowdSec automatically when you choose the built-in Traefik option and enable the proxy. @@ -173,6 +175,6 @@ docker compose exec crowdsec cscli alerts list docker compose exec crowdsec cscli decisions delete --ip ``` -`cscli alerts list` includes a `kind` column that identifies which component generated the alert: `crowdsec` for local log detection, `waf` for an AppSec match, and `cscli` for a manually added decision. AppSec matches block the individual request but do not create an IP decision, so they never appear in `cscli decisions list`; use `cscli metrics show appsec` to inspect those instead. +`cscli alerts list` includes a `kind` column that identifies which component generated the alert: `crowdsec` for local log detection, `waf` for an AppSec match, and `cscli` for a manually added decision. An individual AppSec match blocks only that request and does not appear in `cscli decisions list`; use `cscli metrics show appsec` to inspect those instead. If you installed the `crowdsecurity/appsec-virtual-patching` collection for [dashboard protection](/selfhosted/maintenance/crowdsec-dashboard), matches against two different AppSec rules from the same IP within about a minute trigger the `crowdsecurity/appsec-vpatch` scenario, which creates a regular 4-hour IP ban that does appear in `cscli decisions list`. Loopback and private IPv4 addresses are exempt from this ban through the default `crowdsecurity/whitelists` parser, although their individual matching requests are still blocked. For dashboard lockouts specifically, see [Recovering from a dashboard lockout](/selfhosted/maintenance/crowdsec-dashboard#recovering-from-a-dashboard-lockout). diff --git a/src/pages/selfhosted/maintenance/scaling/high-availability.mdx b/src/pages/selfhosted/maintenance/scaling/high-availability.mdx index b2207e0e..d43b6274 100644 --- a/src/pages/selfhosted/maintenance/scaling/high-availability.mdx +++ b/src/pages/selfhosted/maintenance/scaling/high-availability.mdx @@ -4,7 +4,7 @@ import {Note, Warning} from "@/components/mdx"; A single NetBird server works well for many deployments, but production environments may need to remain available when an individual host or service instance fails. A highly available deployment removes those single points of failure by spreading services across separate failure domains and routing traffic to healthy instances. -NetBird Enterprise supports **active-active high availability (HA)** for Management and Signal. This guide shows how to move an existing self-hosted deployment to that mode: Management, Signal, and Relay run as independent, load-balanced pools, while PostgreSQL, Redis, and NATS provide the shared state that lets a healthy instance continue serving traffic when another instance fails. +NetBird Enterprise supports **active-active high availability (HA)** for Management and Signal. This guide shows how to move an existing self-hosted deployment to that mode: Management, Signal, and Relay run as independent pools: Management and Signal behind load balancers, and Relay with an address of its own for every instance. PostgreSQL, Redis, and NATS provide the shared state that lets a healthy Management or Signal instance continue serving traffic when another instance fails. Use this guide when you need to tolerate the loss of a Management, Signal, Relay, or NATS node and perform rolling upgrades with minimal disruption. It assumes an existing Enterprise deployment that already uses PostgreSQL. For a distributed deployment without active-active Management and Signal, see [Splitting Your Self-Hosted Deployment](/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment). @@ -22,7 +22,7 @@ This is an infrastructure migration, not an in-place switch. Build and validate | You have a recent, restorable PostgreSQL backup. | Database migration or endpoint changes should always have a rollback path. | | You have recorded the current `server.store.encryptionKey` value. | Every Management replica must use the exact same key to read existing encrypted data. | | You can deploy instances in separate failure domains. | Two containers on one host do not provide high availability. | -| You have stable DNS names and load balancers for Management, Signal, and Relay. | Peers and the dashboard must keep using stable URLs while backends change. | +| You have stable DNS names and load balancers for Management and Signal, and a DNS name for every Relay instance. | Peers and the dashboard must keep using stable URLs while backends change. Relay instances are reached by their own names, not through a load balancer. | | PostgreSQL and Redis can each expose one highly available, read-write endpoint. | Management replicas need a shared database and cache, not per-instance stores. | | You can test the change in staging first. | Failure testing is part of validating an HA deployment. | @@ -32,20 +32,20 @@ Build the HA deployment in this sequence: 1. Make PostgreSQL and Redis highly available. 2. Deploy and validate the three-node NATS cluster. -3. Create the Management, Signal, and Relay load-balancer frontends and DNS records. +3. Create the Management and Signal load-balancer frontends, and the DNS records for every service, including one per Relay instance. 4. Deploy the Relay and Signal pools. 5. Configure and deploy the Management pool. 6. Validate normal operation and then run the failure tests. ### Scope and endpoint choices -This guide uses three public service URLs: +This guide uses these public service URLs: | Service | Example URL | Used by | |---|---|---| -| Management | `https://app.example.com` | Dashboard, Management API, Management gRPC, and OAuth | +| Management | `https://netbird.example.com` | Dashboard, Management API, Management gRPC, and OAuth | | Signal | `https://signal.example.com` | NetBird peers for signaling | -| Relay | `rels://relay.example.com:443` | NetBird peers that need relay connectivity | +| Relay | One per instance, e.g. `rels://us-1.relay.example.com:443` and `rels://eu-1.relay.example.com:443`. With the geo-DNS option, also a shared `rels://relay.example.com:443`. | NetBird peers that need relay connectivity | The Management URL can remain the URL used by your existing deployment. If you change it, update the dashboard environment to use the new Management API and gRPC endpoints; see [Dashboard environment configuration](/selfhosted/maintenance/configuration-files#dashboardenv). The dashboard itself is stateless and can be served behind the Management load balancer or separately, as long as browsers can reach the configured Management URL. @@ -55,7 +55,9 @@ A highly available deployment has three service pools. They have different conne - **Management pool:** Enterprise Management replicas serve the dashboard API, Management gRPC, and OAuth2 endpoints. Fully stateless: any replica can serve any request because all durable state lives in PostgreSQL and all short-lived state lives in Redis. Replicas scale horizontally to absorb dashboard traffic, peer sync, and OAuth flows. - **Signal pool:** Enterprise Signal replicas serve the Signal gRPC API. Peers connect to one Signal instance through the load balancer. The NATS cluster reconciles cross-instance peer signaling, so any Signal instance can deliver a message to any peer regardless of its connected instance. This makes Signal active-active in the Enterprise build. See [How active-active Signal works](#how-active-active-signal-works). -- **Relay pool:** Relay instances serve WebSocket relay traffic for peers that cannot reach each other directly. Each relay session stays on one Relay instance for its lifetime through the load balancer's natural connection affinity. When an instance fails, peers reconnect through the load balancer to a healthy instance. Relay traffic flows directly between peers and the Relay pool. +- **Relay pool:** Relay instances carry traffic for peers that cannot reach each other directly. Unlike Management and Signal, Relay does not go behind a load balancer. Relay instances share no state, so two peers can be relayed to each other only when each can reach the other's instance by that instance's own address. Every instance therefore has its own URL, and a peer that loses its relay moves to another instance by itself. See [Step 5](#step-5-deploy-the-relay-pool). + +If you use traffic events, two more services run alongside the Management pool: a flow receiver and a flow enricher. See [Deploy the flow receiver and enricher](#deploy-the-flow-receiver-and-enricher). The pools depend on the following shared infrastructure: @@ -63,15 +65,14 @@ The pools depend on the following shared infrastructure: - **PostgreSQL HA endpoint:** Stores durable Management data, including accounts, peers, policies, OAuth state, and integrations. It is operator-managed. NetBird treats it as an opaque connection string and expects failover to happen transparently behind the DSN. - **Redis HA endpoint:** Stores ephemeral cache data, including OAuth PKCE verifiers, peer cache data, and dynamic configuration. It is operator-managed. Losing Redis interrupts in-flight OAuth flows but does not break running peer connections. -Each pool uses one stable URL backed by its load-balanced instances. Adding or removing instances is transparent to peers because the URL does not change. +The Management and Signal pools each use one stable URL backed by load-balanced instances, so adding or removing one of their instances is transparent to peers. The Relay pool is reached by each instance's own address instead. ```mermaid flowchart TB Peers[NetBird Peers] - LBM[Load Balancer
app.example.com] + LBM[Load Balancer
netbird.example.com] LBS[Load Balancer
signal.example.com] - LBR[Load Balancer
relay.example.com] subgraph Mgmt[Management pool: Enterprise] M1[Management 1] @@ -84,8 +85,8 @@ flowchart TB end subgraph Rel[Relay pool] - R1[Relay 1] - R2[Relay 2] + R1[Relay us-1
us-1.relay.example.com] + R2[Relay eu-1
eu-1.relay.example.com] end subgraph NATSC[NATS cluster: 3 nodes with JetStream] @@ -99,11 +100,10 @@ flowchart TB Peers --> LBM Peers --> LBS - Peers -.->|relay traffic| LBR + Peers -.->|relay traffic, direct to each instance| R1 & R2 LBM --> M1 & M2 LBS --> S1 & S2 - LBR --> R1 & R2 M1 & M2 --> PG M1 & M2 --> RD @@ -111,7 +111,7 @@ flowchart TB S1 & S2 --> NATSC ``` -The three load balancers can be independent load balancers, frontends on one shared load balancer, or managed load-balancer resources. Choose the model that fits your infrastructure. Each pool must be reached through one stable URL with at least two backend instances and a load balancer that can fail over within seconds. +The two load balancers can be independent load balancers, frontends on one shared load balancer, or managed load-balancer resources. Choose the model that fits your infrastructure. Management and Signal must each be reached through one stable URL with at least two backend instances and a load balancer that can fail over within seconds. ### How active-active Signal works @@ -124,9 +124,9 @@ In single-node mode, the Signal service keeps peer connection state in memory an - At least **2 enterprise Signal instances**, on separate failure domains. - At least **2 Relay instances**, on separate failure domains. - At least **3 NATS instances** for the coordination cluster, on separate failure domains. NATS can colocate with NetBird hosts, but the 3 NATS instances must be on different failure domains. -- A **load balancer** for each pool: Management, Signal, and Relay. These can be three independent load balancers, three frontends on one shared load balancer, or three managed load-balancer resources, as long as each pool is reachable through a single stable URL. Management and Signal require HTTP/2 + gRPC support. Relay requires WebSocket support. -- Three public **FQDNs**, one per pool. For example: `app.example.com`, `signal.example.com`, and `relay.example.com`. Each must resolve to its corresponding load balancer. -- Permissions to deploy services, mount configuration and secrets, expose network ports, and register instances with the load balancers in your environment. +- A **load balancer** for the Management and Signal pools. These can be two independent load balancers, two frontends on one shared load balancer, or two managed load-balancer resources, as long as each pool is reachable through a single stable URL. Both require HTTP/2 + gRPC support. The Relay pool does not use a load balancer. +- Public **FQDNs** for Management and Signal, for example `netbird.example.com` and `signal.example.com`, each resolving to its load balancer. One more per Relay instance, for example `us-1.relay.example.com` and `eu-1.relay.example.com`, each resolving to that instance. With the geo-DNS option, also a shared `relay.example.com`. +- Permissions to deploy services, mount configuration and secrets, expose network ports, manage DNS records, and register instances with the load balancers in your environment. ## Step 1: Make PostgreSQL highly available @@ -280,6 +280,95 @@ NetBird uses NATS for coordination between Management and Signal instances. Like point of failure. +### Example: a self-hosted cluster with Docker Compose + +This example runs one NATS node per host, on three hosts: `nats-1.example.com`, `nats-2.example.com` and `nats-3.example.com`. A NATS node can share a host with a NetBird node or a Signal instance, but no host may run two NATS nodes. + +Generate two passwords, one for NetBird and Signal to connect with and one for the NATS nodes to connect to each other. Use hex, so they are safe inside URLs: + +```bash +openssl rand -hex 24 +``` + +On each host, create a directory with two files. The first, `nats-server.conf`, differs per host in `server_name` and `client_advertise`: + +```text +server_name: nats-1 +port: 4222 +client_advertise: "nats-1.example.com:4222" +http_port: 8222 + +jetstream { + store_dir: /data +} + +authorization { + user: netbird + password: "" +} + +cluster { + name: netbird + port: 6222 + authorization { + user: route + password: "" + } + routes: [ + "nats-route://route:@nats-1.example.com:6222" + "nats-route://route:@nats-2.example.com:6222" + "nats-route://route:@nats-3.example.com:6222" + ] +} +``` + +`client_advertise` is the address each node gives clients for reconnecting. Without it, a node in a container advertises its Docker-internal address, which NetBird and Signal cannot reach. + +The second file, `docker-compose.yml`, is the same on every host: + +```yaml +services: + nats: + image: nats:2.14 + container_name: nats + restart: unless-stopped + command: ["-c", "/etc/nats/nats-server.conf"] + ports: + - "4222:4222" + - "6222:6222" + - "127.0.0.1:8222:8222" + volumes: + - ./nats-server.conf:/etc/nats/nats-server.conf:ro + - nats-data:/data + +volumes: + nats-data: +``` + +The configuration holds both passwords, so keep it readable by root only, then start the node: + +```bash +chmod 600 nats-server.conf +docker compose up -d +``` + +Allow port `4222` from the Management and Signal hosts only, and port `6222` between the NATS hosts only. Set these rules on your network firewall or security groups: ports that Docker publishes bypass host firewalls such as UFW and firewalld. The monitoring port `8222` listens on the host itself only. + +Once all three nodes run, check each one: + +```bash +curl -s http://127.0.0.1:8222/healthz +curl -s http://127.0.0.1:8222/jsz | grep -E '"cluster_size"|"leader"' +``` + +Each node answers `{"status":"ok"}`, reports `"cluster_size": 3` and names the same leader. Until all three have started, the nodes log `Error trying to connect to route` for the ones that are not up yet. + +NetBird and Signal connect as the `netbird` user, so the NATS URLs you give them carry the client password: `nats://netbird:@nats-1.example.com:4222`. Both write these URLs to their logs at startup, password included. Keep those logs as private as the configuration. + +This example does not configure TLS, so the passwords and all NATS traffic cross the network unencrypted. Keep NATS on a private network, or add TLS as described in the [NATS documentation](https://docs.nats.io/running-a-nats-service/configuration/securing_nats/tls). + +The `nats` CLI commands below also need the credentials: add `--user netbird --password ""`. The `server report` command needs a system account, which this example does not create; use the `curl` checks above instead. + ### Traffic-flow stream NetBird expects the `traffic-events` JetStream stream to be available for traffic-flow events. Configure it with: @@ -308,6 +397,8 @@ nats --server nats://nats-1.example.com:4222 \ --defaults ``` +The flow receiver publishes to `netbird.flow.events` by default, a subject this stream does not match. Step 7 sets it to one under `traffic-events.`; see [Deploy the flow receiver and enricher](#deploy-the-flow-receiver-and-enricher). + If your NATS platform manages streams declaratively, apply the same settings through that platform instead. If the stream already exists with one replica, update it to three replicas: ```bash @@ -340,118 +431,162 @@ Signal instances reference the same list via the `NATS_ENDPOINTS` environment va **Why an explicit list and not a single DNS round-robin URL?** A single hostname with multiple A/AAAA records (e.g. `nats://nats.example.com:4222`) works because the underlying client resolves and reconnects, but recovery time depends on DNS TTL and cache. An explicit comma-separated list of node addresses gives immediate visibility of every node and the fastest possible failover when one becomes unreachable. **Prefer the explicit list.** -## Step 4: Configure the three load balancers +## Step 4: Configure the load balancers Use the load balancer your organization already operates. NetBird works with any Layer 7 load balancer that supports HTTP/2 and gRPC, including HAProxy, NGINX, AWS Application Load Balancer, Google Cloud Load Balancing, Azure Application Gateway, and Envoy. -Plan one load-balancer frontend for each service pool: Management, Signal, and Relay. These can be separate load balancers or three frontends on a shared load balancer. Each frontend needs its own DNS name and backend pool. Configure the frontends and empty backend pools now, then add instances as you deploy the Relay, Signal, and Management services in Steps 5 through 7. +Plan one load-balancer frontend for each of the Management and Signal pools. These can be separate load balancers or two frontends on a shared load balancer. Each frontend needs its own DNS name and backend pool. Configure the frontends and empty backend pools now, then add instances as you deploy the Signal and Management services in Steps 6 and 7. The Relay pool does not go behind a load balancer; see Step 5. | Pool | Public FQDN | Backend port | Frontend protocol | Health check | |---|---|---|---|---| -| Relay | e.g. `relay.example.com` | 443 | HTTPS with WebSocket upgrade | TCP/443 | | Signal | e.g. `signal.example.com` | 443 | HTTPS, HTTP/2, gRPC | TCP/443 (or gRPC health if supported) | -| Management | e.g. `app.example.com` | 443 | HTTPS, HTTP/2, gRPC | `GET /api/health` → HTTP 200 | +| Management | e.g. `netbird.example.com` | 443 | HTTPS, HTTP/2, gRPC | `GET /oauth2/.well-known/openid-configuration` → HTTP 200 | Common requirements for every pool: | Requirement | Notes | |---|---| | **TLS termination** | **Prefer terminating TLS at the load balancer.** Set `server.tls` to empty on the Management replicas. If your environment requires TLS pass-through, configure TLS on every backend instead. | -| **HTTP/2 + gRPC support end-to-end** | Required for Management and Signal pools. The Relay pool uses WebSockets over HTTPS. Make sure your load balancer supports WebSocket upgrades and long-lived connections. | +| **HTTP/2 + gRPC support end-to-end** | Required for Management and Signal pools. Make sure your load balancer supports long-lived connections. | | **Connection-level affinity** | This is the default for Layer 4 load balancers and Layer 7 load balancers that use HTTP/2 streaming. A long-lived connection stays on one backend for its lifetime. **No application-level sticky sessions are required.** NATS reconciles cross-instance Signal state, while PostgreSQL and Redis hold Management state. | | **Active health checks** | Mark backends out of the pool on failure within seconds, not minutes. | -| **Connection draining on rolling upgrade** | When you remove a backend, the LB should let in-flight gRPC streams and WebSocket connections finish before tearing them down. | -| **Generous idle timeout** | Management gRPC streams and Relay WebSocket connections can be long-lived. Set the idle timeout above your peer-sync interval. Ten to 30 minutes is a comfortable range for both. | - -For the Relay frontend, configure the load balancer to strip any client-supplied `X-Real-Ip` and `X-Real-Port`, then overwrite both headers from the validated client connection. Each Relay instance must trust only the load balancer addresses that connect directly to it, using `NB_TRUSTED_PROXIES`. Without that trust, forwarded client addresses are intentionally ignored. +| **Connection draining on rolling upgrade** | When you remove a backend, the LB should let in-flight gRPC streams finish before tearing them down. | +| **Generous idle timeout** | Management and Signal gRPC streams can be long-lived. Set the idle timeout above your peer-sync interval. Ten to 30 minutes is a comfortable range for both. | For the full set of paths and protocols NetBird exposes to each load balancer, see [Configuration Files Reference](/selfhosted/maintenance/configuration-files). +The Management check confirms that a replica is up and serving. It keeps returning 200 while PostgreSQL or Redis is unreachable, so monitor those separately. + If you're using a managed cloud load balancer, configure the equivalent of each row above using your provider's UI or infrastructure as code. If you're using a self-hosted reverse proxy, create one backend pool per service, attach health checks, and enable HTTP/2 or WebSocket support as appropriate for each pool. ## Step 5: Deploy the Relay pool -Deploy at least two Relay instances on separate failure domains. Configure every instance with the same `NB_AUTH_SECRET` and `NB_EXPOSED_ADDRESS`, which is the Relay load balancer URL. The Relay load balancer distributes new WebSocket connections across instances. Each connection stays on one instance for its lifetime through the load balancer's natural connection affinity. +Deploy at least two Relay instances on separate failure domains. The Relay pool uses the **upstream Relay image** (`netbirdio/relay`). There is no Enterprise-specific Relay image. Relay does not validate a license. It authenticates incoming connections against the shared `NB_AUTH_SECRET`. -The Relay pool uses the **upstream Relay image** (`netbirdio/relay`). There is no Enterprise-specific Relay image. Relay does not validate a license. It authenticates incoming WebSocket connections against the shared `NB_AUTH_SECRET`. +Relay instances share no state with each other, which is why this pool works differently from Management and Signal. The instance a peer connects to becomes its home relay, and announces its own address to that peer. When two peers are on different instances, each reaches the other's instance directly by that announced address. So **every instance needs an `NB_EXPOSED_ADDRESS` of its own**, with a DNS name and a TLS certificate that peers can reach. -Run this on each Relay host. For example, use `relay-host-1` and `relay-host-2` as internal hostnames because peers reach the instances only through the load balancer URL: + +Do not put Relay instances behind a load balancer that gives them one shared address. Two peers whose connections land on different instances then cannot be relayed to each other: each waits for the other to come online on its own instance, and the connection never forms. Every peer still reports the relay as `Available`, so the only symptoms are peers that stay `Connecting`, with `peer not available: ..., context deadline exceeded` in the client log. + + +Choose how peers find an instance: + +| Option | `server.relays.addresses` | Which instance a peer uses | Choose it when | +|---|---|---|---| +| **1. List every instance** | Every instance's URL | The first to answer: the client connects to all of them in parallel and keeps the fastest, normally the nearest | By default. There is nothing extra to run. | +| **2. Geo-DNS** | One shared name that your DNS provider resolves to a nearby instance | The one DNS returns for the peer's location | You run many instances across regions and do not want every peer racing all of them. | + +In both options, a peer that loses its relay moves to another instance by itself, and peers on different instances still reach each other. + +### Option 1: list every instance + +Give each instance its own DNS name and let it obtain its own Let's Encrypt certificate for that name. On `us-1`, create `/etc/netbird/relay/relay.env`: + +```bash +NB_LOG_LEVEL=info +NB_LISTEN_ADDRESS=:443 +# This instance's own address. Different on every instance. +NB_EXPOSED_ADDRESS=rels://us-1.relay.example.com:443 +NB_AUTH_SECRET= +NB_LETSENCRYPT_DOMAINS=us-1.relay.example.com +NB_LETSENCRYPT_EMAIL=admin@example.com +NB_LETSENCRYPT_DATA_DIR=/data/letsencrypt +NB_ENABLE_STUN=true +NB_STUN_PORTS=3478 +``` + +The file holds the shared secret, so make it readable by root only: + +```bash +chmod 600 /etc/netbird/relay/relay.env +``` + +Then create `/etc/netbird/relay/docker-compose.yml`: ```yaml -# /etc/netbird/relay/docker-compose.yml on each Relay host services: relay: image: netbirdio/relay:latest container_name: netbird-relay restart: unless-stopped ports: - - "443:443" # WebSocket relay traffic (registered with the Relay LB) - - "3478:3478/udp" # STUN is direct and not load-balanced. See below. - environment: - - NB_LOG_LEVEL=info - - NB_LISTEN_ADDRESS=:443 - - NB_EXPOSED_ADDRESS=rels://relay.example.com:443 - - NB_AUTH_SECRET= - - NB_TRUSTED_PROXIES= - - NB_ENABLE_STUN=true - - NB_STUN_PORTS=3478 + - "443:443/tcp" # relay over WebSocket + - "443:443/udp" # relay over QUIC + - "3478:3478/udp" # STUN + env_file: + - relay.env + volumes: + - relay_data:/data + +volumes: + relay_data: ``` -| Env var | Required | Notes | -|---|---|---| -| `NB_EXPOSED_ADDRESS` | Yes | The Relay load balancer URL. **It must be identical on every instance.** This is the URL peers see, not the per-instance hostname. | -| `NB_AUTH_SECRET` | Yes | Shared with `server.relays.secret` in every Management replica's `config.yaml`. Must be byte-identical on every Relay instance and on every Management replica. | -| `NB_LISTEN_ADDRESS` | Yes | The address the relay binds inside the container. | -| `NB_LOG_LEVEL` | No | `debug`, `info` (default), `warn`, `error`. | -| `NB_TRUSTED_PROXIES` | When preserving client addresses behind the LB | Comma-separated immediate load-balancer IPs or CIDRs allowed to supply `X-Real-Ip` and `X-Real-Port`. Both headers are required. The load balancer must strip client-supplied values and overwrite them from the validated connection. Never use broad, uncontrolled ranges. | -| `NB_ENABLE_STUN` | No | Enables the embedded STUN server on UDP. | -| `NB_STUN_PORTS` | No | STUN port (default `3478`). | +Repeat on every instance with its own name, for example `eu-1.relay.example.com`. Open 443/tcp, 443/udp and 3478/udp to the internet on each. No inbound port 80 is needed: the relay proves its domain to Let's Encrypt over 443. -Start the relay on each host: +### Option 2: geo-DNS + +Option 2 keeps everything in option 1 and adds one shared name, `relay.example.com`, that resolves to a nearby instance. A peer connects to the shared name, lands on an instance, and from then on is known by that instance's own address. Your DNS provider must support location-based or latency-based answers, such as Route 53 geolocation or latency routing. + +Keep `NB_EXPOSED_ADDRESS` unique on every instance, exactly as in option 1. The shared name goes only in `server.relays.addresses`, never in `NB_EXPOSED_ADDRESS`. + +Two things change: + +- **The certificate.** A peer's first connection uses the shared name, and its connections to another peer's instance use that instance's name, so every instance must present one certificate valid for both. For example, `relay.example.com` plus `*.relay.example.com`. What breaks depends on which name is missing: + - Without the shared name, no peer can connect at all: `x509: certificate is valid for us-1.relay.example.com, not relay.example.com`. + - Without the instance's own name, every peer connects and looks healthy, but peers on different instances cannot relay to each other: `x509: certificate is valid for relay.example.com, not us-1.relay.example.com`. + + A wildcard needs DNS-based validation from your CA. The relay's built-in Let's Encrypt is not suitable: it proves the domain over port 443, and Let's Encrypt's check of the shared name reaches whichever instance DNS returns to Let's Encrypt. Every other instance fails with `acme/autocert: ... no viable challenge type found`. Supply the certificate yourself. Delete the three `NB_LETSENCRYPT_*` lines, set both `NB_TLS_CERT_FILE` and `NB_TLS_KEY_FILE`, and mount the files. Restart every instance after each renewal, because the relay reads the files only at startup. +- **Health checks on the DNS record.** Peers of a failed instance reconnect through the shared name, so the record must stop returning that instance within seconds. Attach health checks and keep the TTL low, or peers are sent back to the failed instance until the record changes. Once it does, peers re-home by themselves on their next reconnection attempt, which can take a minute or more. + +### Start and verify each instance ```bash -docker compose -f /etc/netbird/relay/docker-compose.yml up -d +cd /etc/netbird/relay +docker compose up -d docker compose logs -f relay # verify startup ``` -### Register the Relay instances in the Relay LB - -Add each Relay host's IP (or backend reference, depending on your LB) to the Relay LB's backend pool on port `443`. Once all instances are registered and healthy, verify: +Then check each instance by its own name from outside: ```bash -curl -I https://relay.example.com/ +curl -v https://us-1.relay.example.com/ ``` -You should get a `200 OK`. A `426 Upgrade Required` response is also healthy when the relay rejects non-WebSocket probes. +A `404 page not found` response with `SSL certificate verify ok` is healthy. With option 2, run the same check against `relay.example.com` from a client, and confirm the certificate is valid for that name too. ### STUN handling -STUN is served by the **Relay instances themselves**. Each relay container with `NB_ENABLE_STUN=true` runs an embedded STUN server on UDP/3478 alongside the WebSocket relay listener. You do **not** need a separate STUN/TURN service. +STUN is served by the **Relay instances themselves**. Each relay container with `NB_ENABLE_STUN=true` runs an embedded STUN server on UDP/3478 alongside the relay listener. You do **not** need a separate STUN/TURN service. -Tell peers where to find STUN through `server.stuns` in the Management configuration. UDP/3478 cannot pass through an HTTP or TCP load balancer, so peers reach each Relay instance's STUN endpoint **directly**. Configure one STUN URL and DNS A record for each Relay instance: +Tell peers where to find STUN through `server.stuns` in the Management configuration, with one entry per Relay instance under that instance's own name. -- Add an A record per relay host: `stun-1.example.com → relay-host-1`, `stun-2.example.com → relay-host-2`, etc. -- List each in `server.stuns` (see the config fragment below). Peers iterate the list during NAT discovery. +### How Management points at the Relay pool and STUN -### How Management points at the Relay LB and STUN - -Every Management replica's `config.yaml` references the Relay pool through a **single load balancer URL** in `server.relays.addresses`. The load balancer distributes traffic across Relay instances, so this list always has one entry. Configure STUN separately under `server.stuns`: +Every Management replica's `config.yaml` lists the Relay pool in `server.relays.addresses` and STUN in `server.stuns`. With option 1, list every instance: ```yaml server: - # External STUN: per-instance hostnames (option 1) or external STUN/TURN (option 2) stuns: - - uri: "stun:stun-1.example.com:3478" + - uri: "stun:us-1.relay.example.com:3478" proto: "udp" - - uri: "stun:stun-2.example.com:3478" + - uri: "stun:eu-1.relay.example.com:3478" proto: "udp" - # External Relay pool: one URL pointing at the Relay load balancer + relays: + addresses: + - "rels://us-1.relay.example.com:443" + - "rels://eu-1.relay.example.com:443" + secret: "" + credentialsTTL: "24h" +``` + +With option 2, `relays.addresses` holds only the shared name: + +```yaml relays: addresses: - "rels://relay.example.com:443" - secret: "" - credentialsTTL: "24h" ``` The full Management `config.yaml` is assembled in [Step 7](#step-7-configure-and-deploy-the-management-pool). @@ -492,6 +627,8 @@ services: | `NATS_ENDPOINTS` | Yes | Comma-separated list of NATS cluster endpoints from Step 3. Identical on every Signal instance. | | `SINGLE_NODE_MODE` | Yes | Must be `false`. When unset or set to `true`, Signal runs in single-node mode without NATS coordination. This mode is incompatible with HA. | +When the NATS URLs carry a password, as in the [Step 3 example](#example-a-self-hosted-cluster-with-docker-compose), set `NATS_ENDPOINTS` in a file listed under `env_file`, readable by root only, instead of in `docker-compose.yml`. + Signal gRPC requires TLS. Either terminate TLS at the Signal load balancer and forward plain HTTP/2 (h2c) to backends on port 443, or pass TLS through to the backends. With TLS pass-through, mount certificates into each Signal container and adjust the `--port` or listen address as needed. Start Signal on each host: @@ -527,26 +664,41 @@ The full Management `config.yaml` is assembled in [Step 7](#step-7-configure-and ## Step 7: Configure and deploy the Management pool -Now configure the Management replicas to point at everything you've set up: Postgres, Redis, NATS, the Relay LB URL, and the Signal LB URL. Distribute the same `config.yaml` to every enterprise Management replica. +Now configure the Management replicas to point at everything you've set up: Postgres, Redis, NATS, the Relay instances, and the Signal LB URL. Every replica runs the Enterprise combined server image, `ghcr.io/netbirdio/netbird-server-cloud`, which the Enterprise installer also deploys. Distribute the same `config.yaml` to every replica. -`server.relays.addresses` and `server.signalUri` each take **one URL**, which is the load balancer URL for that pool. The load balancer distributes traffic across the instances behind it, so NetBird does not need to know the individual Relay or Signal instance addresses. +Keep the `auth:` block from your existing deployment's `config.yaml`. Without it the server exits at startup with `failed to create embedded IDP service: issuer is required`. + +`server.signalUri` takes **one URL**, the Signal load balancer's, which distributes traffic across the Signal instances. `server.relays.addresses` takes every Relay instance's URL with option 1, or only the geo-DNS name with option 2. See [Step 5](#step-5-deploy-the-relay-pool). ```yaml server: - exposedAddress: "https://app.example.com:443" + exposedAddress: "https://netbird.example.com:443" dataDir: "/var/lib/netbird/" - # External STUN: see Step 5 for the per-instance and external STUN options + # Embedded identity provider: keep the block from your existing config.yaml + auth: + issuer: "https://netbird.example.com/oauth2" + localAuthDisabled: false + signKeyRefreshEnabled: false + sessionCookieEncryptionKey: "" + dashboardRedirectURIs: + - "https://netbird.example.com/nb-auth" + - "https://netbird.example.com/nb-silent-auth" + cliRedirectURIs: + - "http://localhost:53000/" + + # External STUN: one entry per Relay instance, see Step 5 stuns: - - uri: "stun:stun-1.example.com:3478" + - uri: "stun:us-1.relay.example.com:3478" proto: "udp" - - uri: "stun:stun-2.example.com:3478" + - uri: "stun:eu-1.relay.example.com:3478" proto: "udp" - # External Relay pool: one URL pointing at the Relay load balancer + # Relay pool: every instance (option 1). With option 2, only the geo-DNS name. relays: addresses: - - "rels://relay.example.com:443" + - "rels://us-1.relay.example.com:443" + - "rels://eu-1.relay.example.com:443" secret: "" credentialsTTL: "24h" @@ -575,7 +727,7 @@ server: trafficFlow: enabled: true - address: "https://app.example.com:443" + address: "https://netbird.example.com:443" interval: "60s" ``` @@ -597,20 +749,82 @@ Bring up replicas one at a time and register each in the Management LB once heal nats --server nats://nats-1.example.com:4222 server check connection # expect: OK ``` 2. **Distribute `config.yaml`** to every Management replica host. Verify identical files (`sha256sum config.yaml`) on each. -3. **Start replica 1**. Wait for `/api/health` to return 200 and check the logs for `Management server created` followed by `Starting CloudServer`. -4. **Register replica 1** in the Management LB. Confirm the dashboard is reachable via `https://app.example.com/`. +3. **Start replica 1**. Wait for `/oauth2/.well-known/openid-configuration` to return 200 and check the logs for `Management server created` followed by `Starting CloudServer`. +4. **Register replica 1** in the Management LB. Confirm the dashboard is reachable via `https://netbird.example.com/`. 5. **Start replica 2**, verify health, register in the LB. 6. **Repeat for any additional replicas.** +### Deploy the flow receiver and enricher + +The `trafficFlow` block above tells peers to send their traffic events to `https://netbird.example.com:443`. The Management server does not receive them. Two Enterprise services do: + +- The **flow receiver** (`ghcr.io/netbirdio/flow-receiver-cloud`) accepts the events from peers over gRPC and publishes them to the `traffic-events` stream in NATS. +- The **flow enricher** (`ghcr.io/netbirdio/flow-enricher-cloud`) reads the stream and writes the events to PostgreSQL, where the dashboard reads them. + +Without them, peers keep connecting normally, but no traffic events ever appear and the client log repeats `flow receiver sent no headers`. + +Run one receiver and one enricher next to each Management replica. If one host fails, peers send their events through the receiver on another host, and the enrichers share the stream without storing an event twice. + +```yaml +services: + receiver: + image: ghcr.io/netbirdio/flow-receiver-cloud:latest + restart: unless-stopped + ports: + - "8084:8084" + environment: + - NB_LICENSE_KEY= + - NB_FLOW_LISTEN_PORT=8084 + - NB_FLOW_ADAPTER_TYPE=nats + - NB_FLOW_NATS_ENDPOINTS=nats://netbird:@nats-1.example.com:4222,nats://netbird:@nats-2.example.com:4222,nats://netbird:@nats-3.example.com:4222 + - NB_FLOW_NATS_STREAM=traffic-events + - NB_FLOW_NATS_SUBJECT=traffic-events.flow + - NB_FLOW_AUTH_SECRET= + + enricher: + image: ghcr.io/netbirdio/flow-enricher-cloud:latest + restart: unless-stopped + volumes: + - netbird_enricher:/var/lib/netbird + environment: + - NB_LICENSE_KEY= + - NB_DATADIR=/var/lib/netbird + - NB_MANAGEMENT_STORE_ENGINE=postgres + - NB_MANAGEMENT_POSTGRES_DSN=host=pg.example.com user=netbird password=*** dbname=netbird port=5432 sslmode=require + - NETBIRD_STORE_ENGINE_POSTGRES_DSN=host=pg.example.com user=netbird password=*** dbname=netbird port=5432 sslmode=require + - NB_TRAFFIC_EVENT_STORE_ENGINE=postgres + - NB_TRAFFIC_EVENT_POSTGRES_DSN=host=pg.example.com user=netbird password=*** dbname=netbird port=5432 sslmode=require + - NB_MANAGEMENT_STORE_KEY= + - NB_FLOW_ADAPTER_TYPE=nats + - NB_FLOW_NATS_ENDPOINTS=nats://netbird:@nats-1.example.com:4222,nats://netbird:@nats-2.example.com:4222,nats://netbird:@nats-3.example.com:4222 + - NB_FLOW_NATS_STREAM=traffic-events + - NB_METRICS_PORT=9091 + - NB_PERSISTENCE_RETENTION_PERIOD=168h + +volumes: + netbird_enricher: +``` + +Two values must match what you configured earlier. Each one fails silently when it does not: + +| Setting | Must be | If it is not | +|---|---|---| +| `NB_FLOW_NATS_SUBJECT` | A subject under `traffic-events.`, the stream's subjects from [Step 3](#traffic-flow-stream) | The default, `netbird.flow.events`, matches no stream. Every publish fails and the receiver logs `failed to publish message to nats: nats: no response from stream`. | +| `NB_FLOW_AUTH_SECRET` | The same value as `server.relays.secret` | Management signs the peers' flow tokens with the relay secret. The receiver rejects every peer with `invalid token validation: invalid signature`. | + +Then route the events to the receivers. On the Management load balancer, send the path prefix `/flow.FlowService/` on `netbird.example.com` to port 8084 on every receiver, over HTTP/2 like the other gRPC paths. Without that route, the events never reach a receiver. + +To verify, generate traffic between two peers in a group with traffic events enabled. `nats stream info traffic-events` shows the message count rising, and `GET /api/events/network-traffic` returns the events. + ## Step 8: Verify HA end to end Run each failure scenario and confirm the expected behavior. Do this in a staging environment first. | Scenario | Expected behavior | |---|---| -| Stop one Management replica | The Management LB marks it unhealthy within seconds; API traffic continues on remaining replicas. Established peer connections (signal, relay) are unaffected because they flow through the Signal and Relay LBs, not the Management LB. | +| Stop one Management replica | The Management LB marks it unhealthy within seconds; API traffic continues on remaining replicas. Established peer connections (signal, relay) are unaffected because they flow through the Signal LB and directly to the Relay instances, not through the Management LB. | | Stop one Signal instance | The Signal LB marks it unhealthy; peers connected to that instance reconnect via the LB and land on a surviving Signal instance. Cross-peer signaling continues via NATS without interruption to peers that were already connected to a different instance. | -| Stop one Relay instance | The Relay LB marks it unhealthy; new peer relay sessions go to surviving instances. Existing WebSocket sessions on the killed instance drop and peers re-establish via the LB. | +| Stop one Relay instance | Peers homed on that instance move to another by themselves: with option 1 to the next instance that answers, with option 2 to the instance DNS returns once its health check drops the stopped one. Relayed connections through the stopped instance drop and re-establish. Peers homed on other instances are unaffected. | | Stop one NATS node | Cluster retains quorum (2 of 3 healthy). Writes to the `traffic-events` stream continue. Cross-instance Signal routing continues. | | Disconnect Redis | New OAuth flows fail until Redis returns; established peer connections continue working. Dynamic log/rate-limit changes pause. | | Trigger PostgreSQL failover (managed service) | Brief outage during the failover; Management replicas reconnect to the new primary and resume. Existing peer connections survive the gap because they don't touch PostgreSQL on every message. | @@ -621,7 +835,7 @@ If any scenario doesn't match the expected behavior, see [Troubleshooting](#trou ### Rolling upgrades -The procedure is the same for any of the three pools: drain one instance, upgrade it, return it to the LB, repeat. Schema migrations on the Management pool run automatically on first startup of any replica; subsequent replicas detect the migrated schema and start without re-running it. +For the Management and Signal pools, drain one instance, upgrade it, return it to the LB, and repeat. Schema migrations on the Management pool run automatically on first startup of any replica; subsequent replicas detect the migrated schema and start without re-running it. 1. Mark instance #1 as draining in its load balancer. Wait for active gRPC streams or WebSocket connections to drain (or hit your drain timeout). 2. Stop and re-pull the image on instance #1: `docker compose pull && docker compose up -d`. @@ -629,13 +843,17 @@ The procedure is the same for any of the three pools: drain one instance, upgrad 4. Add instance #1 back to the LB pool. 5. Repeat for the remaining instances in the pool. +The Relay pool has no load balancer to drain. Upgrade one instance at a time, and peers on it move to another instance by themselves. With option 2, take the instance out of the DNS record first. + Upgrade Relay, Signal, and Management pools in any order. The protocols between pools are stable across patch releases. ### Adding or removing instances -To **add** an instance to a pool, provision the host, install the same image with the same configuration as existing instances, start the service, and add it to the load balancer pool once healthy. No additional coordination is required because the new instance reads the same shared state. +To **add** a Management or Signal instance, provision the host, install the same image with the same configuration as existing instances, start the service, and add it to the load balancer pool once healthy. No additional coordination is required because the new instance reads the same shared state. -To **remove** an instance: mark it as draining in the LB, wait for streams to drain, stop the service. The remaining instances continue serving traffic. +A Relay instance is different: it needs its own name, certificate and `NB_EXPOSED_ADDRESS`. With option 1, add its URL to `server.relays.addresses` and its STUN entry to `server.stuns` on every Management replica, then restart the replicas one at a time. With option 2, add it to `server.stuns` the same way, and to the geo-DNS record once it is healthy. + +To **remove** a Management or Signal instance: mark it as draining in the LB, wait for streams to drain, stop the service. The remaining instances continue serving traffic. To remove a Relay instance, first take it out of `server.relays.addresses` (option 1) or the geo-DNS record (option 2), and out of `server.stuns`, then stop it. ### Rotating secrets @@ -643,7 +861,7 @@ To **remove** an instance: mark it as draining in the LB, wait for streams to dr |---|---| | `POSTGRES_PASSWORD` / Postgres user password | Update the password in PostgreSQL, update the DSN in every Management replica's `config.yaml`, restart Management replicas one at a time. | | Redis password | Update Redis, update `server.ha.redisAddr` on every Management replica, restart Management replicas one at a time. | -| `NB_AUTH_SECRET` (relay auth) | Update every Relay instance simultaneously, then update `server.relays.secret` on every Management replica at the same time. Peer relay sessions can be rejected while the values differ. Restart Management and Relay together to minimise this window. | +| `NB_AUTH_SECRET` (relay auth) | Update every Relay instance simultaneously, then update `server.relays.secret` on every Management replica at the same time. Peer relay sessions can be rejected while the values differ. Restart Management and Relay together to minimise this window. On each Relay instance, apply the new value with `docker compose up -d`: `docker compose restart` keeps the old one. | | `server.store.encryptionKey` | **Do not rotate.** This key encrypts data at rest in PostgreSQL. Rotating it makes existing encrypted data unreadable. Plan a fresh deployment if you need to change it. | ## Troubleshooting @@ -680,13 +898,19 @@ nats --server nats://nats-1.example.com:4222 server check jetstream If a node is down, restart it and wait for it to rejoin the cluster. The `traffic-events` stream resumes writes as soon as quorum returns. -### Relay rejects peer connections with `auth: invalid token` +### Relay rejects peer connections: `invalid signature` -`NB_AUTH_SECRET` on the Relay instances differs from `server.relays.secret` on the Management replicas. The two values must be byte-identical. Confirm on every Relay container and in every Management replica's `config.yaml`. +`NB_AUTH_SECRET` on the Relay instances differs from `server.relays.secret` on the Management replicas. The relay logs `failed to handshake: validate ... invalid signature` for every rejected peer. The two values must be byte-identical. Confirm on every Relay container and in every Management replica's `config.yaml`. -### Management replica refuses to start: `Redis is required for multi-instance mode` +### Relayed connections never form between some peers: `peer not available` -`server.ha.enabled: true` but `server.ha.redisAddr` is empty or unreachable. Set the address and confirm connectivity from the replica: +Some pairs of peers stay `Connecting` while others relay normally, every peer reports the relay as `Available`, and the client log shows `peer not available: ..., context deadline exceeded`. The Relay instances share one `NB_EXPOSED_ADDRESS`, usually because they sit behind a load balancer, so peers on different instances cannot find each other. Give every instance its own address, as in [Step 5](#step-5-deploy-the-relay-pool). + +If the client log shows `x509: certificate is valid for relay.example.com, not us-1.relay.example.com` instead, the addresses are right but an instance's certificate lacks its own name. See option 2 in [Step 5](#step-5-deploy-the-relay-pool). + +### Management replica refuses to start: Redis + +`server.ha.enabled: true` needs a reachable `server.ha.redisAddr`. If it is empty, the replica exits with `server.ha.redisAddr is required when ha.enabled is true`. If Redis cannot be reached, it exits with `failed to create shared cache store: dial tcp ...: connect: connection refused`. Set the address and confirm connectivity from the replica: ```bash redis-cli -u "" ping diff --git a/src/pages/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment.mdx b/src/pages/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment.mdx index aa1fc07a..a8b539cb 100644 --- a/src/pages/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment.mdx +++ b/src/pages/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment.mdx @@ -23,11 +23,12 @@ The default single-server deployment runs all services on one machine: **Traefik and a **combined netbird-server** container that includes Management, Signal, and Relay + STUN as components. Traefik handles TLS termination on ports 80/443, while STUN listens on UDP port 3478. The Management server uses a **SQLite** database by default. After splitting, the **main server** keeps Traefik, Dashboard, Management, and optionally Signal. -The **relay servers** run independently on different machines, each handling relay (port 443) and STUN (port 3478) traffic. Peers receive relay addresses from the Management server and connect to them directly. Optionally, the SQLite database can be migrated to **PostgreSQL** on a dedicated server, and Signal can also be extracted to its own machine. +The **relay servers** run independently on different machines, each handling relay (port 443, TCP for WebSocket and UDP for QUIC) and STUN (port 3478/udp) traffic. Peers receive relay addresses from the Management server and connect to them directly. Optionally, the SQLite database can be migrated to **PostgreSQL** on a dedicated server, and Signal can also be extracted to its own machine. ## Guides - [Set Up External Relay Servers](/selfhosted/maintenance/scaling/set-up-external-relays) — Deploy relay and STUN servers on separate machines and configure the main server to use them +- [External Relays on a Commercial License](/selfhosted/enterprise/external-relays) — What differs on NetBird Enterprise, where the shared secret is also used by traffic flow - [Migrate from SQLite to PostgreSQL](/selfhosted/maintenance/scaling/migrate-sqlite-to-postgresql) — Move the Management database to a dedicated PostgreSQL instance (optional) - [Set Up External Signal Server](/selfhosted/maintenance/scaling/set-up-external-signal) — Extract the Signal server to its own machine (optional) @@ -46,6 +47,7 @@ The **relay servers** run independently on different machines, each handling rel | `NB_LETSENCRYPT_EMAIL` | No | Email for Let's Encrypt notifications | | `NB_TLS_CERT_FILE` | No | Path to TLS certificate (alternative to Let's Encrypt) | | `NB_TLS_KEY_FILE` | No | Path to TLS private key | +| `NB_TRUSTED_PROXIES` | No | Comma-separated IPs or CIDRs whose `X-Real-Ip` and `X-Real-Port` headers the relay trusts. Since v0.75.0 | | `NB_LOG_LEVEL` | No | Log level: `debug`, `info`, `warn`, `error` | ### Main Server config.yaml - External Services diff --git a/src/pages/selfhosted/maintenance/scaling/set-up-external-relays.mdx b/src/pages/selfhosted/maintenance/scaling/set-up-external-relays.mdx index f714e040..4333a7ce 100644 --- a/src/pages/selfhosted/maintenance/scaling/set-up-external-relays.mdx +++ b/src/pages/selfhosted/maintenance/scaling/set-up-external-relays.mdx @@ -4,15 +4,25 @@ import {Note, Warning} from "@/components/mdx"; This guide is part of the [Splitting Your Self-Hosted Deployment](/selfhosted/maintenance/scaling/scaling-your-self-hosted-deployment) guide. It covers deploying external relay and STUN servers and configuring your main server to use them. + +Running NetBird Enterprise with a commercial license? Use [External Relays on a Commercial License](/selfhosted/enterprise/external-relays) instead. It is the same procedure written for an Enterprise deployment, and it is complete on its own. Following this page would have you generate a new shared secret, which on an Enterprise deployment with traffic flow stops traffic event logging without breaking connectivity. + + For each relay server you want to deploy: ## Server Requirements -- A Linux VM with at least **1 CPU** and **1GB RAM** +- A Linux VM with at least **1 CPU** and **1 GB RAM** - Public IP address - A domain name pointing to the server (e.g., `relay-us.example.com`) - Docker installed -- Firewall ports open: **80/tcp** (Let's Encrypt HTTP challenge), **443/tcp** (relay), and **3478/udp** (STUN). If you configure multiple STUN ports, open all of them +- Firewall ports open: **443/tcp** and **443/udp** (relay), and **3478/udp** (STUN). If you configure multiple STUN ports, open all of them + + +**No inbound port 80 is needed.** A relay proves its domain to Let's Encrypt over 443 itself, using the TLS-ALPN-01 challenge. Your main NetBird server is different and does still need 80, and so may a proxy in front of the relay. + +**Both 443 rules matter.** The relay serves two transports on that port: WebSocket over TCP and QUIC over UDP. Clients race both and use whichever connects first. With 443/udp closed the QUIC attempt runs until it times out, WebSocket carries the connection, and nothing reports an error, so the missing rule is easy to overlook. A relay behind a TLS-terminating reverse proxy is the exception: it cannot serve QUIC, so it does not need 443/udp. See [Running the Relay Behind a Proxy or Load Balancer](#running-the-relay-behind-a-proxy-or-load-balancer). + ## Generate Authentication Secret @@ -23,7 +33,9 @@ All relay servers must share the same authentication secret with your main serve openssl rand -base64 32 ``` -Save this secret - you'll need it for both the relay servers and your main server's config. +Save this secret. The identical value goes in two settings: `NB_AUTH_SECRET` on every relay server, and `relays.secret` on your main server. + +Paste it from one source into each file rather than retyping it. A single wrong character produces a relay that looks healthy and refuses every peer. ## Create Relay Configuration @@ -42,9 +54,6 @@ NB_LISTEN_ADDRESS=:443 NB_EXPOSED_ADDRESS=rels://relay-us.example.com:443 NB_AUTH_SECRET=your-shared-secret-here -# Only when a reverse proxy or load balancer is directly in front of the relay -# NB_TRUSTED_PROXIES=10.20.0.10,10.20.1.0/24 - # TLS via Let's Encrypt (automatic certificate provisioning) NB_LETSENCRYPT_DOMAINS=relay-us.example.com NB_LETSENCRYPT_EMAIL=admin@example.com @@ -59,6 +68,14 @@ NB_STUN_PORTS=3478 Replace `relay-us.example.com` with your relay server's domain and `your-shared-secret-here` with the secret you generated.
+The file holds the shared secret, so make it readable by root only: + +```bash +chmod 600 relay.env +``` + +The relay reads `relay.env` when its container is created. If you change the file later on a running relay, apply it with `docker compose up -d`. `docker compose restart` keeps the old values. + Create `docker-compose.yml`: ```yaml @@ -68,7 +85,10 @@ services: container_name: netbird-relay restart: unless-stopped ports: - - '443:443' + # Both relay transports: WebSocket over TCP and QUIC over UDP. + # Docker treats a bare '443:443' as TCP only, so the UDP line is required. + - '443:443/tcp' + - '443:443/udp' # Expose all ports listed in NB_STUN_PORTS - '3478:3478/udp' env_file: @@ -87,31 +107,68 @@ volumes: ## Running the Relay Behind a Proxy or Load Balancer -When a reverse proxy or load balancer terminates the Relay WebSocket connection, set `NB_TRUSTED_PROXIES` to the IP addresses or CIDRs of the proxies that connect **directly** to the relay: +In this shape your proxy holds the certificate and the relay serves plain HTTP behind it. + +Use this shape only because a load balancer or proxy is already in front of your relays. Do not add one just to terminate TLS on the relay host: the relay already does that itself, and a proxy in front of it costs you QUIC. + +**On the relay host**, `relay.env` has no `NB_LETSENCRYPT_*` lines, listens on an internal port, and names the proxy's address: ```bash -NB_TRUSTED_PROXIES=10.20.0.10,10.20.1.0/24 +NB_LOG_LEVEL=info +NB_LISTEN_ADDRESS=:8080 +NB_EXPOSED_ADDRESS=rels://relay-us.example.com:443 +NB_AUTH_SECRET=your-shared-secret-here +NB_TRUSTED_PROXIES=10.20.0.10 +NB_ENABLE_STUN=true +NB_STUN_PORTS=3478 ``` -The proxy must strip any client-supplied `X-Real-Ip` and `X-Real-Port`, then overwrite both headers with the address and source port from the validated client connection. The relay accepts those headers only when the immediate connection source matches the trusted list. If either header is missing, the list is empty, or the immediate source is not trusted, the relay ignores the headers and uses the direct connection address. +Keep `rels://` written out in `NB_EXPOSED_ADDRESS`. With no TLS of its own, the relay would otherwise advertise `rel://`. + +In `docker-compose.yml`, remove the two `443` port lines and keep `3478:3478/udp` for STUN. Make port 8080 reachable from the proxy and nothing else. If the proxy is on another machine, publish the port with `'8080:8080/tcp'` and allow it from the proxy's address only, at a network firewall or cloud security group. A host firewall such as UFW or firewalld does not restrict it, because Docker-published ports bypass those rules. + +**Your proxy must:** + +- hold a certificate for the relay's domain, and accept connections on 443/tcp; +- forward WebSocket upgrades over HTTP/1.1 to the relay's port 8080; +- set both `X-Real-Ip` and `X-Real-Port` to the address and source port of the connection it received, overwriting anything the client sent. + +The relay uses those two headers only when the connection comes from an address in `NB_TRUSTED_PROXIES`, and only when both are present. A proxy that sets only `X-Real-Ip` gets its own address logged for every peer. A proxy that passes the client's headers through lets any client write the address of its choice into your relay logs. + +`NB_TRUSTED_PROXIES` is the address the relay sees the proxy's connections come from, which is not always the proxy's public address. When the proxy and relay share a Docker network, it is the proxy container's address on that network. When the proxy is another machine on the same private network, it is that machine's private address: a published Docker port keeps the original source address. -Trust only the smallest proxy IP ranges you control. Do not trust `0.0.0.0/0`, `::/0`, or a shared network where untrusted systems can connect directly to the relay and forge forwarding headers. Invalid IP or CIDR entries prevent the relay from starting. +Trust only the smallest proxy ranges you control. Never `0.0.0.0/0`, `::/0`, or a shared network where an untrusted system could reach the relay directly and forge these headers. Entries are IP addresses or CIDRs separated by commas. Hostnames are rejected, and a prefix such as `10.20.0.10/24` trusts the whole `/24`. -The CLI equivalent is `--trusted-proxies`. Restart the relay after changing the list and check its startup logs for parsing errors. +These headers decide only which client address the relay records, including in the `invalid signature` lines used for troubleshooting. Authentication does not depend on them. + +Apply a change to `NB_TRUSTED_PROXIES` with `docker compose up -d relay`. An invalid entry stops the relay: it exits with `failed to parse trusted proxies: ...` and restarts in a loop until you fix it. To confirm the setting works, check that the relay logs your peers' addresses rather than the proxy's: + +```bash +docker compose logs relay | grep 'WS client connected from' +``` + +The CLI equivalent of `NB_TRUSTED_PROXIES` is `--trusted-proxies`, available since **v0.75.0**. + +At startup the relay reports that it cannot serve QUIC: + +``` +WARN relay/server/server.go:78: Not starting QUIC listener: valid TLS config is required for QUIC listener +``` + +That is expected here. Peers connect over WebSocket, so neither the proxy nor the relay host needs 443/udp. The proxy needs 443/tcp, plus whatever it uses to obtain its own certificate, often 80/tcp. The relay host needs 3478/udp for STUN. ## Alternative: TLS with Existing Certificates -If you have existing TLS certificates (e.g., from your own CA or a wildcard cert), replace the Let's Encrypt variables in `relay.env` with: +If you have existing TLS certificates (e.g., a wildcard certificate, or one from your own CA), delete the three `NB_LETSENCRYPT_*` lines from `relay.env` and add: ```bash -# Replace the NB_LETSENCRYPT_* lines with: NB_TLS_CERT_FILE=/certs/fullchain.pem NB_TLS_KEY_FILE=/certs/privkey.pem ``` -And add a certificate volume to `docker-compose.yml`: +Then replace the relay service's `volumes:` list in `docker-compose.yml` with this one. Do not add it as a second `volumes:` key, which makes the file invalid: ```yaml volumes: @@ -119,6 +176,13 @@ And add a certificate volume to `docker-compose.yml`: - relay_data:/data ``` +`fullchain.pem` is your certificate followed by any intermediates, and the certificate must cover the relay's domain. Four things catch people out: + +- **Set both variables, and remove every `NB_LETSENCRYPT_*` line.** If a Let's Encrypt line remains, the relay uses Let's Encrypt and ignores your files, and the `curl -v` check below shows a Let's Encrypt issuer instead of yours. If only one `NB_TLS_*` variable is set, the relay starts with no TLS at all, and the sign is a missing QUIC line at startup. +- **Nothing in the startup log names your certificate.** The `curl -v` check below is how you confirm it is the one being served. +- **The files are read once, at startup.** Restart the relay after every renewal: `docker compose restart relay`. +- **A private CA must be trusted by every peer.** NetBird checks the relay's certificate against each device's operating-system trust store, and has no setting for a separate CA. A device that does not trust yours reports the relay as `Unavailable` with `x509: certificate signed by unknown authority`, and cannot relay to other peers. Distribute the CA to every device before you switch. + ## Start the Relay Server ```bash @@ -131,19 +195,26 @@ Verify it's running: docker compose logs -f ``` -You should see: +You should see the relay announce its exposed address, both listeners, and the STUN server: + ``` -level=info msg="Starting relay server on :443" -level=info msg="Starting STUN server on port 3478" +INFO relay/cmd/root.go:242: server will be available on: rels://relay-us.example.com:443 +INFO relay/server/listener/ws/listener.go:51: WS server listening address: :443 +INFO relay/server/listener/quic/listener.go:39: QUIC server listening on address: :443 +INFO [component: stun] stun/server.go:71: STUN server listening on [::]:3478 ``` +Other lines appear alongside these, including the Let's Encrypt setup, the health check server and the metrics server. The order changes from one start to the next, because the listeners come up concurrently. What matters is that all four lines are present, not where they sit. + +A missing QUIC line means the relay has no TLS configuration of its own, which is expected only behind a TLS-terminating proxy. + If you configured Let's Encrypt, the relay generates TLS certificates lazily on the first incoming request. Trigger certificate provisioning and verify it by running: ```bash curl -v https://relay-us.example.com/ ``` -A `404 page not found` response is expected — what matters is that the TLS handshake succeeds. Look for a valid Let's Encrypt certificate in the output: +A `404 page not found` response is expected. What matters is that the TLS handshake succeeds: ``` * Server certificate: @@ -152,6 +223,18 @@ A `404 page not found` response is expected — what matters is that the TLS han * SSL certificate verify ok. ``` +The issuer's `CN` names whichever intermediate signed your certificate, so yours will often differ. + +Two things can go wrong here, and they look different. If the first attempt fails with a TLS error such as `SSL_ERROR_SYSCALL`, wait a few seconds and run it again: that first request is what triggers issuance, and it can time out while that happens. + +If instead the command hangs with no output and no error, issuance is stuck rather than slow, and only the relay can tell you why: + +```bash +docker compose logs relay | grep -i acme +``` + +A rate limit is the likeliest cause if you have rebuilt the same relay host several times. Let's Encrypt allows five certificates per week for one exact set of domain names. + ## Repeat for Additional Relay Servers If deploying multiple relays (e.g., for different regions), repeat the steps above on each server. Use the **same `NB_AUTH_SECRET`** but update the domain name for each. @@ -169,15 +252,23 @@ cd ~/netbird # or wherever your deployment is nano config.yaml ``` -Remove the `authSecret` from the `server` section and add `relays` and `stuns` sections pointing to your external servers. The presence of the `relays` section disables both the embedded relay and the embedded STUN server, so the `stuns` section is required to provide external STUN addresses: +Add `stuns` and `relays` inside the existing `server:` block, at the same indentation as the keys already there. Anywhere inside that block works. + +This is an addition, not a replacement. Leave every key already under `server:` exactly as it is. A quickstart deployment also carries `auth`, `reverseProxy` and `store` sections there, holding your OIDC redirect URIs, your reverse-proxy trust settings and your datastore encryption key. Overwriting any of them breaks the deployment, so do not paste the example below over your existing block. + +The presence of `relays.addresses` is what disables the embedded relay, and it disables the embedded STUN server too, so the `stuns` section is required to provide external STUN addresses. + +You do not need to remove `server.authSecret`. It is required only when the embedded relay is running. Leaving it in place is also the safer choice on a NetBird Enterprise deployment, where the same value has a second job: see [External Relays on a Commercial License](/selfhosted/enterprise/external-relays). + +Both `relays` and `stuns` are available since **v0.65.0**. ```yaml server: listenAddress: ":80" exposedAddress: "https://netbird.example.com:443" - # Remove authSecret to disable the embedded relay - # authSecret: ... - # Remove or comment out stunPorts since we're using external STUN + # Leave authSecret as it is. relays.addresses below is what disables the embedded relay. + authSecret: "your-existing-secret" + # stunPorts no longer applies once stuns is set, so you can comment it out # stunPorts: # - 3478 metricsPort: 9090 @@ -201,18 +292,20 @@ server: secret: "your-shared-secret-here" credentialsTTL: "24h" - auth: - issuer: "https://netbird.example.com/oauth2" - # ... rest of auth config + # ... the rest of your existing configuration is unchanged ``` -The `secret` under `relays` and the `NB_AUTH_SECRET` on all relay servers **must be identical**. Mismatched secrets will cause relay connections to fail silently. +The `secret` under `relays` and the `NB_AUTH_SECRET` on every relay server **must be identical**. If they differ, peers cannot use that relay. +A mismatch is easiest to spot on the relay host, which logs `failed to handshake: ... invalid signature` for every rejected peer. + +It is harder to spot from a peer, because a peer lists only the relays it is actually using. One bad relay among several never appears as a failure: the peer picks a working one instead, and the broken relay is simply absent. Only when every relay rejects does `netbird status -d` report `Unavailable, reason: failed to get reader: failed to read frame header: EOF`. The `stun:` entry stays `Available` throughout, because STUN is unauthenticated. + ### Update docker-compose.yml (Optional) -If your main server was exposing STUN port 3478, you can remove it since STUN is now handled by external relays: +If your main server was exposing STUN port 3478, you can remove it, since STUN is now handled by external relays. Change only the `ports` entry and leave the rest of the service as it is: ```yaml netbird-server: @@ -227,15 +320,53 @@ If your main server was exposing STUN port 3478, you can remove it since STUN is - netbird_data:/var/lib/netbird - ./config.yaml:/etc/netbird/config.yaml command: ["--config", "/etc/netbird/config.yaml"] + # ... the rest of this service, including its labels, is unchanged ``` ### Restart the Main Server +Which command you need depends on whether you did the optional `docker-compose.yml` step above. + +**If you edited `config.yaml` only**, restart the container: + ```bash -docker compose down -docker compose up -d +docker compose restart netbird-server ``` +`config.yaml` is a bind mount, so editing it does not change anything Docker Compose compares. `docker compose up -d` would report the service as already up to date, and the server would keep serving the old configuration. + +**If you also removed the STUN port from `docker-compose.yml`**, recreate the container instead: + +```bash +docker compose up -d netbird-server +``` + +A published port is fixed when the container is created, so `docker compose restart` cannot remove one: the container comes back with the mapping still in place and nothing reports a problem. Recreating the container applies both files at once, so this one command covers the `config.yaml` change as well. + + +Avoid `docker compose down` here. It stops every other service too, including your reverse proxy and, on larger deployments, the database and the traffic-flow services. + + +## The Embedded Relay Is Now Off + +Setting `relays.addresses` switches the embedded relay off. There is no setting that keeps it running alongside external relays, and listing the main server's own address among them does not bring it back: clients are handed an address that never answers. + +```bash +curl https://netbird.example.com/relay +``` + +``` +Relay service not enabled +``` + +The endpoint answers with HTTP 404 and that single line as its body. Add `-i` to the command if you want to see the status code as well. + +From here on, relay capacity is whatever your external relay hosts provide. Size them accordingly. + + +This is a cutover, not a gradual migration. The restart that applies the change is also the restart that drops every connection the embedded relay was serving, so those peers reconnect straight onto the external relays. Bring the relay hosts up and confirm they answer before you edit `config.yaml`, because there is no period where both are available. + + ## Verify the Configuration ### Check Main Server Logs @@ -256,6 +387,8 @@ INFO combined/cmd/root.go: Relay: false (log level: ) Relay addresses: [rels://relay-us.example.com:443 rels://relay-eu.example.com:443] ``` +The address list is logged twice, from two different places in the server. That is normal. + ### Check Peer Status Connect a NetBird client and verify that both STUN and relay services are available: @@ -264,24 +397,31 @@ Connect a NetBird client and verify that both STUN and relay services are availa netbird status -d ``` -The output should list your external STUN and relay servers. All configured STUN servers will appear, but only one randomly chosen relay is used per client: +The output lists your external STUN and relay servers. Every configured STUN server appears. Relays work differently: the client dials them in parallel and keeps the first to answer, which is normally the nearest. That one becomes its **home relay**, so relays in several regions give each client the nearest one automatically. ``` Relays: [stun:relay-us.example.com:3478] is Available [stun:relay-eu.example.com:3478] is Available - [rels://relay-eu.example.com:443] is Available + [rels://relay-eu.example.com:443] is Available via ws ``` -You can also test failover by stopping one of the relay servers and checking the status again. The client will detect the unavailable server and use the remaining one: +You may see more than one `rels://` line. A client also connects to another peer's home relay when that peer picked a different one, so holding two relay connections at once is normal in a multi-region deployment. + +The suffix names the transport that won the race, and it is `ws` or `quic` depending on which answered first. Both are normal, the winner can differ between two peers on the same deployment, and neither is a sign of a problem. What 443/udp buys you is that the QUIC attempt can win or lose on merit rather than sitting until it times out. + +### Test Failover + +Stop the relay a peer is actually using, which is the one on its `rels://` line, otherwise nothing observable changes. Then check that peer again. + +Read the `rels://` line, not the `stun:` lines. It is the one that shows failover working: the stopped relay drops out of it and the surviving relay carries the traffic. ``` -Relays: - [stun:relay-us.example.com:3478] is Available - [stun:relay-eu.example.com:3478] is Unavailable, reason: stun request: context deadline exceeded - [rels://relay-us.example.com:443] is Available + [rels://relay-us.example.com:443] is Available via ws ``` +The `stun:` entries are not a dependable failover signal. Depending on circumstances they either report the stopped server as `Unavailable` with a reason, or sit at `Checking...` for every configured STUN server, including healthy ones, without clearing on their own or after a client restart. Both have been observed. Either way they return to `Available` once the stopped relay is back, so do not read `Checking...` as a second failure. + ### Test Relay Connectivity You can force all peer connections through relay to verify it works end-to-end. On a client, run: diff --git a/src/pages/selfhosted/maintenance/upgrade.mdx b/src/pages/selfhosted/maintenance/upgrade.mdx index c0234174..82cd3fbf 100644 --- a/src/pages/selfhosted/maintenance/upgrade.mdx +++ b/src/pages/selfhosted/maintenance/upgrade.mdx @@ -1,5 +1,7 @@ # Upgrade Your Self-Hosted NetBird Installation +This guide covers NetBird upgrades and [updating Traefik and CrowdSec](#upgrading-traefik-and-crowd-sec), including deployments created from marketplace images. Run the commands from the directory containing your `docker-compose.yml` (`/opt/netbird` on [Vultr](/selfhosted/marketplaces/vultr)). + ## Check for Updates The NetBird Dashboard displays an update indicator at the bottom of the left navigation menu when a new version is available: @@ -27,6 +29,8 @@ Example response: } ``` +The dashboard indicator and version API cover NetBird components. They do not report updates for Traefik, CrowdSec, or the optional Traefik CrowdSec bouncer plugin. Check those projects' releases separately, as described below. + ## Check Release Notes Before upgrading, review the release notes for any breaking changes or migration steps: @@ -44,7 +48,8 @@ To upgrade NetBird to the latest version: 1. Run the backup steps described in the [backup](/selfhosted/maintenance/backup) section. 2. Review the release notes (see above) for any breaking changes. -3. Pull the latest NetBird docker images: +3. Check the `image:` entries in `docker-compose.yml`. If you use explicit version tags, change them to the releases you want to deploy before pulling. `docker compose pull` downloads the configured tags; it does not change them. With `:latest`, it fetches the image currently published under that tag. +4. Pull the configured NetBird images: ```bash docker compose pull netbird-server dashboard ``` @@ -52,7 +57,7 @@ To upgrade NetBird to the latest version: ```bash docker compose pull proxy ``` -4. Restart the NetBird containers with the new images: +5. Restart the NetBird containers with the new images: ```bash docker compose up -d --force-recreate netbird-server dashboard ``` @@ -72,11 +77,160 @@ Compare it with the `management_current_version` field returned by `GET /api/ins
-For upgrades from older versions (pre-v0.26.0), see the [Legacy upgrade notes](#legacy-self-hosting-with-zitadel-idp) below. +For upgrades from older versions (pre-v0.26.0), see the [legacy upgrade notes](/selfhosted/selfhosted-guide#upgrade). +## Upgrading Traefik and CrowdSec + +Traefik and CrowdSec have their own release cycles. Upgrading NetBird does not update their version tags or apply changes from newer installation scripts to your existing Compose file. Use your existing configuration for upgrades; rerunning `getting-started.sh` is not an upgrade procedure. + +### Choosing versions + +The following dependency versions have been validated for self-hosted NetBird as of **October 9, 2026**: + +| Component | Validated version | +| --- | --- | +| Bundled Traefik | `traefik:v3.7.14` | +| CrowdSec | `crowdsecurity/crowdsec:v1.8.1` | +| Traefik CrowdSec bouncer plugin | `v1.7.1` | + +Installer defaults can differ from these versions. Check the image tags in your `docker-compose.yml`; the bouncer plugin is configured separately in the [dashboard AppSec guide](/selfhosted/maintenance/crowdsec-dashboard#update-docker-compose-yml). Before adopting a newer release, review its migration requirements and verify it with your NetBird configuration in a test deployment. + +- **Traefik:** check the [releases](https://github.com/traefik/traefik/releases), [support policy](https://doc.traefik.io/traefik/deprecation/releases/), and [v3 migration notes](https://doc.traefik.io/traefik/migrate/v3/). Configuration changes can be required within the same major version. +- **CrowdSec:** check the [releases](https://github.com/crowdsecurity/crowdsec/releases) and [Docker documentation](https://docs.crowdsec.net/u/getting_started/installation/docker/). +- **Dashboard AppSec:** also check the [Traefik bouncer plugin releases](https://github.com/maxlerebourg/crowdsec-bouncer-traefik-plugin/releases). The plugin version is set by `--experimental.plugins.bouncer.version` in Traefik's `command:` list. Changing the CrowdSec image does not update it. + +You can subscribe to release notifications on each GitHub repository using **Watch → Custom → Releases**. Check security advisories and upstream support status as well as the installer defaults when planning updates. + +Full version tags such as `traefik:v3.7.14` and `crowdsecurity/crowdsec:v1.8.1` select specific releases. A minor-version tag such as `traefik:v3.6` can fetch a newer patch image without a Compose edit. For controlled rollouts, use full version tags for all images, or image digests when you need immutable references, and update them deliberately after validation. + +### Before changing a version + +1. Record the configured images and the images currently in use: + + ```bash + docker compose config --images + docker compose images + docker compose exec traefik traefik version + ``` + + If CrowdSec is enabled, also run: + + ```bash + docker compose exec crowdsec cscli version + ``` + + Record the current plugin version if you use dashboard AppSec. For each image, save its digest for rollback; use an image ID from `docker compose images`: + + ```bash + docker image inspect --format '{{json .RepoDigests}}' + ``` + +2. [Back up the deployment](/selfhosted/maintenance/backup), including the Compose file, environment files, Traefik dynamic configuration, CrowdSec configuration and database, and certificate volumes. If you enabled dashboard AppSec, also back up the Compose project `.env` file containing its bouncer key and the `crowdsec/acquis.d/` configuration. +3. Review release notes for every version you are skipping, then test the candidate versions with the [checks below](#verify-the-upgrade). Keep SSH access available independently of the dashboard and proxy while testing blocks or restarting services. + +### Apply the update + +Change one component at a time so you can identify regressions. Edit that service's `image:` in `docker-compose.yml` to the version or digest you selected, preserving its existing environment, labels, networks, and volume mounts. If the image is supplied through a Compose variable, update that variable instead. + +For Traefik, also check [header handling](#traefik-header-handling) and the [Docker network setting](#traefik-docker-network) before recreating the container. + +Validate the configuration before applying it: + +```bash +docker compose config --quiet +``` + +For CrowdSec, pull and recreate only its service: + +```bash +docker compose pull crowdsec +docker compose up -d --no-deps crowdsec +docker compose ps crowdsec +docker compose logs --tail=100 crowdsec +``` + +Confirm CrowdSec is healthy and repeat the blocking checks below before continuing. AppSec-protected requests can be denied while CrowdSec is unavailable. + +For Traefik, pull and recreate its service after editing its image: + +```bash +docker compose pull traefik +docker compose up -d --no-deps traefik +docker compose logs --tail=100 traefik +``` + +If you change only the bouncer plugin version, apply the updated Traefik `command:` with `docker compose up -d --no-deps traefik`. A plain `docker compose restart` does not apply Compose configuration changes. + +These restarts briefly interrupt the affected services. Preserve the existing CrowdSec database, bouncer keys, and certificate volumes; do not use `docker compose down -v` during an upgrade. + +### Verify the upgrade + +A running container alone does not confirm that routing, certificate issuance, or blocking works. Check the features enabled in your deployment: + +| Area | What to verify | +| --- | --- | +| Dashboard and peers | Sign in, load peers and policies, connect an existing peer, enroll a test peer, and verify peer connectivity and relay fallback. | +| Reverse proxy | Reach an existing service and create a test service. Exercise the protocols, authentication methods, and access restrictions you use, including HTTP, WebSockets, TCP/UDP/TLS services, and Traefik TLS passthrough where configured. | +| Certificates | Check trusted HTTPS on the dashboard and proxied domains without disabling certificate verification. Use a new test hostname to verify certificate issuance, and check ACME logs for failures. An existing valid certificate alone does not verify issuance or renewal; exercise renewal in the test deployment and monitor it after rollout. See [Certificate troubleshooting](/selfhosted/troubleshooting/certificates). | +| Client IPs | Confirm proxy access logs contain the original client IP, so IP restrictions and CrowdSec decisions apply to the correct address. | +| CrowdSec IP reputation | Follow [Testing the integration](/selfhosted/maintenance/crowdsec#testing-the-integration): confirm the bouncer syncs, a temporary decision blocks in enforce mode, observe mode allows and logs the verdict, and access returns after removing the decision. | +| Dashboard AppSec | If configured, repeat [Verify protection](/selfhosted/maintenance/crowdsec-dashboard#verify-protection) and [the middleware checks](/selfhosted/maintenance/crowdsec-dashboard#verifying-the-middleware-stays-attached). Normal dashboard and OIDC requests should succeed; `/.env` and `/api/.env` probes should return `403`, with AppSec metrics confirming inspection. Test dashboard IP blocking separately. | +| Persistence | Restart the updated services and repeat the checks. Confirm certificates, CrowdSec enrollment, bouncer authentication, and AppSec configuration survive the restart. | + +Record the exact image versions or digests, plugin version, and test results with your deployment configuration so the next upgrade has a known working starting point. + +### Roll back a dependency update + +Restore the previous image digest or full version tag and any configuration changed for that component, including the bouncer plugin version. Recreate the affected service with `docker compose up -d --no-deps ` and repeat the verification checks. Restoring a moving tag such as `v3.6` or `latest` may fetch the upgraded image again; use the recorded digest to recover the exact previous image. + +When rolling Traefik back to a release older than `v3.7.12`, also remove the `aliasHeadersStrategy` flags added in [Traefik header handling](#traefik-header-handling). Those releases do not recognize the option and fail to start with `field not found, node: aliasHeadersStrategy`. + + +Traefik v3.0.0 through v3.7.11 are affected by header-alias spoofing ([GHSA-rf44-j88r-hh8c](https://github.com/traefik/traefik/security/advisories/GHSA-rf44-j88r-hh8c)). Removing `aliasHeadersStrategy` so those releases can start restores that vulnerable handling: a client can send an alias of a header Traefik wrote, and a backend that treats those names as the same variable can read the client's value instead. Do not expose an affected rollback version to untrusted traffic. Return to `v3.7.12` or newer, with `aliasHeadersStrategy` set to `delete`, once service is restored. + + +If the release migrated stored data or configuration in a way the older version cannot read, stop the affected service and [restore its pre-upgrade backup](/selfhosted/maintenance/backup) before starting the older image. Changing an image tag does not undo a database migration. + ## Migration Notes +### Traefik header handling + +Traefik v3.7.12 introduced `aliasHeadersStrategy` and warns when it is unset. When upgrading to the validated `v3.7.14` version, add these settings to the existing `services.traefik.command` list in `docker-compose.yml`: + +```yaml + - '--entrypoints.web.http.aliasHeadersStrategy=delete' + - '--entrypoints.websecure.http.aliasHeadersStrategy=delete' +``` + +These settings apply to HTTP traffic on both entry points, whether or not CrowdSec is enabled. Replace any existing `underscoreHeadersStrategy` flags with these settings. The `delete` strategy helps prevent header spoofing by removing request headers whose names contain characters other than letters, digits, and hyphens. It also removes legitimate headers such as `X_Request_Id`, so check custom integrations before enabling it. See [Traefik's header handling documentation](https://doc.traefik.io/traefik/reference/install-configuration/entrypoints/#aliasheadersstrategy). + +### Traefik Docker network + +Some installer-generated Compose files set `--providers.docker.network=netbird`, while Compose creates a network named `_netbird`. Traefik then warns and falls back to another network. This usually works with one shared network but can select the wrong one when containers have several. + +Check the networks attached to the bundled Traefik container: + +```bash +docker inspect netbird-traefik --format '{{range $name, $_ := .NetworkSettings.Networks}}{{println $name}}{{end}}' +``` + +Replace the existing `--providers.docker.network` value with the shared network's actual name. For example, if the command prints `netbird_netbird`: + +```yaml + - '--providers.docker.network=netbird_netbird' +``` + +The name depends on the Compose project. An explicitly named network, such as `netbird` in the enterprise installer, does not receive a project prefix. Keep that name if it already matches. See [Docker's network naming documentation](https://docs.docker.com/reference/compose-file/networks/#name). + +After changing either Traefik setting, validate the Compose file and recreate Traefik: + +```bash +docker compose config --quiet +docker compose up -d --no-deps traefik +docker compose logs --tail=100 traefik +``` + ### Set `reverseProxy.trustedPeers` on older deployments Deployments created with the Quickstart script (or a marketplace image based on it, such as [Vultr](/selfhosted/marketplaces/vultr)) before **September 2026** are missing the `reverseProxy.trustedPeers` setting in the generated `config.yaml`. Without it, server versions up to v0.79 record your reverse proxy's address instead of the real client IP for each peer connection. Versions after v0.79 ([netbird#7589](https://github.com/netbirdio/netbird/pull/7589)) instead fall back to accepting forwarded client-IP headers from **any** source and log a spoofing warning at startup. diff --git a/src/pages/selfhosted/marketplaces/vultr.mdx b/src/pages/selfhosted/marketplaces/vultr.mdx index d64b092e..eb5440a7 100644 --- a/src/pages/selfhosted/marketplaces/vultr.mdx +++ b/src/pages/selfhosted/marketplaces/vultr.mdx @@ -72,15 +72,16 @@ The Vultr instance ships with a root password. Before relying on the deployment, ## Maintaining your deployment -The NetBird stack lives in **`/opt/netbird`** on the instance, including the `docker-compose.yml` and environment files. If the dashboard shows an **Update available** banner, SSH in and upgrade by pulling the latest images and restarting the containers: +The NetBird stack lives in **`/opt/netbird`** on the instance, including the `docker-compose.yml` and environment files. SSH in and run the [Upgrade guide](/selfhosted/maintenance/upgrade) from that directory, starting with a backup and release-note review: ```bash cd /opt/netbird -docker compose pull -docker compose up -d +docker compose config --images ``` -Refresh the dashboard and the banner should clear. See the [Upgrade guide](/selfhosted/maintenance/upgrade) for the full procedure, including migration notes and rollback steps. +The dashboard's **Update available** indicator covers NetBird components. Traefik, CrowdSec, and the optional dashboard AppSec plugin need separate update checks. Follow [Upgrading Traefik and CrowdSec](/selfhosted/maintenance/upgrade#upgrading-traefik-and-crowd-sec) for version selection, targeted updates, verification, and rollback. + +An updated marketplace image does not change an existing instance's Compose file or image tags. Maintain the running deployment with these steps rather than redeploying it from a newer image. ## Next steps diff --git a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx index 788fd1d7..ece375b2 100644 --- a/src/pages/selfhosted/migration/enable-reverse-proxy.mdx +++ b/src/pages/selfhosted/migration/enable-reverse-proxy.mdx @@ -42,6 +42,7 @@ Before starting, ensure you have: - **A domain for the proxy** - preferably a dedicated domain such as `proxy.example.com`. It can share the NetBird server's domain, but that base hostname remains reserved for the dashboard and management server. Proxied services must use subdomains (e.g., `myapp.netbird.example.com`) - **Wildcard DNS capability** - ability to create a wildcard record such as `*.proxy.example.com` pointing to your server - **Port 443 accessible** - the proxy needs this for ACME TLS-ALPN-01 challenges (certificate provisioning) +- **The server can reach its own public IP** - the proxy runs its own NetBird client, and that client connects to Signal and the relay at your NetBird domain's public address, even when the proxy reaches the management server over the Docker network. If the firewall in front of the server does not route a connection from the server back to its own public IP (hairpin NAT), every proxied service returns `504 Gateway Timeout` and the proxy logs `failed connecting to Signal Service`. Resolving the NetBird domain to an internal address instead (split DNS) keeps the services working, but every connection between the proxy and your peers then goes through the relay This guide covers both the **combined container** setup (`netbirdio/netbird-server`, the default for new deployments) and the **multi-container** setup (separate `management`, `signal`, and `relay` images). Where commands or configuration differ between the two setups, both variants are shown. @@ -270,6 +271,32 @@ docker compose up -d proxy You only need port mappings for TCP and UDP mode services. HTTP and TLS mode services are routed through port 443 via Traefik and do not require additional port entries. +Changing `ports` recreates the proxy container, which briefly interrupts every service it serves, not only the new one. To add L4 services without that step, run the proxy on the host network instead. + +#### Use host networking instead + +With host networking, the proxy binds each L4 listen port on the server itself, so a new TCP or UDP service is reachable as soon as you create it, with no compose change and no restart. The server's firewall then decides which ports are reachable, so open each listen port there as you add its service. The port mappings above remain the default: host networking gives up Docker's port isolation, and here the proxy shares the host's network with your NetBird server and Traefik. + +Two settings must change with it, because the proxy is no longer on the `netbird` Docker network: + +- **The management address.** `http://netbird-server:80` (or `http://management:33073` in a multi-container setup) is a Docker network name, which a container on the host network cannot resolve; the proxy logs `name resolver error: produced zero addresses` and never connects. In `proxy.env`, set `NB_PROXY_MANAGEMENT_ADDRESS` to your public NetBird URL, for example `https://netbird.example.com:443`. This connection goes through Traefik, so Traefik must route `/management.ProxyService/` and allow long-lived gRPC streams. Deployments from the current installer already do; for older ones, apply steps 1 and 3 of [Connecting through Traefik instead of Docker network](#connecting-through-traefik-instead-of-docker-network). +- **How Traefik reaches the proxy.** For a container on the host network, Traefik dials `127.0.0.1:8443`, which inside Traefik's own container is Traefik itself, so every HTTP service fails with `connection refused`. Mapping `host.docker.internal` to the host in Traefik's service makes it reach the proxy on the host instead. + +In `docker-compose.yml`, remove the `networks` and `ports` entries from the `proxy` service, then add: + +```yaml +proxy: + # ...existing configuration, without networks: and ports:... + network_mode: host + +traefik: + # ...existing configuration... + extra_hosts: + - host.docker.internal:host-gateway +``` + +Apply both with `docker compose up -d proxy traefik`. The proxy's main port stays `8443`, which an unprivileged process can bind on the host. An L4 listen port below `1024` needs the host's unprivileged port floor lowered first; see [Bring Your Own Proxy](/manage/reverse-proxy/bring-your-own-proxy#expose-tcp-and-udp-services). + ### Step 4: Set up DNS records Create DNS records pointing to the server running your NetBird stack: one for the base proxy domain and one wildcard for service subdomains. @@ -331,7 +358,7 @@ Add the following service to your `docker-compose.yml`: ```yaml crowdsec: - image: crowdsecurity/crowdsec:v1.7.7 + image: crowdsecurity/crowdsec:v1.8.1 container_name: netbird-crowdsec restart: unless-stopped networks: [netbird] @@ -731,6 +758,7 @@ The proxy is configured through environment variables (each one maps to an equiv | `NB_PROXY_SUPPORTS_CUSTOM_PORTS` | No | Whether the proxy can bind arbitrary ports for UDP/TCP passthrough. | `true` | | `NB_PROXY_REQUIRE_SUBDOMAIN` | No | Require a subdomain label in front of the cluster domain. | `false` | | `NB_PROXY_PRIVATE` | No | Serve private services with NetBird-Only authentication, reachable exclusively over the WireGuard tunnel (also enables per-account inbound listeners). Required for the **Proxy Cluster** target type and **NetBird-Only Access**. The `netbirdio/reverse-proxy` image runs in embedded mode by default, so setting this on a standard self-hosted deployment is supported: the cluster then reports the `Private` capability and the dashboard **Clusters** page shows a **Private** badge. | `false` | +| `NB_PROXY_DIRECT_UPSTREAM_BLOCK_PRIVATE` | No | Refuse **Direct Upstream** dials to addresses that are not publicly routable (loopback, RFC 1918, CGNAT, link-local including cloud metadata services, ULA, multicast, and reserved ranges). Hostnames are checked after DNS resolution. Refused requests return `502 Destination Not Allowed`. Enable this on proxies that serve untrusted accounts; leave it off if services need to reach LAN or localhost backends. Tunneled traffic through peers is unaffected. | `false` | | `NB_PROXY_MAX_DIAL_TIMEOUT` | No | Cap the per-service backend dial timeout (`0` = no cap), e.g. `10s`. | `0` | | `NB_PROXY_MAX_SESSION_IDLE_TIMEOUT` | No | Cap the per-service session idle timeout (`0` = no cap), e.g. `5m`. | `0` | diff --git a/src/pages/selfhosted/migration/external-to-embedded-idp.mdx b/src/pages/selfhosted/migration/external-to-embedded-idp.mdx index f8e223c3..e74b9206 100644 --- a/src/pages/selfhosted/migration/external-to-embedded-idp.mdx +++ b/src/pages/selfhosted/migration/external-to-embedded-idp.mdx @@ -149,6 +149,22 @@ Using Zitadel as an example, the JSON should have the following values: - "clientID" and "clientSecret": are the values you copy when creating the OAuth app
+ +For Google, use the `google` connector type, which needs no issuer. It uses the same Google account ID as the standalone Google setup, so existing users keep their NetBird accounts: +```json +{ + "type": "google", + "name": "Google", + "id": "google", + "config": { + "clientID": "my-client-id", + "clientSecret": "my-client-secret" + } +} +``` +In the Google Cloud Console, add `https:///oauth2/callback` to the OAuth client's **Authorized redirect URIs**. + + 2. Encode and store it in the `NETBIRD_IDP_SEED_INFO` environment variable: diff --git a/src/pages/selfhosted/migration/mysql-to-postgresql.mdx b/src/pages/selfhosted/migration/mysql-to-postgresql.mdx new file mode 100644 index 00000000..750b21b4 --- /dev/null +++ b/src/pages/selfhosted/migration/mysql-to-postgresql.mdx @@ -0,0 +1,164 @@ +import {Note} from "@/components/mdx" + +export const description = 'Move a self-hosted NetBird deployment from MySQL to PostgreSQL before MySQL support is removed.' + +# Migrate from MySQL to PostgreSQL + +MySQL support for the Management store is deprecated and will be removed in an upcoming release. This guide moves an existing MySQL deployment to PostgreSQL with the `netbird-mysql-migrate` tool. + +The tool copies the following into PostgreSQL: +- the Management store, from MySQL +- the activity events (`events.db`) and embedded IdP (`idp.db`) stores, from their SQLite files + +The tool only reads your MySQL database and doesn't change it. + +## Before You Begin + +- Upgrade the management server to `v0.81.0` or later and start it once on MySQL, so the database schema is current. That's the `netbird-server` service, or `management` on the older multi-container setup, not the NetBird client. Use the tool from the same release. +- Prepare an empty PostgreSQL database. To run one in Docker, see [Set Up PostgreSQL](/selfhosted/maintenance/scaling/migrate-sqlite-to-postgresql#set-up-postgre-sql). The examples in this guide connect with `sslmode=require`; use `verify-full` to also verify the server certificate. Use `sslmode=disable` only when PostgreSQL runs on the same host or Docker network as the management server. +- Run the tool on a Linux machine that can reach both MySQL and PostgreSQL. The tool is Linux-only, built for `amd64`, `arm64`, and `arm`. + +## Step 1: Get the Migration Tool + +```bash +VERSION=0.81.0 +curl -L -o netbird-mysql-migrate.tar.gz \ + https://github.com/netbirdio/netbird/releases/download/v${VERSION}/netbird-mysql-migrate_${VERSION}_linux_amd64.tar.gz +tar xzf netbird-mysql-migrate.tar.gz +chmod +x netbird-mysql-migrate +``` + +On `arm64` or `arm`, replace `linux_amd64` with `linux_arm64` or `linux_arm`. + +## Step 2: Stop the Management Server and Back Up + +Stop the management server and copy its data directory, which holds `events.db` and `idp.db`: + +```bash +docker compose stop netbird-server +mkdir backup +docker compose cp -a netbird-server:/var/lib/netbird/. backup/ +chmod 700 backup +``` + +On the older multi-container setup, use the `management` service instead of `netbird-server`. + +Back up MySQL as well: + +```bash +mysqldump --no-tablespaces -h -u -p > backup/netbird-mysql.sql +``` + +The backup holds password hashes from `idp.db` and a full database dump, so `chmod 700` keeps it readable only by you. `--no-tablespaces` avoids a `PROCESS` privilege error when you dump as the NetBird database user. + +## Step 3: Run the Migration + +```bash +./netbird-mysql-migrate \ + --mysql-dsn ':@tcp(:3306)/' \ + --postgres-dsn 'host= port=5432 user= password= dbname= sslmode=require' \ + --events-db backup/events.db \ + --auth-db backup/idp.db +``` + +When it finishes, you see: + +``` +management: 1520 rows from 39 tables +activity: 8230 rows from 2 tables +auth: 41 rows from 12 tables +Done. Point the store, activity and auth store settings at Postgres before starting the server. +``` + + +- All stores go into the database given by `--postgres-dsn`. To keep the activity or auth store in a separate database, add `--events-postgres-dsn` or `--auth-postgres-dsn`. +- A missing `events.db` or `idp.db` is skipped. For example, deployments on an external IdP have no `idp.db`. +- MySQL timestamps are read as UTC. If your management server runs in another time zone, for example with `TZ` set on the container, add `--mysql-timezone`, such as `--mysql-timezone Europe/Berlin`. +- The tool stops if the PostgreSQL database already holds NetBird data. + + +## Step 4: Point the Management Server at PostgreSQL + +Change only the store settings. Keep the encryption key (`server.store.encryptionKey` in `config.yaml`, `DataStoreEncryptionKey` in `management.json`) as it is: the tool copies encrypted values as they are, so the management server needs the same key to read them. + +### Combined setup (config.yaml) + +Set all three stores to PostgreSQL in `config.yaml`, using the DSNs you migrated into: + +```yaml +server: + store: + engine: "postgres" + dsn: "host= port=5432 user= password= dbname= sslmode=require" + activityStore: + engine: "postgres" + dsn: "host= port=5432 user= password= dbname= sslmode=require" + authStore: + engine: "postgres" + dsn: "host= port=5432 user= password= dbname= sslmode=require" +``` + +Remove any `NETBIRD_STORE_ENGINE_MYSQL_DSN` or `NB_STORE_ENGINE_MYSQL_DSN` variable from `docker-compose.yml`. + +### Older multi-container setup (management.json) + +In `management.json`, set the store engine and, if the file has an `EmbeddedIdP` section, its storage: + +```json +"StoreConfig": { + "Engine": "postgres" +}, +"EmbeddedIdP": { + "Storage": { + "Type": "postgres", + "Config": { + "DSN": "host= port=5432 user= password= dbname= sslmode=require" + } + } +} +``` + +Then, on the `management` service in `docker-compose.yml`, replace `NETBIRD_STORE_ENGINE_MYSQL_DSN` with: + +```yaml +environment: + - NETBIRD_STORE_ENGINE_POSTGRES_DSN=host= port=5432 user= password= dbname= sslmode=require + - NB_ACTIVITY_EVENT_STORE_ENGINE=postgres + - NB_ACTIVITY_EVENT_POSTGRES_DSN=host= port=5432 user= password= dbname= sslmode=require +``` + +## Step 5: Start and Verify + +```bash +docker compose up -d +``` + +Log in to the dashboard and confirm that your peers, users, and activity events are there. Then delete the `backup` directory. + +## Roll Back + +The tool doesn't change MySQL, so to roll back, revert the store settings from Step 4 and restart the management server. Changes made while running on PostgreSQL aren't carried back to MySQL. + +A failed run leaves no data in PostgreSQL, so you can fix the problem and run the tool again. + +## Troubleshooting + +### Tool exits with "target already holds data" + +The PostgreSQL database for the store named in the error already holds NetBird data. If an earlier run of the tool finished, your data is already there, so continue with [Step 4](#step-4-point-the-management-server-at-postgre-sql). Otherwise, point the tool at an empty database. + +### Tool exits with "tls error: server refused TLS connection" + +The DSN requires TLS (`sslmode=require` or `verify-full`), but PostgreSQL doesn't accept TLS connections. Enable TLS on PostgreSQL. If PostgreSQL runs on the same host or Docker network as the management server, you can use `sslmode=disable` instead, in the tool's DSNs and in the Step 4 settings. + +### Tool exits with "source lacks columns" + +Your data was last written by an older release than the tool. Start the management server once against MySQL on the same release as the tool, then repeat [Step 2](#step-2-stop-the-management-server-and-back-up) so your backup holds the updated `events.db` and `idp.db`, and run the tool again. + +### Tool exits with "no NetBird tables found in the source" + +The error names the store it came from. For `management`, check the database name in `--mysql-dsn`. For `activity` or `auth`, check that `--events-db` or `--auth-db` points to the `events.db` or `idp.db` in your backup. + +### Management server doesn't start: "activityStore.dsn is required when activityStore.engine is postgres" + +The `activityStore` block in `config.yaml` sets `engine: "postgres"` without a `dsn`. Add the DSN as shown in [Step 4](#step-4-point-the-management-server-at-postgre-sql). You can see this error with `docker compose logs netbird-server`. diff --git a/src/pages/selfhosted/selfhosted-guide.mdx b/src/pages/selfhosted/selfhosted-guide.mdx index ffca268a..4486418a 100644 --- a/src/pages/selfhosted/selfhosted-guide.mdx +++ b/src/pages/selfhosted/selfhosted-guide.mdx @@ -139,7 +139,7 @@ NetBird supports generic OpenID (OIDC) protocol allowing integration with any ID NetBird's management service integrates with some of the most popular IDP APIs, allowing the service to cache and display user names and email addresses without storing sensitive data. -Pick the one that suits your needs, follow the **Standalone Setup (Advanced)** section in each guide, and continue with this guide: +Pick the one that suits your needs, follow the **Standalone Setup (Advanced)** section in each guide (except Google, see the note below), and continue with this guide: **Self-hosted options** - [Zitadel](/selfhosted/identity-providers/zitadel) - Previously used in the quickstart script @@ -149,13 +149,15 @@ Pick the one that suits your needs, follow the **Standalone Setup (Advanced)** s **Managed options** - [Microsoft Entra ID](/selfhosted/identity-providers/managed/microsoft-entra-id) - Azure AD / Microsoft 365 -- [Google Workspace](/selfhosted/identity-providers/managed/google-workspace) - Google accounts +- [Google Workspace](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended) - Google accounts. Not recommended as a standalone IdP; use the Management Setup (Recommended) instead - [Okta](/selfhosted/identity-providers/managed/okta) - Enterprise SSO - [Auth0](/selfhosted/identity-providers/managed/auth0) - Flexible auth platform - [JumpCloud](/selfhosted/identity-providers/managed/jumpcloud) - Cloud directory Each provider page includes both "Management Setup (Recommended)" (for use with the embedded IdP) and "Standalone Setup (Advanced)" sections. For this guide, follow the **Standalone Setup (Advanced)** section. + +Google is the exception: its standalone setup is not recommended. Deploy with the [Quickstart guide](/selfhosted/selfhosted-quickstart), which uses the embedded IdP, and add Google with the [Management Setup (Recommended)](/selfhosted/identity-providers/managed/google-workspace#management-setup-recommended) section instead of continuing with this guide. ### Step 4: Disable single account mode (optional) diff --git a/src/pages/selfhosted/selfhosted-quickstart.mdx b/src/pages/selfhosted/selfhosted-quickstart.mdx index 5d50dfe4..a3b34215 100644 --- a/src/pages/selfhosted/selfhosted-quickstart.mdx +++ b/src/pages/selfhosted/selfhosted-quickstart.mdx @@ -126,6 +126,51 @@ The `/setup` page is only accessible when no users exist. After creating the fir For automated deployments, you can create the first owner user through the setup API and optionally receive a Personal Access Token for bootstrapping resources. See [Automated setup with a Personal Access Token](/selfhosted/automated-setup). +## Unattended installation + +When you install from cloud-init, a CI pipeline, or a Terraform provisioner, nobody is there to answer the script's questions. Answer them in advance with environment variables instead, and the script installs without prompting. + +For each question, the script uses the matching environment variable if it is set and not empty. An empty variable counts as unset: the script asks on the terminal, or, when there is no terminal, uses the default, or stops if the answer is required. So with no terminal at all, the script runs unattended on its own. Set `NETBIRD_NON_INTERACTIVE=true` if your automation attaches a terminal that nobody is watching. + +This installs the setup from the steps above (built-in Traefik with a Let's Encrypt certificate) with no prompts: + +```bash +export NETBIRD_DOMAIN=netbird.my-domain.com +export NETBIRD_LETSENCRYPT_EMAIL=admin@my-domain.com +export NETBIRD_NON_INTERACTIVE=true +curl -fsSL https://github.com/netbirdio/netbird/releases/latest/download/getting-started.sh | bash +``` + + +The variables must be set for `bash`, not `curl`. Export them first, as above, or set them after the pipe: `curl -fsSL | NETBIRD_DOMAIN=netbird.my-domain.com bash`. If you set them before `curl`, only `curl` sees them, and the script stops with `NETBIRD_DOMAIN is required for a non-interactive install.` + + +| Variable | Default | Used with | What it sets | +|----------|---------|-----------|--------------| +| `NETBIRD_DOMAIN` | Required | Every option | The domain of your NetBird server, for example `netbird.my-domain.com` | +| `NETBIRD_LETSENCRYPT_EMAIL` | Required | Option `0` | The email address for Let's Encrypt certificate notifications | +| `NETBIRD_REVERSE_PROXY_TYPE` | `0` | Every option | The reverse proxy option, `0` to `5`, from [Reverse Proxy Selection](#reverse-proxy-selection) | +| `NETBIRD_ENABLE_PROXY` | `false` | Option `0` | Whether to enable the [NetBird Proxy service](#enable-the-net-bird-proxy-service) | +| `NETBIRD_ENABLE_CROWDSEC` | `false` | Option `0`, proxy enabled | Whether to add [CrowdSec IP reputation](#crowd-sec-ip-reputation-optional) | +| `NETBIRD_TRAEFIK_EXTERNAL_NETWORK` | Empty | Option `1` | The Docker network of your existing Traefik | +| `NETBIRD_TRAEFIK_ENTRYPOINT` | `websecure` | Option `1` | The entrypoint of your existing Traefik | +| `NETBIRD_TRAEFIK_CERTRESOLVER` | Empty | Option `1` | The certificate resolver of your existing Traefik | +| `NETBIRD_BIND_LOCALHOST_ONLY` | `true` | Options `2` to `5` | Whether NetBird's HTTP ports listen on `127.0.0.1` only | +| `NETBIRD_EXTERNAL_PROXY_NETWORK` | Empty | Options `2` to `4` | The Docker network your reverse proxy runs on | +| `NETBIRD_TRUSTED_PEERS` | The built-in Traefik's address for option `0`, empty otherwise | Every option | The address your reverse proxy reaches the NetBird server from, for example `172.20.0.5/32` | +| `NETBIRD_NON_INTERACTIVE` | Not set | Every option | `true` never prompts, even when a terminal is available | + +The comment block at the top of [`getting-started.sh`](https://github.com/netbirdio/netbird/blob/main/infrastructure_files/getting-started.sh) is the authoritative list. It also has `NETBIRD_AGENT_NETWORK`, which installs the [Agent Network](/agent-network/how-it-works) preset instead of this setup. The variables need NetBird v0.77.1 or later, and `NETBIRD_TRUSTED_PEERS` needs v0.79.0 or later. + +Before you automate the install, know that: + +- **An invalid domain stops the script.** Unattended, a domain with a scheme (`https://`) or a port, a bare IP address, or the placeholder `netbird.example.com` ends the run instead of asking for another. A domain that does not resolve yet only prints a warning, but certificate issuance and client connections fail until it does, so create the DNS record first. +- **Set `NETBIRD_TRUSTED_PEERS` if you use your own reverse proxy (options `1` to `5`).** Without it, the NetBird server trusts forwarded client IP headers from any source, not only from your proxy. The script only prints a warning, which is easy to miss in an unattended run. +- **The script does not wait for your reverse proxy.** Interactively, options `2`, `4` and `5` pause until you confirm that your proxy is configured. Unattended, the script starts the containers straight away, and you configure the proxy afterwards. +- **A rerun never overwrites an installation.** If the directory already has a generated `config.yaml`, the script stops and lists the files to remove. Run it in a fresh directory. + +Like the interactive install, an unattended install creates no users. Create the first user in the browser, as described in [Initial setup](#initial-setup-onboarding), or from your automation with the [setup API](/selfhosted/automated-setup). + ## Add More Users NetBird includes built-in local user management powered by an embedded Dex server, allowing you to create and manage users directly from the Dashboard without requiring an external identity provider. You can also add external identity providers for SSO authentication alongside local users. diff --git a/src/pages/selfhosted/troubleshooting.mdx b/src/pages/selfhosted/troubleshooting.mdx index 102b4cdd..9b999102 100644 --- a/src/pages/selfhosted/troubleshooting.mdx +++ b/src/pages/selfhosted/troubleshooting.mdx @@ -41,11 +41,12 @@ This page helps with issues when self-hosting NetBird. Collect the diagnostics b @@ -82,7 +83,7 @@ This page helps with issues when self-hosting NetBird. Collect the diagnostics b title: "Connectivity", href: "/selfhosted/troubleshooting/connectivity", icon: "firewall", - description: "Testing the TURN server, peer-to-peer failures, and an unreachable Management service.", + description: "Checking the relay, peer-to-peer failures, and an unreachable Management service.", }, { title: "Database", diff --git a/src/pages/selfhosted/troubleshooting/certificates.mdx b/src/pages/selfhosted/troubleshooting/certificates.mdx index 6d7bc70e..e061c1ab 100644 --- a/src/pages/selfhosted/troubleshooting/certificates.mdx +++ b/src/pages/selfhosted/troubleshooting/certificates.mdx @@ -25,7 +25,7 @@ Deployments created before NetBird switched to Traefik may still run Caddy, whic **Likely causes and fixes** (most common first): -1. **Let's Encrypt is still enabled, so two components fight over TLS.** Set `NETBIRD_DISABLE_LETSENCRYPT=true` so NetBird stops managing certificates and leaves termination to your proxy. +1. **NetBird is also terminating TLS, so two components fight over it.** Installs from `getting-started.sh` with options 1 to 5 don't do this: netbird-server serves plain HTTP behind your proxy. If you wrote `config.yaml` yourself, leave `server.tls.certFile` empty and `server.tls.letsencrypt.enabled` set to `false`. On older deployments installed with `configure.sh`, set `NETBIRD_DISABLE_LETSENCRYPT=true` in `setup.env`. 2. **Forwarded headers are missing.** Set `X-Forwarded-For` and `X-Forwarded-Proto` on the proxy so NetBird sees the original scheme and client. 3. **gRPC fails without HTTP/2.** The Management gRPC endpoints need HTTP/2; enable it on the proxy. diff --git a/src/pages/selfhosted/troubleshooting/connectivity.mdx b/src/pages/selfhosted/troubleshooting/connectivity.mdx index d471b3ae..00c56d0f 100644 --- a/src/pages/selfhosted/troubleshooting/connectivity.mdx +++ b/src/pages/selfhosted/troubleshooting/connectivity.mdx @@ -1,15 +1,29 @@ export const description = - "Self-hosted NetBird connectivity troubleshooting: testing the TURN server, peer-to-peer connection failures, and an unreachable Management service." + "Self-hosted NetBird connectivity troubleshooting: checking the relay, peer-to-peer connection failures, and an unreachable Management service." # Connectivity issues Peer connectivity and relay problems on a self-hosted deployment. For other areas, start from [Troubleshooting](/selfhosted/troubleshooting). +## Debugging relay connections + +When peers can't establish a direct connection, they fall back to the NetBird relay. In the combined setup, netbird-server runs the relay itself and serves it at `/relay` on TCP/443 through your reverse proxy. It also runs STUN on UDP/3478. There is no TURN server. + +To check the relay, run `netbird status -d` on a client and read the `Relays:` list. A working setup lists your STUN server and relay as available: + +```text +Relays: + [stun:netbird.example.com:3478] is Available + [rels://netbird.example.com:443] is Available via ws +``` + +If it reads `Unavailable, reason: ...`, the reason names the failure. The most common cause is a reverse proxy that doesn't route `/relay` to netbird-server or doesn't allow the WebSocket upgrade. Compare your proxy with the routes in [External Reverse Proxy Setup](/selfhosted/external-reverse-proxy). + ## Debugging TURN connections -When peers can't establish a direct connection, they fall back to the TURN (relay) server. If the connection still fails with relay, confirm your TURN configuration is correct and reachable. +This section applies to older multi-container deployments that run Coturn, configured in the `TURNConfig` section of `management.json`. The combined setup has no TURN server; use [Debugging relay connections](#debugging-relay-connections) instead. -To test it, open the [Trickle ICE test tool](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice). In the **ICE servers** box, remove the default server and add your TURN server. Replace netbird.DOMAIN.com and PASSWORD with your STUN/TURN details from your configuration (`config.yaml` for the combined setup, or the `TURNConfig` section in `management.json` for older multi-container setups), then click Add server. +To test it, open the [Trickle ICE test tool](https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice). In the **ICE servers** box, remove the default server and add your TURN server. Replace netbird.DOMAIN.com and PASSWORD with your STUN/TURN details from the `TURNConfig` section in `management.json`, then click Add server.

The Trickle ICE test tool with a NetBird STUN/TURN server added to the ICE servers list @@ -33,7 +47,7 @@ The candidate types are `host` (local address), `srflx` (STUN reflexive address) **Likely causes and fixes** (most common first): -1. **STUN/TURN is unreachable.** Confirm UDP/3478 (STUN/TURN) is open, then verify the relay path with the [TURN test above](#debugging-turn-connections). +1. **STUN or the relay is unreachable.** Confirm UDP/3478 (STUN) and TCP/443 are open, then check the relay with [Debugging relay connections](#debugging-relay-connections). 2. **A host firewall is dropping traffic** on one or both peers. Check the local firewall rules on each peer. 3. **The peer itself is unhealthy.** Run `netbird status -d` on both peers and check the connection type and last handshake. @@ -47,6 +61,6 @@ The candidate types are `host` (local address), `srflx` (STUN reflexive address) 1. **TCP/443 is blocked** between the client and the server. Confirm the port is open end to end. 2. **DNS doesn't resolve your domain** to the server's public IP. Check resolution from the client. -3. **Management is down or erroring.** Review `docker compose logs management`, and test the endpoint directly: `curl -v https://YOUR_DOMAIN/api/health`. +3. **Management is down or erroring.** Review `docker compose logs netbird-server` (`management` on installs from before v0.65.0), and test the endpoint through your proxy: `curl -s https://YOUR_DOMAIN/api/instance`. -**Confirm**: `curl https://YOUR_DOMAIN/api/health` returns a healthy response, and `netbird status` shows `Management: Connected`. +**Confirm**: `curl -s https://YOUR_DOMAIN/api/instance` returns JSON with a `setup_required` field, and `netbird status` shows `Management: Connected`. diff --git a/src/pages/selfhosted/troubleshooting/dashboard.mdx b/src/pages/selfhosted/troubleshooting/dashboard.mdx index 742cc144..14c01834 100644 --- a/src/pages/selfhosted/troubleshooting/dashboard.mdx +++ b/src/pages/selfhosted/troubleshooting/dashboard.mdx @@ -11,8 +11,8 @@ Problems with the self-hosted dashboard. For other areas, start from [Troublesho **Likely causes and fixes** (most common first): -1. **The dashboard can't reach the Management API.** Open the browser console (F12 → Console) and look for failed requests. Confirm the dashboard's configured API URL is correct and reachable from the browser. -2. **CORS (Cross-Origin Resource Sharing) is blocking the API behind a custom reverse proxy.** Serve the API on the same origin as the dashboard, or set the correct CORS headers on your proxy. +1. **The dashboard can't reach the Management API.** Open the browser console (F12 → Console) and look for failed requests. Confirm `NETBIRD_MGMT_API_ENDPOINT` in `dashboard.env` is your public NetBird URL and is reachable from the browser. +2. **CORS (Cross-Origin Resource Sharing) errors behind a custom reverse proxy.** netbird-server already sends CORS headers, so don't add them on the proxy: duplicate `Access-Control-Allow-Origin` headers make browsers reject the response. CORS errors can also mean the proxy doesn't route `/api` and `/oauth2` to netbird-server, so the browser gets a response from something else. 3. **A stale cached bundle.** Hard-reload the page or clear the browser cache. **Confirm**: Reload the dashboard. The login or peers view renders, and the console shows no failed API requests. @@ -25,6 +25,6 @@ Problems with the self-hosted dashboard. For other areas, start from [Troublesho 1. **An expired or invalid token.** Re-authenticate. For direct API access, use a valid Personal Access Token (PAT). 2. **The user lacks permission** for the action. Check the user's role in the dashboard. -3. **Management can't validate the token.** Review `docker compose logs management` for token-validation errors. +3. **Management can't validate the token.** Review `docker compose logs netbird-server` (`management` on installs from before v0.65.0) for token-validation errors. **Confirm**: Re-run the action. It succeeds, and the Management logs show no auth errors. diff --git a/src/pages/selfhosted/troubleshooting/database.mdx b/src/pages/selfhosted/troubleshooting/database.mdx index bc223b22..04834c56 100644 --- a/src/pages/selfhosted/troubleshooting/database.mdx +++ b/src/pages/selfhosted/troubleshooting/database.mdx @@ -7,15 +7,15 @@ Database and migration problems on a self-hosted deployment. For other areas, st ## Management service won't start after upgrade -**Symptom**: After an upgrade, the Management container fails to start or crash-loops. +**Symptom**: After an upgrade, the netbird-server container (`management` on installs from before v0.65.0) fails to start or crash-loops. **Likely causes and fixes** (most common first): -1. **A failed schema migration.** Check `docker compose logs management` for migration errors. This is the usual cause and the logs name the failing step. +1. **A failed schema migration.** Check `docker compose logs netbird-server` (`docker compose logs management` on installs from before v0.65.0) for migration errors. This is the usual cause and the logs name the failing step. 2. **A skipped intermediate version.** Major version jumps may require stepping through intermediate releases. Follow the [upgrade path](/selfhosted/maintenance/upgrade) for your version. 3. **A corrupt or partially-migrated database.** If the migration can't be completed, restore from a pre-upgrade backup and retry the upgrade. -**Confirm**: `docker compose ps` shows `management` as `Up`, and its logs end with the service listening rather than a migration error. +**Confirm**: `docker compose ps` shows `netbird-server` (or `management`) as `Up`, and its logs end with the service listening rather than a migration error. ## Data corruption after power loss @@ -23,8 +23,15 @@ Database and migration problems on a self-hosted deployment. For other areas, st **Likely causes and fixes** (most common first): -1. **A stale lock file.** Check for and remove a leftover database lock if the process that held it is gone. -2. **An interrupted write.** Review all service logs to find which store is failing. For SQLite, run an integrity check on the database file. -3. **An unrecoverable file.** If the store can't be repaired, restore from the most recent backup. +1. **An interrupted write.** Review all service logs to find which store is failing. With the default SQLite store, the files are `store.db`, `events.db`, and `idp.db` in the `netbird_data` volume (`/var/lib/netbird` inside the container). The netbird-server image doesn't include `sqlite3`, so stop the stack with `docker compose down` and check the files from a throwaway container. Find the volume name with `docker volume ls` (it ends in `netbird_data`), then run: + + ```bash + docker run --rm -v VOLUME_NAME:/data:ro alpine sh -c \ + "apk add -q sqlite && for f in store.db events.db idp.db; do echo \$f; if [ -f /data/\$f ]; then cp /data/\$f* /tmp/ && sqlite3 /tmp/\$f 'PRAGMA integrity_check;'; else echo MISSING; fi; done" + ``` + + A healthy file prints `ok`. The check runs on copies inside the throwaway container, so the volume isn't changed. If the interrupted write left a `-journal` file next to a store, the check applies it to the copy, the same way netbird-server does when it starts. `MISSING` means that store doesn't exist in the volume. Start the stack again with `docker compose up -d` when you're done. + +2. **An unrecoverable file.** If the store can't be repaired, restore from the most recent backup. **Confirm**: All services report `Up` in `docker compose ps`, and peers reconnect with `netbird status` showing `Management: Connected`. diff --git a/src/pages/selfhosted/troubleshooting/identity-provider.mdx b/src/pages/selfhosted/troubleshooting/identity-provider.mdx index efe266e7..99f39dc3 100644 --- a/src/pages/selfhosted/troubleshooting/identity-provider.mdx +++ b/src/pages/selfhosted/troubleshooting/identity-provider.mdx @@ -13,7 +13,7 @@ Problems with the embedded identity provider (IdP) and SSO (single sign-on) on a 1. **A user already exists.** `/setup` is only available when no users exist. If you've already created one, use the regular login page. 2. **The embedded IdP is disabled.** Confirm it is enabled in your configuration. -3. **Management isn't running.** Check `docker compose logs management`. +3. **Management isn't running.** Check `docker compose logs netbird-server` (`management` on installs from before v0.65.0). **Confirm**: `/setup` loads (first run), or the regular login page loads (setup already done). @@ -29,9 +29,22 @@ Problems with the embedded identity provider (IdP) and SSO (single sign-on) on a **Symptom**: You created a user but the password doesn't work. -**Cause**: Passwords are shown only once at creation. If it wasn't saved, it can't be recovered. +**Cause**: Passwords are shown only once at creation. If it wasn't saved, it can't be recovered, but you can set a new one. -**Fix**: Recreate the user (delete and re-add, or create a new one with a new password via the API). For the owner account with no other admins, you may need to reset the database. +**Fix**: Set a new password with the admin CLI (NetBird v0.75.0 and later). This works for the owner account too: + +```bash +read -rsp 'New password: ' NB_PASSWORD; echo +printf '%s\n' "$NB_PASSWORD" | docker exec -i netbird-server \ + /go/bin/netbird-server --config /etc/netbird/config.yaml admin user change-password \ + --email user@example.com \ + --password-file - +unset NB_PASSWORD +``` + +The password is read from a silent prompt, so it doesn't end up in your shell history. + +See [Change a local user's password](/selfhosted/maintenance/admin-cli#change-a-local-users-password) for the password rules and other options. **Confirm**: You can log in with the new credentials. @@ -43,7 +56,7 @@ Problems with the embedded identity provider (IdP) and SSO (single sign-on) on a 1. **The connector wasn't saved.** Check **Settings → Identity Providers**. 2. **The redirect URI is wrong.** Ensure the IdP application has the exact redirect URI shown by NetBird. -3. **A configuration error.** Review `docker compose logs management` for connector errors. +3. **A configuration error.** Review `docker compose logs netbird-server` for connector errors. **Confirm**: The SSO button appears on the login page and starts the IdP flow. @@ -61,9 +74,9 @@ Problems with the embedded identity provider (IdP) and SSO (single sign-on) on a **Symptom**: The Identity Providers tab is missing from Settings. -**Cause**: The tab only appears when the embedded IdP is enabled. +**Cause**: The tab only appears when the embedded IdP is enabled and your role is allowed to read identity providers. -**Fix**: Check your deployment: quickstart enables it by default, the combined setup always has it on, and older multi-container deployments need `EmbeddedIdP.Enabled` set to `true` in `management.json`. +**Fix**: Check your deployment: quickstart enables it by default, the combined setup always has it on, and older multi-container deployments need `EmbeddedIdP.Enabled` set to `true` in `management.json`. If the embedded IdP is on and the tab is still missing, sign in with a role that can manage identity providers, or ask an administrator to check your role's permissions. **Confirm**: The Identity Providers tab appears under Settings. diff --git a/src/pages/selfhosted/troubleshooting/installation.mdx b/src/pages/selfhosted/troubleshooting/installation.mdx index 96176560..1a6259e9 100644 --- a/src/pages/selfhosted/troubleshooting/installation.mdx +++ b/src/pages/selfhosted/troubleshooting/installation.mdx @@ -9,7 +9,14 @@ Problems running the self-hosted `getting-started.sh` script. For other areas, s **Symptom**: The `getting-started.sh` script stays on the wait line for several minutes, even though netbird-server looks healthy in `docker ps`. -The wait check probes the OIDC endpoint through your reverse proxy, so a healthy server alone isn't enough. Something on the path from the public internet to netbird-server is broken. Check these likely causes in order, most common first: +What the wait checks depends on the reverse proxy option you picked: + +- **Options 0 and 1 (Traefik)**: the script probes the OIDC endpoint at your public hostname through your reverse proxy, so a healthy server alone isn't enough. Something on the path to netbird-server is broken. The probe runs from the installation host itself, so first run the `curl` in step 5 below from both that host and a machine outside your network. If only the host fails, the host can't reach its own public address (some NAT setups don't support this), and external clients may be fine. Otherwise, work through the causes below. +- **Options 2 to 5 (Nginx, Nginx Proxy Manager, Caddy, other)**: the script probes netbird-server directly on `http://127.0.0.1:8081`, with no proxy in the path. A hang here means netbird-server itself isn't healthy, so start with `docker compose logs netbird-server`. + +After about two minutes of waiting, the script prints `Taking too long. Checking logs...` followed by the last 20 log lines from netbird-server, and from Traefik when the script can find its container. Read that output first; it often names the problem. + +Check these likely causes in order, most common first: **1. DNS doesn't point at the host** @@ -27,7 +34,7 @@ For options 0 (built-in Traefik) and 1 (existing Traefik), check that a real cer curl -vI https://netbird.example.com 2>&1 | grep -E "subject|issuer" ``` -A self-signed or default cert means ACME hasn't completed. Check Traefik logs for ACME errors. Common causes: port 80 not reachable from the internet, DNS not propagated yet, or a Let's Encrypt rate limit. +A self-signed or default cert means ACME hasn't completed. Check Traefik logs for ACME errors. Common causes: TCP/443 not reachable from the internet, DNS not propagated yet, or a Let's Encrypt rate limit. The built-in Traefik (option 0) validates with the TLS-ALPN-01 challenge on port 443, so port 80 doesn't affect issuance. With option 1, the challenge depends on your Traefik's certificate resolver. **3. Traefik can't read the Docker socket (option 1 only)** @@ -51,7 +58,7 @@ curl -k https://netbird.example.com/oauth2/.well-known/openid-configuration A valid response is JSON containing `"issuer"`. Anything else points to where to dig: 502 means Traefik can't reach netbird-server, 404 means the labels aren't matched, connection refused means you're not hitting Traefik at all. -**Confirm**: The probe succeeds and the script continues on its own. You can leave it waiting while you debug. To start over instead, stop it with Ctrl+C, run `docker compose down -v`, fix the issue, and re-run. +**Confirm**: The probe succeeds and the script continues on its own. You can leave it waiting while you debug. To start over instead, stop it with Ctrl+C, remove the generated files as shown in [Script fails on existing installation check](#script-fails-on-existing-installation-check), fix the issue, and re-run. `docker compose down -v` on its own leaves `config.yaml` behind, and the script refuses to run while that file exists. ## Script fails on existing installation check @@ -66,6 +73,7 @@ docker compose down --volumes rm -f docker-compose.yml dashboard.env config.yaml proxy.env \ traefik-dynamic.yaml nginx-netbird.conf caddyfile-netbird.txt \ npm-advanced-config.txt +rm -rf crowdsec/ ``` **Confirm**: Re-run the script; it proceeds past the existing-installation check and starts provisioning. diff --git a/src/pages/use-cases/deployment/mdm-rollout.mdx b/src/pages/use-cases/deployment/mdm-rollout.mdx new file mode 100644 index 00000000..15a7afe6 --- /dev/null +++ b/src/pages/use-cases/deployment/mdm-rollout.mdx @@ -0,0 +1,281 @@ +import { Note, Warning } from "@/components/mdx"; +import { Tiles } from "@/components/Tiles"; + +export const description = + "Roll NetBird out to hundreds of company laptops with your MDM: install the client, have users sign in with SSO, pin the client's settings so they cannot drift, and keep the fleet updated. Includes an end-to-end Microsoft Intune example for Windows and macOS."; + +# Roll Out NetBird Across Your Organization with MDM + +Installing NetBird on your own laptop takes a minute. Installing it on 500 company laptops, making sure every one of them connects to the right server as the right user, and keeping their settings the way your security team signed off on, is a different job. + +This guide shows how to do that with the MDM you already use, and walks through a complete rollout with Microsoft Intune. + + +**TL;DR**: a fleet rollout is four jobs with one owner each. Your MDM **installs** the client and **pins its settings**, users **sign in with SSO** once, and you pick **one owner for updates**. Start with a pilot group, then widen. Jump to the [Intune example](#example-roll-out-with-microsoft-intune) if you are ready to build. + + +## One device, by hand + +On a single machine: + +1. Download the installer: EXE or MSI on [Windows](/get-started/install/windows), PKG on [macOS](/get-started/install/macos). +2. Run it with administrator rights. +3. Open NetBird and choose the management server: NetBird Cloud, or self-hosted. +4. Click **Connect** and sign in through the browser with your identity provider (IdP). The device joins as a peer under your user. + +That is enough for one person. Afterward the device's owner still controls the server URL, profiles, SSH, and every setting your security posture depends on. On one machine, nobody minds. On a fleet, each of those is a support ticket or a gap nobody notices. + +## Why it does not scale + +Repeat those four steps across an organization and each one turns into its own problem: + +- **Installing needs administrator rights** most users do not have (and should not). +- **Users get the server wrong** and land in an account that is not yours. +- **Setup keys break identity.** A peer enrolled with a setup key has no user behind it, so user-based policies, login expiration, and offboarding do not apply. Setup keys are for servers, not laptops: see [Bootstrap peers via config file](/manage/peers/bootstrap-via-config-file). +- **Settings and versions drift** with no owner to pin them. +- **Unmanaged devices look the same as managed ones** once someone signs in with valid credentials. + +## The rollout model + +| Job | Owner | What it uses | +| --- | --- | --- | +| **Install** the client | Your MDM | The standard installer, with no arguments: MSI on Windows, PKG on macOS | +| **Enroll** the device | The user, once | SSO on first launch. The peer joins under the user's identity; [IdP group sync](/manage/team/idp-sync) places it | +| **Enforce** the settings | Your MDM | An [MDM policy](/client/mdm-integration) that pins the management server and locks the settings you choose | +| **Update** the client | NetBird **or** your MDM, never both | [Automatic Updates](/manage/peers/auto-update), or a new installer version in your MDM | + +Two properties make this split work: + +- **The installer carries no configuration; the policy carries all of it.** Same MSI or PKG everywhere. Change the policy anytime and the client applies it within a minute, with no reinstall. +- **The policy removes the hard step for users.** When it pins the management server, the app skips the server question. The user opens NetBird, clicks **Connect**, and signs in with the account they already use. + +Optional jobs on the same tools: + +- **Gate access on compliance** with your MDM's [NetBird integration](/manage/access-control/endpoint-detection-and-response). See [Step 7](#step-7-optional-only-let-compliant-devices-in). +- **Enroll devices with no user** (kiosks, meeting-room PCs, servers) with a [setup key](/manage/peers/register-machines-using-setup-keys). Keep them in their own group with their own policy. + + +**Skip this guide when it does not match the job.** A handful of laptops is faster by hand. A Linux-only fleet has no MDM policy channel today: use [service-install flags or a config file](/client/mdm-integration#when-not-to-use-mdm-policy). Headless devices belong on setup keys, not this SSO path. + + +## Supported MDMs + +NetBird uses three MDM capabilities and adds no agent of its own: app deployment, a managed-configuration channel (`HKLM\Software\Policies` on Windows, managed preferences on macOS), and an optional compliance signal. + +Any MDM that can install a package and write to those channels works. These have dedicated guides: + +| MDM | Install the client | Enforce settings | +| --- | --- | --- | +| [Microsoft Intune](https://learn.microsoft.com/en-us/intune/) | [Deploy with Intune](/manage/peers/mdm-deployment/intune-netbird-integration) | [Windows](/manage/peers/mdm-deployment/windows-mdm-policy), [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#deliver-it-with-your-mdm) | +| [Jamf Pro](https://www.jamf.com/products/jamf-pro/) | [Deploy with Jamf Pro](/manage/peers/mdm-deployment/jamf-pro-netbird-integration) | [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#deliver-it-with-your-mdm) | +| [Kandji](https://www.kandji.io/) | [Deploy with Kandji](/manage/peers/mdm-deployment/kandji-netbird-integration) | [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#deliver-it-with-your-mdm) | +| Group Policy (Active Directory) | [Deploy with Group Policy](/manage/peers/mdm-deployment/windows-gpo-deployment) | [Windows](/manage/peers/mdm-deployment/windows-mdm-policy#group-policy) | +| [JumpCloud](https://jumpcloud.com/) | Upload the MSI or PKG as a software package | [Windows](/manage/peers/mdm-deployment/windows-mdm-policy#jump-cloud), [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#deliver-it-with-your-mdm) | +| Mosyle, Workspace ONE, and others | Upload the MSI or PKG as a software package | [Windows](/manage/peers/mdm-deployment/windows-mdm-policy#registry-reference), [macOS](/manage/peers/mdm-deployment/macos-mdm-policy#deliver-it-with-your-mdm) | + +This guide covers Windows and macOS. Linux has no MDM channel in NetBird today: install with your configuration management tool and set the same options with [service-install flags or a config file](/client/mdm-integration#when-not-to-use-mdm-policy). + +## Example: roll out with Microsoft Intune + +This example rolls NetBird out to Windows and macOS laptops at a company that uses Microsoft Entra ID for identity and Intune for device management. Users sign in with their Entra ID accounts. Laptops get the [user device policy](/client/mdm-integration#recommended-policies) recommended for end-user machines. + +The examples use a self-hosted management server at `https://netbird.example.com:443`. On NetBird Cloud, use `https://api.netbird.io:443` instead. + +### Before you start + +- NetBird SSO with Microsoft Entra ID. On NetBird Cloud, [Microsoft sign-in](/manage/team/single-sign-on#google-microsoft-and-git-hub) works with no extra setup; for self-hosted, see [Microsoft Entra ID](/selfhosted/identity-providers/managed/microsoft-entra-id). +- Recommended: [Entra ID group sync](/manage/team/idp-sync/microsoft-entra-id-sync), so each new peer lands in its user's groups and your [access policies](/manage/access-control) apply from the first connection. +- An Intune admin with at least the **Policy and Profile Manager** role, and Windows and macOS devices enrolled in Intune. +- Two Entra ID **device** groups: `NetBird Pilot` (a handful of IT-owned laptops) and `NetBird Laptops` (every end-user laptop). Keep routing peers and servers out of both: the policy below stops a device from routing other peers' traffic. +- The installers: the [Windows MSI](https://pkgs.netbird.io/windows/msi/x64), and the macOS PKG for [Apple Silicon](https://pkgs.netbird.io/macos/arm64) and [Intel](https://pkgs.netbird.io/macos/amd64). +- The policy templates: netbird.admx and netbird.adml for Windows, and netbird-macos.mobileconfig for macOS. + + +**Find laptops where users already installed NetBird themselves.** On Windows, the EXE and the MSI do not detect each other: pushing the MSI onto a laptop that has the EXE produces two overlapping installations instead of an upgrade. Uninstall the EXE first, or deploy the EXE with Intune instead. Either way, the peer's registration in `C:\ProgramData\Netbird` survives the uninstall, so the user does not have to enroll again. + + +### Step 1: Define the policy + +Decide what to pin before you deploy anything. For end-user laptops, start from four keys with your server URL (for example `https://netbird.example.com:443`) as the management URL: + +| Key | Value | Why | +| --- | --- | --- | +| `managementURL` | Your server URL | Every laptop talks to your server; the app skips the server question on first launch | +| `disableUpdateSettings` | `true` | Users can connect, disconnect, and sign in, but cannot change settings | +| `disableProfiles` | `true` | Users cannot add a second profile (for example a personal NetBird account) | +| `disableServerRoutes` | `true` | A laptop wrongly assigned as a routing peer does not carry other peers' traffic | + +Leave every other key out. A key in the policy is pinned even when its value is `false`; a key you leave out stays the user's to change. To adapt the policy, see [What you can achieve](/client/mdm-integration#what-you-can-achieve). + + +**On NetBird Cloud, `managementURL` pins the server, not the account.** Every Cloud account shares `https://api.netbird.io:443`, so this key stops a user from pointing the client at another server, but not from signing out and into a different Cloud account. `disableProfiles` still blocks a second profile on the device. A peer that leaves your account loses access because your policies no longer apply to it. + + +### Step 2: Deliver the policy + +Create the policy first and assign it **together with, or before,** the app. The desktop app decides whether to ask for a server when it opens for the first time; if the policy has not arrived by then, the user sees the question you meant to remove. + +**Windows**: an Imported Administrative templates profile. + +1. Import `netbird.admx` and `netbird.adml` once per tenant: **Devices → Manage devices → Configuration → Import ADMX → Import**. +2. Create the profile: **Devices → Manage devices → Configuration → Create → New policy**, platform **Windows 10 and later**, profile type **Templates → Imported Administrative templates (Preview)**. Name it `NetBird: user laptops`. +3. Under **NetBird**, set **Management URL** to **Enabled** with your URL, and **Disable update settings**, **Disable profiles**, and **Disable server routes** to **Enabled**. Leave everything else **Not configured**: **Disabled** pins the setting to `false`. +4. Assign the profile to the `NetBird Pilot` device group. + +[Enforce NetBird Settings on Windows](/manage/peers/mdm-deployment/windows-mdm-policy#enforce-settings-with-intune) has every step in detail, plus an OMA-URI alternative for tenants that cannot import templates. + +**macOS**: a custom configuration profile. + +1. Edit `netbird-macos.mobileconfig`. Inside the `mcx_preference_settings` dictionary, keep only the four keys, and replace each `PayloadUUID` with a fresh value from `uuidgen`: + + ```xml + managementURL + https://netbird.example.com:443 + disableUpdateSettings + + disableProfiles + + disableServerRoutes + + ``` + + Write booleans as `` or ``, never as ``: macOS locks an integer boolean but never applies it. +2. Create the profile: **Devices → Manage devices → Configuration → Create → New policy**, platform **macOS**, profile type **Templates → Custom**. Upload the file and name it `NetBird: user laptops`. +3. Assign it to the `NetBird Pilot` device group. + +See [Enforce NetBird Settings on macOS](/manage/peers/mdm-deployment/macos-mdm-policy) for the template details and how to check the profile arrived. + +### Step 3: Deploy the client + +**Windows**: the MSI as a line-of-business app. + +1. **Apps → Windows → Create**, app type **Line-of-business app**, and upload the MSI. +2. Set **App install context** to **Device**, and leave **Command-line arguments** empty: the policy carries the configuration. +3. Set **Ignore app version** according to who owns updates, in [Step 5](#step-5-pick-one-owner-for-updates). +4. Assign it as **Required** to the `NetBird Pilot` device group. + +

+ Intune Add App screen for the NetBird MSI, with App install context, Ignore app version, and an empty Command-line arguments field +

+ +**macOS**: the PKG as a macOS app. + +1. **Apps → macOS → Create**, app type **macOS app (PKG)**, and upload the PKG. Apple Silicon and Intel Macs need different packages: add each as its own app, and assign each to a group that holds only Macs of that type. +2. The bundle ID is `io.netbird.client`. Set **Ignore app version** as shown in [Step 5](#step-5-pick-one-owner-for-updates). +3. Assign it as **Required** to the `NetBird Pilot` device group. + +For a Win32 package with custom detection rules and supersedence, see [Deploy with Intune](/manage/peers/mdm-deployment/intune-netbird-integration#deploying-net-bird-as-a-win32-app). + +### Step 4: First sign-in + +Intune installs NetBird and starts its service. The device does not join your network until its user signs in, so tell users what to expect before the pilot starts: + +- **On macOS**, the installer opens the NetBird app for the user who is logged in. +- **On Windows**, a silent install does not open the app. The user opens **NetBird** from the Start menu once; from then on it opens at every login. + +Because the policy pins the server, the app skips the server question. The user clicks **Connect**, signs in with their Entra ID account in the browser, and is connected. The new peer appears in the NetBird dashboard under that user's name and, with group sync, in that user's groups. + +

+ NetBird desktop app showing the Connected state after the user signs in +

+ +A short message is enough: + +> NetBird is now installed on your laptop. Open NetBird, click Connect, and sign in with your work account. You only need to do this once. + +### Step 5: Pick one owner for updates + +The client can be updated by NetBird or by Intune. Choose one: when both try, they work against each other. + +| Owner | What to do | +| --- | --- | +| **NetBird** updates the client | Enable [Automatic Updates](/manage/peers/auto-update) under **Settings → Clients**, and **Force Automatic Updates** to install without prompting. Set **Ignore app version** to **Yes** on the Intune apps so Intune only checks that NetBird is installed. Otherwise Intune sees the self-updated version as a different app, tries to reinstall the older one, and the MSI refuses. | +| **Intune** updates the client | Leave Automatic Updates disabled in NetBird, set **Ignore app version** to **No**, and upload each new MSI and PKG when you are ready to roll it out. | + +Letting NetBird update is less work and keeps clients close to your management server's version. Letting Intune do it gives you change windows and staged rings. To hold the fleet on a specific version with NetBird, set Automatic Updates to a **Custom Version**. + +### Step 6: Verify the pilot, then widen + +Check each layer, from Intune down to the device: + +1. **In Intune**, the app's **Device install status** and the profile's **Device status** show success for every pilot device. +2. **On a Windows device**, from an elevated prompt: + + ```shell + reg query HKLM\Software\Policies\NetBird + netbird debug config + netbird status + ``` + + `reg query` shows what Intune wrote. In `netbird debug config`, the `mDMManagedFields` array lists every key the client reads from the policy: + + ```json + "mDMManagedFields": [ + "disableProfiles", + "disableServerRoutes", + "disableUpdateSettings", + "managementURL" + ] + ``` + +3. **On a macOS device**, run `netbird debug config` and `netbird status` the same way. The [macOS page](/manage/peers/mdm-deployment/macos-mdm-policy) shows how to check the profile itself. +4. **In the NetBird dashboard**, each pilot device appears under **Peers** with its user's name and groups. +5. **As a user**, open the app: the **Network**, **Security**, **SSH**, **Advanced**, and **Profiles** settings tabs are gone, and **Connect** still works. + +When the pilot looks right, add the `NetBird Laptops` group to the assignments of both apps and both profiles. Devices pick them up at their next Intune check-in. If something is off, see [Verifying enforcement](/client/mdm-integration#verifying-enforcement) and [Troubleshooting](/client/mdm-integration#troubleshooting). + +### Step 7 (optional): Only let compliant devices in + +With the client on every company laptop, you can make sure nothing else gets in. NetBird's [Intune integration](/manage/access-control/endpoint-detection-and-response/intune-mdm) checks each peer in the groups you select against Intune: a device that is not managed by Intune, or not compliant with your compliance policies, waits for approval and cannot reach anything. A personal laptop signed in with a valid account stays locked out. + +Connect Intune under **Integrations → EDR** in the NetBird dashboard and select the groups the check applies to, such as the groups synced from Entra ID for your employees. The integration covers Windows and macOS, and is available on the NetBird Cloud Business plan and with a self-hosted [Enterprise license](/selfhosted/enterprise). + +

+ Connect NetBird with Intune dialog, selecting the groups the Intune check applies to +

+ +A device that fails the check shows **Approval required** in the peers list until Intune reports it as managed and compliant: + +

+ NetBird peers list with a peer marked Approval required +

+ +## Rollout checklist + +- SSO works with your IdP, and groups sync into NetBird +- Access policies use the synced groups +- Laptops with a user-installed NetBird EXE are cleaned up, or you deploy the EXE +- The policy holds only the keys you mean to pin +- The policy is assigned together with, or before, the app +- One owner for updates, with **Ignore app version** set to match +- Users know to open NetBird, click **Connect**, and sign in once +- The pilot group checks out in Intune, on the device, and in the dashboard +- Routing peers and servers are outside the laptop groups +- Optional: the Intune integration gates access on compliance + + diff --git a/src/pages/use-cases/index.mdx b/src/pages/use-cases/index.mdx index b699f80f..777e0172 100644 --- a/src/pages/use-cases/index.mdx +++ b/src/pages/use-cases/index.mdx @@ -48,6 +48,13 @@ You know what you want to achieve; this page finds the guide that gets you there | Implement Zero Trust networking step by step | [Implement Zero Trust](/use-cases/security/implement-zero-trust) | | Publish internal services without opening inbound ports | [Private Proxy Without Inbound Ports](/use-cases/security/private-no-inbound) | +### Roll NetBird out to company devices + +| I want to... | Guide | +| -------------------------------------------------------------- | ------------------------------------------------------ | +| Install and configure NetBird on every company laptop with MDM | [Roll Out with MDM](/use-cases/deployment/mdm-rollout) | +| Stop users changing NetBird's settings on managed devices | [MDM Integration](/client/mdm-integration) | + ### Run NetBird at home Homelabbers get a dedicated starting point covering NAS access, home automation, media servers, and connecting family networks: see the [Homelab guide](/use-cases/homelab). To run NetBird directly on your router, see the [MikroTik](/get-started/install/mikrotik), [OpenWrt](/get-started/install/openwrt), [pfSense](/get-started/install/pfsense), and [OPNsense](/get-started/install/opnsense) install guides. diff --git a/src/pages/use-cases/remote-access/exit-nodes.mdx b/src/pages/use-cases/remote-access/exit-nodes.mdx index 4aba92ee..a41f7b92 100644 --- a/src/pages/use-cases/remote-access/exit-nodes.mdx +++ b/src/pages/use-cases/remote-access/exit-nodes.mdx @@ -148,7 +148,7 @@ On an east-coast device, run `netbird status -d`: the peer carrying the `0.0.0.0 ### What this does and does not do -This is nearest-exit selection plus automatic failover, not load balancing: each device follows its own lowest-latency exit node, and traffic is not spread evenly across the exit nodes. See [Latency switching](/manage/networks/how-routing-peers-work#latency-switching-equal-metrics) for the selection mechanism. +This is nearest-exit selection plus automatic failover, not load balancing: each device follows its own lowest-latency exit node, and traffic is not spread evenly across the exit nodes. See [Nearest routing peer](/manage/networks/how-routing-peers-work#nearest-routing-peer-equal-metrics) for the selection mechanism. Latency-based selection is also not a guarantee of *which* exit node a given user lands on. If a rule must always hold, such as certain users always exiting in a specific country, use separate exit nodes with per-group distribution, or posture checks as in [Geo-Based Exit Node Routing](#geo-based-exit-node-routing) below. Make sure each device receives at most one exit node with [Auto Apply](#exit-node-selection-and-auto-apply) enabled: enable it on one exit node per distribution group, and keep those groups from overlapping — more than one auto-applied exit node on a device is the failure mode described in step 2 above. Offer any additional exit nodes with Auto Apply disabled; users can switch between exit nodes manually in the client either way. @@ -214,7 +214,7 @@ Set up the exit node as described in the [configuration steps](#configuration-st ### 2. Lock network selection on the devices -Deliver `disableNetworks: true` to the devices through your device management tooling: a registry policy or Intune profile on Windows, a configuration profile on macOS. See [MDM Integration](/client/mdm-integration) for the payload formats and delivery options per platform. The daemon re-reads the policy about once a minute, so it takes effect without restarting NetBird. +Deliver `disableNetworks: true` to the devices through your device management tooling: a registry policy or Intune profile on Windows, a configuration profile on macOS. See [MDM Integration](/client/mdm-integration) for how the client applies a policy, and the [Windows](/manage/peers/mdm-deployment/windows-mdm-policy), [macOS](/manage/peers/mdm-deployment/macos-mdm-policy), and [iOS](/manage/peers/mdm-deployment/ios-mdm-policy) pages for the payload formats and delivery options. The daemon re-reads the policy about once a minute, so it takes effect without restarting NetBird. On Linux devices and servers, where there is no MDM channel, set the equivalent flag when installing the service: @@ -245,7 +245,7 @@ On a locked device, `netbird networks list` returns the "network selection is di With Auto Apply and `disableNetworks` in place, every device in `remote-workers` routes its internet traffic through the exit node, and no user can select, deselect, or switch exit nodes from the device. Enforcement lives in the daemon, so the CLI and the UI are equally locked. -It does not make the tunnel itself mandatory. A user can still disconnect NetBird entirely with `netbird down`, and a user with administrator rights on the device can stop or remove the service; NetBird has no always-on or kill-switch mode. Enforce that layer with the operating system: run devices with standard user accounts, manage the NetBird service through your MDM, and design your access policies so that disconnecting from NetBird costs the user access to company resources instead of freeing them from restrictions. The `disableUpdateSettings` and `disableProfiles` keys close the remaining side doors of reconfiguring the client or switching to an unmanaged profile; see [MDM Integration](/client/mdm-integration#policy-keys-reference). +It does not make the tunnel itself mandatory. A user can still disconnect NetBird entirely with `netbird down`, and a user with administrator rights on the device can stop or remove the service; NetBird has no always-on or kill-switch mode. Enforce that layer with the operating system: run devices with standard user accounts, manage the NetBird service through your MDM, and design your access policies so that disconnecting from NetBird costs the user access to company resources instead of freeing them from restrictions. The `disableUpdateSettings` and `disableProfiles` keys close the remaining side doors of reconfiguring the client or switching to an unmanaged profile; see [Lock the client app](/client/mdm-integration#lock-the-client-app). ## Selective Internal Access Behind an Exit Node diff --git a/src/pages/use-cases/security/implement-zero-trust.mdx b/src/pages/use-cases/security/implement-zero-trust.mdx index 1b3d804c..cf79bf28 100644 --- a/src/pages/use-cases/security/implement-zero-trust.mdx +++ b/src/pages/use-cases/security/implement-zero-trust.mdx @@ -15,6 +15,10 @@ Before you start planning or changing policies, confirm the basics. This guide uses features from both the **Team** and **Business** plans. Check that your deployment includes what you need. + + Team and Business are NetBird Cloud plans. On a self-hosted deployment, IdP user and group provisioning, traffic events logging, and event streaming require an [Enterprise Commercial License](/selfhosted/enterprise). Audit event logging and posture checks are included in the open source Community Edition. + + **Team plan** - **[User and group provisioning from IdP (Identity Provider)](/manage/team/idp-sync)** for automatic group sync @@ -511,7 +515,7 @@ A routing peer is a NetBird peer that: - Is selected as a routing peer in a [Network](/manage/networks) or [Network Route](/manage/network-routes) configuration

- high-level-dia + Three NetBird peers on the overlay network connect through a WireGuard tunnel to a routing peer in a private network. Access policies on the routing peer allow a database on TCP 5432, a web app on TCP 443, and an RDP workstation on TCP/UDP 3389; a printer with no policy is unreachable.

### 5.2 Direction of traffic with routing peers diff --git a/typography.js b/typography.js index 5915d5a4..b5fa59c4 100644 --- a/typography.js +++ b/typography.js @@ -48,9 +48,13 @@ module.exports = ({ theme }) => ({ // Layout '> *': { - maxWidth: theme('maxWidth.2xl'), - marginLeft: 10, + maxWidth: '100%', + marginLeft: 0, marginRight: 'auto', + '@screen md': { + maxWidth: theme('maxWidth.2xl'), + marginLeft: 10, + }, '@screen lg': { maxWidth: theme('maxWidth.3xl'), marginLeft: 10,