Files
netbird/client/link/cmd/nblink/main.go
T
Maycon Santos 66a28fd666 [client] Add nblink, an unprivileged forwarder into the network (#7955)
Reaching an internal service meant installing the agent, which needs root
for a TUN device. nblink runs the embedded client in netstack mode and
forwards local HTTP listeners to upstreams inside the tunnel, so it works on
managed machines and in rootless containers. Upstream names resolve
in-process.

Forwards bind loopback unless --allow-public-bind is given. The listener
refuses rebound and cross-site browser requests and bounds stalled request
bodies. It ships as release archives and a distroless image that runs under
an arbitrary UID.

An embedded client given no StatePath now keeps its state in memory rather
than falling back to the installed agent's state file, which every embedded
client in a proxy process also shared. The state manager gets an explicit
in-memory store for it.
2026-10-12 13:54:15 +02:00

70 lines
1.9 KiB
Go

// Command nblink forwards local listeners into a NetBird network from an
// unprivileged process.
package main
import (
"context"
"fmt"
"os"
"os/signal"
"syscall"
"github.com/spf13/cobra"
"github.com/netbirdio/netbird/client/link"
"github.com/netbirdio/netbird/util"
"github.com/netbirdio/netbird/version"
)
func main() {
if err := rootCmd().Execute(); err != nil {
fmt.Fprintf(os.Stderr, "nblink: %v\n", err)
os.Exit(1)
}
}
func rootCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "nblink",
Short: "Forward local ports into a NetBird network",
Long: "nblink runs a NetBird peer in userspace and forwards local listeners\n" +
"into the network. It needs no TUN device and no elevated privileges,\n" +
"so it runs on managed machines and in rootless containers.\n\n" +
"Every flag can also be set through an NB_-prefixed environment variable\n" +
"of the same name, which is how the container image is configured.",
Example: " nblink --forward 'http://8080=https://grafana.internal'\n" +
" nblink --forward 'http://127.0.0.1:8080=https://grafana.internal' --setup-key file:/run/secrets/key",
Version: version.NetbirdVersion(),
// A stray positional argument is a mistake, most often a forward spec
// that lost its --forward, and must not start the forwarder anyway.
Args: cobra.NoArgs,
// main prints the error, so cobra must not print it a second time.
SilenceUsage: true,
SilenceErrors: true,
}
raw := link.BindFlags(cmd)
cmd.RunE = func(cmd *cobra.Command, _ []string) error {
if err := link.SetFlagsFromEnvVars(cmd); err != nil {
return err
}
cfg, err := raw.Resolve()
if err != nil {
return err
}
if err := util.InitLog(cfg.LogLevel, util.LogConsole); err != nil {
return fmt.Errorf("init log: %w", err)
}
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
return link.Run(ctx, cfg)
}
return cmd
}