mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-05 04:59:06 +02:00
[management,client] Take the debug-bundle upload destination from management
The debug-bundle paths that upload without a human picking a destination compiled the vendor endpoint in: the mobile clients and the desktop UI hold `https://upload.debug.netbird.io/upload-url` as a constant, the CLI defaults its flag to it, and the remote job falls back to it when nothing else is set. A self-hosted deployment therefore shipped peer logs, routes, DNS and firewall state to NetBird-run infrastructure without its operator ever configuring that, and had no way to point those paths anywhere else. #7147 and #7153 gave the remote job a per-job URL and an MDM override, but neither reaches the mobile, UI or CLI paths, and both fail open when unset. Publish the destination from the management server instead, on the channel that already carries stun/turn/signal/relay/flow/metrics: - `NetbirdConfig.debug.upload_url`, sourced from the new account setting `debug_bundle_upload_url` (REST + dashboard) and falling back to the new `DebugUpload.URL` in the management server config, which a self-hosted install can set once so a fresh account is not left on the vendor default. Both are validated as https-with-host where they are written; a change fans out to connected peers rather than waiting for the next login. - One resolver on the client, `debug.ResolveUploadURL`, used by every path: MDM override > explicitly named URL > destination published by management > the NetBird service, but only for a peer enrolled with NetBird's cloud. Anything else fails closed with ErrNoUploadDestination and the bundle stays local, which is the behaviour change: a self-hosted deployment that names no upload service no longer uploads at all. - The engine keeps the published value (`Engine.DebugUploadURL`) so the bundle paths, which run off the engine loop, do not have to read it back out of the opt-in sync-response store. - The daemon request grows `upload`, so "upload to wherever this deployment says" is expressible; an empty `uploadURL` no longer has to mean "no upload". The privilege gate is unchanged and still applies only to a URL the local caller named — a destination published by management is the operator naming their own service. - The desktop UI stops carrying a vendor URL of its own and sends the intent. Reported privately as GHSA-hf99-43rj-h577.
This commit is contained in:
@@ -184,6 +184,10 @@ func LoadMgmtConfig(ctx context.Context, mgmtConfigPath string) (*nbconfig.Confi
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := loadedConfig.DebugUpload.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for account, version := range loadedConfig.PerAccountHighestSupportedSyncMessageVersion {
|
||||
err := grpc.ValidateSyncMessageVersion(&version)
|
||||
if err != nil {
|
||||
|
||||
@@ -199,6 +199,7 @@ func accountSettings(s *nmdata.AccountSettingsInfo) *types.Settings {
|
||||
AutoUpdateVersion: s.AutoUpdateVersion,
|
||||
AutoUpdateAlways: s.AutoUpdateAlways,
|
||||
MetricsPushEnabled: s.MetricsPushEnabled,
|
||||
DebugBundleUploadURL: s.DebugBundleUploadURL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ const (
|
||||
settings_lazy_connection_enabled as lazy_connection_enabled,
|
||||
settings_auto_update_version as auto_update_version,
|
||||
settings_auto_update_always as auto_update_always,
|
||||
settings_metrics_push_enabled as metrics_push_enabled
|
||||
settings_metrics_push_enabled as metrics_push_enabled,
|
||||
settings_debug_bundle_upload_url as debug_bundle_upload_url
|
||||
from accounts
|
||||
where id=$1
|
||||
`
|
||||
@@ -50,6 +51,7 @@ func (pgc *PgStoreConn) GetAccountSettings(ctx context.Context, accountId string
|
||||
AutoUpdateVersion: settings.AutoUpdateVersion.String,
|
||||
AutoUpdateAlways: settings.AutoUpdateAlways.Bool,
|
||||
MetricsPushEnabled: settings.MetricsPushEnabled.Bool,
|
||||
DebugBundleUploadURL: settings.DebugBundleUploadURL.String,
|
||||
}
|
||||
if settings.IPv6EnabledGroups != nil {
|
||||
if err := json.Unmarshal(settings.IPv6EnabledGroups, &settingsInfo.IPv6EnabledGroups); err != nil {
|
||||
|
||||
@@ -68,6 +68,7 @@ type Account struct {
|
||||
AutoUpdateVersion sql.NullString
|
||||
AutoUpdateAlways sql.NullBool
|
||||
MetricsPushEnabled sql.NullBool
|
||||
DebugBundleUploadURL sql.NullString
|
||||
}
|
||||
|
||||
type Domain struct {
|
||||
|
||||
@@ -20,7 +20,8 @@ const (
|
||||
settings_lazy_connection_enabled as lazy_connection_enabled,
|
||||
settings_auto_update_version as auto_update_version,
|
||||
settings_auto_update_always as auto_update_always,
|
||||
settings_metrics_push_enabled as metrics_push_enabled
|
||||
settings_metrics_push_enabled as metrics_push_enabled,
|
||||
settings_debug_bundle_upload_url as debug_bundle_upload_url
|
||||
from accounts
|
||||
where id=?
|
||||
`
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
|
||||
"github.com/netbirdio/netbird/management/server/idp"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
@@ -57,6 +60,10 @@ type Config struct {
|
||||
|
||||
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
|
||||
|
||||
@@ -206,6 +213,48 @@ type AgentNetwork struct {
|
||||
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 a self-hosted deployment keeps its
|
||||
// bundles, which carry peer logs, routes, DNS and firewall state, inside the
|
||||
// operator's own control sphere instead of reaching the upload service NetBird
|
||||
// runs. Leaving it unset publishes no destination: a peer enrolled with
|
||||
// NetBird's cloud still uses NetBird's service, a self-hosted peer keeps the
|
||||
// bundle local.
|
||||
//
|
||||
// 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.
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestDebugUploadValidate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
url string
|
||||
wantErr string
|
||||
}{
|
||||
{name: "unset publishes no destination", url: ""},
|
||||
{name: "https accepted", url: "https://upload.example.com/upload-url"},
|
||||
{name: "https with port accepted", url: "https://upload.example.com:8443/upload-url"},
|
||||
// The client fetches an upload URL from this endpoint and then PUTs the
|
||||
// bundle to whatever comes back, so a plaintext hop intercepts both.
|
||||
{name: "http refused", url: "http://upload.example.com/upload-url", wantErr: "must use https"},
|
||||
{name: "scheme-less refused", url: "upload.example.com/upload-url", wantErr: "must use https"},
|
||||
{name: "host-less refused", url: "https:///upload-url", wantErr: "must have a host"},
|
||||
{name: "unparsable refused", url: "https://upload.example.com:port", wantErr: "parse debug upload URL"},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := DebugUpload{URL: tc.url}.Validate()
|
||||
if tc.wantErr == "" {
|
||||
require.NoError(t, err)
|
||||
return
|
||||
}
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), tc.wantErr)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -115,6 +115,17 @@ func toNetbirdConfig(config *nbconfig.Config, turnCredentials *Token, relayToken
|
||||
}
|
||||
}
|
||||
|
||||
// The account setting wins, the server config is the deployment-wide default
|
||||
// a self-hosted install can set once so a fresh account is not left with the
|
||||
// vendor fallback. Both are https-validated where they are written.
|
||||
debugUploadURL := config.DebugUpload.URL
|
||||
if settings != nil && settings.DebugBundleUploadURL != "" {
|
||||
debugUploadURL = settings.DebugBundleUploadURL
|
||||
}
|
||||
if debugUploadURL != "" {
|
||||
nbConfig.Debug = &proto.DebugConfig{UploadUrl: debugUploadURL}
|
||||
}
|
||||
|
||||
return nbConfig
|
||||
}
|
||||
|
||||
|
||||
@@ -363,7 +363,8 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
oldSettings.AutoUpdateAlways != newSettings.AutoUpdateAlways ||
|
||||
oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled ||
|
||||
oldSettings.PeerLoginExpiration != newSettings.PeerLoginExpiration ||
|
||||
oldSettings.MetricsPushEnabled != newSettings.MetricsPushEnabled {
|
||||
oldSettings.MetricsPushEnabled != newSettings.MetricsPushEnabled ||
|
||||
oldSettings.DebugBundleUploadURL != newSettings.DebugBundleUploadURL {
|
||||
// Session deadline is derived from LastLogin + PeerLoginExpiration
|
||||
// on every Login/Sync response. Without a fan-out push, connected
|
||||
// peers keep the deadline they received at login time and only see
|
||||
@@ -415,6 +416,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
am.handleAutoUpdateAlwaysSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
am.handlePeerExposeSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
am.handleMetricsPushSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
am.handleDebugBundleUploadURLSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
if err = am.handleInactivityExpirationSettings(ctx, oldSettings, newSettings, userID, accountID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -579,6 +581,17 @@ func (am *DefaultAccountManager) handleMetricsPushSettings(ctx context.Context,
|
||||
}
|
||||
}
|
||||
|
||||
// handleDebugBundleUploadURLSettings records a change of debug-bundle
|
||||
// destination. The value decides whose infrastructure the account's peer logs,
|
||||
// routes and firewall state land on, so a change is worth an audit entry even
|
||||
// though it is not a permission change. The URL itself is not recorded: it is
|
||||
// operator-supplied free text that can carry a host or a token.
|
||||
func (am *DefaultAccountManager) handleDebugBundleUploadURLSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) {
|
||||
if oldSettings.DebugBundleUploadURL != newSettings.DebugBundleUploadURL {
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountDebugBundleUploadURLUpdated, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) handlePeerLoginExpirationSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) {
|
||||
if oldSettings.PeerLoginExpirationEnabled != newSettings.PeerLoginExpirationEnabled {
|
||||
event := activity.AccountPeerLoginExpirationEnabled
|
||||
|
||||
@@ -284,6 +284,10 @@ const (
|
||||
// AgentNetworkSettingsDeleted indicates that a user deleted the Agent Network account settings, releasing the endpoint
|
||||
AgentNetworkSettingsDeleted Activity = 142
|
||||
|
||||
// AccountDebugBundleUploadURLUpdated indicates that a user changed where the
|
||||
// account's peers upload their debug bundles
|
||||
AccountDebugBundleUploadURLUpdated Activity = 143
|
||||
|
||||
AccountDeleted Activity = 99999
|
||||
)
|
||||
|
||||
@@ -455,8 +459,9 @@ var activityMap = map[Activity]Code{
|
||||
AgentNetworkBudgetRuleUpdated: {"Agent Network budget rule updated", "agent_network.budget_rule.update"},
|
||||
AgentNetworkBudgetRuleDeleted: {"Agent Network budget rule deleted", "agent_network.budget_rule.delete"},
|
||||
|
||||
AgentNetworkSettingsUpdated: {"Agent Network settings updated", "agent_network.settings.update"},
|
||||
AgentNetworkSettingsDeleted: {"Agent Network settings deleted", "agent_network.settings.delete"},
|
||||
AgentNetworkSettingsUpdated: {"Agent Network settings updated", "agent_network.settings.update"},
|
||||
AgentNetworkSettingsDeleted: {"Agent Network settings deleted", "agent_network.settings.delete"},
|
||||
AccountDebugBundleUploadURLUpdated: {"Account debug bundle upload URL updated", "account.setting.debug.upload.url.update"},
|
||||
|
||||
AccountMetricsPushEnabled: {"Account metrics push enabled", "account.setting.metrics.push.enable"},
|
||||
AccountMetricsPushDisabled: {"Account metrics push disabled", "account.setting.metrics.push.disable"},
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
goversion "github.com/hashicorp/go-version"
|
||||
|
||||
nbconfig "github.com/netbirdio/netbird/management/internals/server/config"
|
||||
"github.com/netbirdio/netbird/management/server/account"
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/settings"
|
||||
@@ -286,6 +287,14 @@ func (h *handler) updateAccountRequestSettings(req api.PutApiAccountsAccountIdJS
|
||||
if req.Settings.MetricsPushEnabled != nil {
|
||||
returnSettings.MetricsPushEnabled = *req.Settings.MetricsPushEnabled
|
||||
}
|
||||
if req.Settings.DebugBundleUploadUrl != nil {
|
||||
// Same rule the management server config and the peers apply, so a
|
||||
// destination accepted here cannot be one the peers then refuse.
|
||||
if err := (nbconfig.DebugUpload{URL: *req.Settings.DebugBundleUploadUrl}).Validate(); err != nil {
|
||||
return nil, status.Errorf(status.InvalidArgument, "invalid debug bundle upload URL: %v", err)
|
||||
}
|
||||
returnSettings.DebugBundleUploadURL = *req.Settings.DebugBundleUploadUrl
|
||||
}
|
||||
if req.Settings.AgentNetworkOnly != nil {
|
||||
returnSettings.AgentNetworkOnly = *req.Settings.AgentNetworkOnly
|
||||
}
|
||||
@@ -432,6 +441,7 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
|
||||
AutoUpdateAlways: &settings.AutoUpdateAlways,
|
||||
Ipv6EnabledGroups: &settings.IPv6EnabledGroups,
|
||||
MetricsPushEnabled: &settings.MetricsPushEnabled,
|
||||
DebugBundleUploadUrl: &settings.DebugBundleUploadURL,
|
||||
AgentNetworkOnly: &settings.AgentNetworkOnly,
|
||||
EmbeddedIdpEnabled: &settings.EmbeddedIdpEnabled,
|
||||
LocalAuthDisabled: &settings.LocalAuthDisabled,
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/mock/gomock"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/mock/gomock"
|
||||
|
||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||
"github.com/netbirdio/netbird/management/server/mock_server"
|
||||
@@ -127,6 +127,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -156,6 +157,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -185,6 +187,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr("latest"),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -214,6 +217,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -243,6 +247,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -272,6 +277,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -301,6 +307,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -315,6 +322,48 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
expectedArray: false,
|
||||
expectedID: accountID,
|
||||
},
|
||||
{
|
||||
name: "PutAccount OK setting debug_bundle_upload_url",
|
||||
expectedBody: true,
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/accounts/" + accountID,
|
||||
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true,\"debug_bundle_upload_url\": \"https://upload.example.com/upload-url\"},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
|
||||
expectedStatus: http.StatusOK,
|
||||
expectedSettings: api.AccountSettings{
|
||||
PeerLoginExpiration: 15552000,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
GroupsPropagationEnabled: br(false),
|
||||
JwtGroupsClaimName: sr(""),
|
||||
JwtGroupsEnabled: br(false),
|
||||
JwtAllowGroups: &[]string{},
|
||||
RegularUsersViewBlocked: false,
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr("https://upload.example.com/upload-url"),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
AgentNetworkOnly: br(false),
|
||||
EmbeddedIdpEnabled: br(false),
|
||||
LocalAuthDisabled: br(false),
|
||||
LocalMfaEnabled: br(false),
|
||||
},
|
||||
expectedArray: false,
|
||||
expectedID: accountID,
|
||||
},
|
||||
{
|
||||
// The peers fetch an upload URL from this endpoint and then PUT the
|
||||
// bundle to whatever comes back, so a plaintext destination must not
|
||||
// be storable at all.
|
||||
name: "PutAccount fails on a plaintext debug_bundle_upload_url",
|
||||
expectedBody: true,
|
||||
requestType: http.MethodPut,
|
||||
requestPath: "/api/accounts/" + accountID,
|
||||
requestBody: bytes.NewBufferString("{\"settings\": {\"peer_login_expiration\": 15552000,\"peer_login_expiration_enabled\": true,\"debug_bundle_upload_url\": \"http://upload.example.com/upload-url\"},\"onboarding\": {\"onboarding_flow_pending\": true,\"signup_form_pending\": true}}"),
|
||||
expectedStatus: http.StatusUnprocessableEntity,
|
||||
expectedArray: false,
|
||||
},
|
||||
{
|
||||
name: "PutAccount fails enabling agent_network_only without dashboard_features",
|
||||
expectedBody: true,
|
||||
@@ -342,6 +391,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
@@ -374,6 +424,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
DebugBundleUploadUrl: sr(""),
|
||||
AutoUpdateAlways: br(false),
|
||||
AutoUpdateVersion: sr(""),
|
||||
MetricsPushEnabled: br(false),
|
||||
|
||||
@@ -1652,7 +1652,8 @@ func (s *SqlStore) getAccount(ctx context.Context, accountID string) (*types.Acc
|
||||
settings_jwt_groups_enabled, settings_jwt_groups_claim_name, settings_jwt_allow_groups,
|
||||
settings_routing_peer_dns_resolution_enabled, settings_dns_domain, settings_network_range,
|
||||
settings_network_range_v6, settings_ipv6_enabled_groups, settings_lazy_connection_enabled,
|
||||
settings_local_mfa_enabled, settings_metrics_push_enabled, settings_agent_network_only,
|
||||
settings_local_mfa_enabled, settings_metrics_push_enabled, settings_debug_bundle_upload_url,
|
||||
settings_agent_network_only,
|
||||
settings_dashboard_features, settings_auto_update_version, settings_auto_update_always,
|
||||
settings_peer_expose_enabled, settings_peer_expose_groups,
|
||||
-- Embedded ExtraSettings
|
||||
@@ -1678,6 +1679,7 @@ func (s *SqlStore) getAccount(ctx context.Context, accountID string) (*types.Acc
|
||||
sLazyConnectionEnabled sql.NullBool
|
||||
sLocalMFAEnabled sql.NullBool
|
||||
sMetricsPushEnabled sql.NullBool
|
||||
sDebugBundleUploadURL sql.NullString
|
||||
sAgentNetworkOnly sql.NullBool
|
||||
sDashboardFeatures sql.NullString
|
||||
autoUpdateVersion sql.NullString
|
||||
@@ -1706,7 +1708,7 @@ func (s *SqlStore) getAccount(ctx context.Context, accountID string) (*types.Acc
|
||||
&sJWTGroupsEnabled, &sJWTGroupsClaimName, &sJWTAllowGroups,
|
||||
&sRoutingPeerDNSResolutionEnabled, &sDNSDomain, &sNetworkRange,
|
||||
&sNetworkRangeV6, &sIPv6EnabledGroups, &sLazyConnectionEnabled,
|
||||
&sLocalMFAEnabled, &sMetricsPushEnabled, &sAgentNetworkOnly,
|
||||
&sLocalMFAEnabled, &sMetricsPushEnabled, &sDebugBundleUploadURL, &sAgentNetworkOnly,
|
||||
&sDashboardFeatures, &autoUpdateVersion, &autoUpdateAlways,
|
||||
&peerExposeEnabled, &peerExposeGroups,
|
||||
&sExtraPeerApprovalEnabled, &sExtraUserApprovalRequired,
|
||||
@@ -1777,6 +1779,9 @@ func (s *SqlStore) getAccount(ctx context.Context, accountID string) (*types.Acc
|
||||
if sMetricsPushEnabled.Valid {
|
||||
account.Settings.MetricsPushEnabled = sMetricsPushEnabled.Bool
|
||||
}
|
||||
if sDebugBundleUploadURL.Valid {
|
||||
account.Settings.DebugBundleUploadURL = sDebugBundleUploadURL.String
|
||||
}
|
||||
if sAgentNetworkOnly.Valid {
|
||||
account.Settings.AgentNetworkOnly = sAgentNetworkOnly.Bool
|
||||
}
|
||||
|
||||
@@ -582,6 +582,7 @@ func TwinAccountSettings(s *Settings) *nmdata.AccountSettingsInfo {
|
||||
AutoUpdateVersion: s.AutoUpdateVersion,
|
||||
AutoUpdateAlways: s.AutoUpdateAlways,
|
||||
MetricsPushEnabled: s.MetricsPushEnabled,
|
||||
DebugBundleUploadURL: s.DebugBundleUploadURL,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,15 @@ type Settings struct {
|
||||
// MetricsPushEnabled globally enables or disables client metrics push for the account
|
||||
MetricsPushEnabled bool `gorm:"default:false"`
|
||||
|
||||
// DebugBundleUploadURL is the debug-bundle upload service the peers of this
|
||||
// account send their bundles to. A bundle carries peer logs, routes, DNS and
|
||||
// firewall state, so the destination decides whose infrastructure that data
|
||||
// lands on; setting it keeps a self-hosted account's bundles inside its own
|
||||
// control sphere. Empty falls back to the deployment-wide DebugUpload.URL
|
||||
// from the management server config, and with neither only a peer enrolled
|
||||
// with NetBird's cloud uploads at all. Must be an https URL with a host.
|
||||
DebugBundleUploadURL string
|
||||
|
||||
// AgentNetworkOnly limits the dashboard to the Agent Network surface for this account.
|
||||
// Set for accounts created via netbird.ai signups; users can disable it later.
|
||||
AgentNetworkOnly bool `gorm:"default:false"`
|
||||
@@ -123,6 +132,7 @@ func (s *Settings) Copy() *Settings {
|
||||
AutoUpdateAlways: s.AutoUpdateAlways,
|
||||
IPv6EnabledGroups: slices.Clone(s.IPv6EnabledGroups),
|
||||
MetricsPushEnabled: s.MetricsPushEnabled,
|
||||
DebugBundleUploadURL: s.DebugBundleUploadURL,
|
||||
AgentNetworkOnly: s.AgentNetworkOnly,
|
||||
EmbeddedIdpEnabled: s.EmbeddedIdpEnabled,
|
||||
LocalAuthDisabled: s.LocalAuthDisabled,
|
||||
|
||||
Reference in New Issue
Block a user