Files
netbird/management/internals/server/config/config.go
T
riccardom c71fd1d841 [management,client] Default to NetBird's upload service when nothing is configured
The previous commit made a peer with no destination — no MDM override, no URL
named by the caller, nothing published by its management server — refuse to
upload and keep the bundle local unless it was enrolled with NetBird's cloud.
That closed the reported data-boundary concern, but it broke the default for
everyone who uploads a bundle as part of their day: a self-hosted user opening
a support ticket got a refusal where the command used to work.

Product decision (NetBird's, not the reporter's): the knob to keep bundles
inside your own infrastructure is what this branch provides, and it is enough.
The default stays the service NetBird runs, self-hosted included. An admin who
needs the bundles to stay in-house configures the destination; until then the
everyday flow keeps working.

So ResolveUploadURL drops the cloud check, the sentinel error and the
managementURL argument, and never fails:

    MDM  >  explicitly named URL  >  published by management  >  NetBird's service

Nothing observable changes for a deployment that configures nothing, which also
removes two edge cases the fail-closed default had: a peer still enrolled on the
legacy api.wiretrustee.com host would have been classified self-hosted and
refused, and an upgrade would have silently stopped uploads for self-hosted
deployments relying on them. The privilege gate is unaffected — a host other
than the default one still requires a privileged caller, so pointing the CLI
somewhere other than what management published needs root.
2026-09-10 16:38:41 +02:00

286 lines
10 KiB
Go

package config
import (
"errors"
"fmt"
"net/netip"
"net/url"
"github.com/netbirdio/netbird/management/server/idp"
"github.com/netbirdio/netbird/management/server/types"
"github.com/netbirdio/netbird/shared/management/client/common"
"github.com/netbirdio/netbird/util"
)
type (
// Protocol type
Protocol string
// Provider authorization flow type
Provider string
)
const (
UDP Protocol = "udp"
DTLS Protocol = "dtls"
TCP Protocol = "tcp"
HTTP Protocol = "http"
HTTPS Protocol = "https"
NONE Provider = "none"
)
const (
// DefaultDeviceAuthFlowScope defines the bare minimum scope to request in the device authorization flow
DefaultDeviceAuthFlowScope string = "openid"
)
var MgmtConfigPath string
// Config of the Management service
type Config struct {
Stuns []*Host
TURNConfig *TURNConfig
Relay *Relay
Signal *Host
Datadir string
DataStoreEncryptionKey string
HttpConfig *HttpServerConfig
IdpManagerConfig *idp.Config
DeviceAuthorizationFlow *DeviceAuthorizationFlow
PKCEAuthorizationFlow *PKCEAuthorizationFlow
StoreConfig StoreConfig
ReverseProxy ReverseProxy
AgentNetwork AgentNetwork
// DebugUpload configures where the peers of this deployment send their
// debug bundles. See DebugUpload.
DebugUpload DebugUpload
// disable default all-to-all policy
DisableDefaultPolicy bool
// EmbeddedIdP contains configuration for the embedded Dex OIDC provider.
// When set, Dex will be embedded in the management server and serve requests at /oauth2/
EmbeddedIdP *idp.EmbeddedIdPConfig
HighestSupportedSyncMessageVersion *int
PerAccountHighestSupportedSyncMessageVersion map[string]int
}
// GetAuthAudiences returns the audience from the http config and device authorization flow config
func (c Config) GetAuthAudiences() []string {
audiences := []string{c.HttpConfig.AuthAudience}
if c.HttpConfig.ExtraAuthAudience != "" {
audiences = append(audiences, c.HttpConfig.ExtraAuthAudience)
}
if c.DeviceAuthorizationFlow != nil && c.DeviceAuthorizationFlow.ProviderConfig.Audience != "" {
audiences = append(audiences, c.DeviceAuthorizationFlow.ProviderConfig.Audience)
}
return audiences
}
// TURNConfig is a config of the TURNCredentialsManager
type TURNConfig struct {
TimeBasedCredentials bool
CredentialsTTL util.Duration
Secret string
Turns []*Host
}
// Relay configuration type
type Relay struct {
Addresses []string
CredentialsTTL util.Duration
Secret string
}
// HttpServerConfig is a config of the HTTP Management service server
type HttpServerConfig struct {
LetsEncryptDomain string
// CertFile is the location of the certificate
CertFile string
// CertKey is the location of the certificate private key
CertKey string
// AuthClientID is the client id used for proxy SSO auth
AuthClientID string
// AuthAudience identifies the recipients that the JWT is intended for (aud in JWT)
AuthAudience string
// CLIAuthAudience identifies the client app recipients that the JWT is intended for (aud in JWT)
// Used only in conjunction with EmbeddedIdP
CLIAuthAudience string
// AuthIssuer identifies principal that issued the JWT
AuthIssuer string
// AuthUserIDClaim is the name of the claim that used as user ID
AuthUserIDClaim string
// AuthKeysLocation is a location of JWT key set containing the public keys used to verify JWT
AuthKeysLocation string
// OIDCConfigEndpoint is the endpoint of an IDP manager to get OIDC configuration
OIDCConfigEndpoint string
// IdpSignKeyRefreshEnabled identifies the signing key is currently being rotated or not
IdpSignKeyRefreshEnabled bool
// Extra audience
ExtraAuthAudience string
// AuthCallbackDomain contains the callback domain
AuthCallbackURL string
}
// Host represents a Netbird host (e.g. STUN, TURN, Signal)
type Host struct {
Proto Protocol
// URI e.g. turns://stun.netbird.io:4430 or signal.netbird.io:10000
URI string
Username string
Password string
}
// DeviceAuthorizationFlow represents Device Authorization Flow information
// that can be used by the client to login initiate a Oauth 2.0 device authorization grant flow
// see https://datatracker.ietf.org/doc/html/rfc8628
type DeviceAuthorizationFlow struct {
Provider string
ProviderConfig ProviderConfig
}
// PKCEAuthorizationFlow represents Authorization Code Flow information
// that can be used by the client to login initiate a Oauth 2.0 authorization code grant flow
// with Proof Key for Code Exchange (PKCE). See https://datatracker.ietf.org/doc/html/rfc7636
type PKCEAuthorizationFlow struct {
ProviderConfig ProviderConfig
}
// ProviderConfig has all attributes needed to initiate a device/pkce authorization flow
type ProviderConfig struct {
// ClientID An IDP application client id
ClientID string
// ClientSecret An IDP application client secret
ClientSecret string
// Domain An IDP API domain
// Deprecated. Use TokenEndpoint and DeviceAuthEndpoint
Domain string
// Audience An Audience for to authorization validation
Audience string
// TokenEndpoint is the endpoint of an IDP manager where clients can obtain access token
TokenEndpoint string
// DeviceAuthEndpoint is the endpoint of an IDP manager where clients can obtain device authorization code
DeviceAuthEndpoint string
// AuthorizationEndpoint is the endpoint of an IDP manager where clients can obtain authorization code
AuthorizationEndpoint string
// Scopes provides the scopes to be included in the token request
Scope string
// UseIDToken indicates if the id token should be used for authentication
UseIDToken bool
// RedirectURL handles authorization code from IDP manager
RedirectURLs []string
// DisablePromptLogin makes the PKCE flow to not prompt the user for login
DisablePromptLogin bool
// LoginFlag is used to configure the PKCE flow login behavior
LoginFlag common.LoginFlag
}
// StoreConfig contains Store configuration
type StoreConfig struct {
Engine types.Engine
}
// AgentNetwork contains agent-network (LLM gateway) configuration.
type AgentNetwork struct {
// PricingDefaultsFile is the path to the YAML file holding the default
// LLM pricing table (defaults_llm_pricing.yaml). A relative path is
// resolved against <Datadir>, so a bare filename lands alongside the
// store. Empty falls back to probing <Datadir>/defaults_llm_pricing.yaml;
// with no file present the compiled-in defaults serve. Schema: surface ("openai"/"anthropic"/
// "bedrock") -> model -> rates in USD per 1k tokens (input_per_1k,
// output_per_1k, and the optional cached_input_per_1k /
// cache_read_per_1k / cache_creation_per_1k). File entries replace the
// compiled-in entry for the same surface+model whole; everything else
// keeps the compiled-in rates. The file is re-read periodically (mtime
// poll), and the live table feeds both the synthesizer (what proxies
// bill with) and the dashboard's catalog endpoint (what model rows
// prefill with). An explicitly configured path that fails to load
// fails startup; runtime reload errors keep the previous table.
PricingDefaultsFile string
}
// DebugUpload configures the debug-bundle upload service this deployment
// publishes to its peers.
//
// The client paths that upload without a human picking a destination — the
// remote debug-bundle job, the mobile clients and the desktop UI — take the
// destination from here. It exists so an operator who needs the bundles, which
// carry peer logs, routes, DNS and firewall state, to stay inside their own
// infrastructure can say so once. Leaving it unset publishes no destination and
// the peers upload to the service NetBird runs, which keeps the everyday
// "collect a bundle and send it to support" flow working out of the box.
//
// Set URL to the upload service's get-URL endpoint, e.g.
// https://upload.example.com/upload-url (see the upload-server component).
type DebugUpload struct {
// URL is the get-URL endpoint of the upload service. Must be https: the
// client fetches an upload URL from it and then PUTs the bundle to whatever
// that fetch returns, so a plaintext hop is a place to intercept both.
URL string
}
// Validate rejects a destination the client would refuse anyway, so a typo in
// management.json surfaces at startup instead of at the first bundle upload.
func (d DebugUpload) Validate() error {
if d.URL == "" {
return nil
}
parsed, err := url.Parse(d.URL)
if err != nil {
return fmt.Errorf("parse debug upload URL: %w", err)
}
if parsed.Scheme != "https" {
return fmt.Errorf("debug upload URL must use https, got scheme %q", parsed.Scheme)
}
if parsed.Host == "" {
return errors.New("debug upload URL must have a host")
}
return nil
}
// ReverseProxy contains reverse proxy configuration in front of management.
type ReverseProxy struct {
// TrustedHTTPProxies represents a list of trusted HTTP proxies by their IP prefixes.
// When extracting the real IP address from request headers, the middleware will verify
// if the peer's address falls within one of these trusted IP prefixes.
TrustedHTTPProxies []netip.Prefix
// TrustedHTTPProxiesCount specifies the count of trusted HTTP proxies between the internet
// and the server. When using the trusted proxy count method to extract the real IP address,
// the middleware will search the X-Forwarded-For IP list from the rightmost by this count
// minus one.
TrustedHTTPProxiesCount uint
// TrustedPeers represents a list of trusted peers by their IP prefixes.
// These peers are considered trustworthy by the gRPC server operator,
// and the middleware will attempt to extract the real IP address from
// request headers if the peer's address falls within one of these
// trusted IP prefixes.
TrustedPeers []netip.Prefix
// AccessLogRetentionDays specifies the number of days to retain access logs.
// Logs older than this duration will be automatically deleted during cleanup.
// A value of 0 will default to 7 days. Negative means logs are kept indefinitely (no cleanup).
AccessLogRetentionDays int
// AccessLogCleanupIntervalHours specifies how often (in hours) to run the cleanup routine.
// Defaults to 24 hours if not set or set to 0.
AccessLogCleanupIntervalHours int
}