diff --git a/src/components/NavigationDocs.jsx b/src/components/NavigationDocs.jsx index 5a70bc3e..1b87ec11 100644 --- a/src/components/NavigationDocs.jsx +++ b/src/components/NavigationDocs.jsx @@ -904,6 +904,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: 'gRPC Daemon Socket', href: '/client/grpc-socket' }, { title: 'HTTP/JSON Daemon Socket', href: '/client/json-socket' }, 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.