mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-17 12:19:07 +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:
@@ -148,7 +148,7 @@ Debug.RevealFile(path: string): Promise<void> // OS file-manager focus
|
||||
|
||||
**Log level case sensitivity bug:** `proto.LogLevel_value` is keyed on uppercase enum names (`"TRACE"`, `"DEBUG"`, `"INFO"`, `"WARN"`, `"ERROR"`, `"PANIC"`, `"FATAL"`, `"UNKNOWN"`). `Debug.SetLogLevel` calls `proto.LogLevel_value[lvl.Level]` and falls back to `INFO` on miss. `useDebugBundle` currently passes `"trace"` (lowercase), which silently maps to `INFO` — the trace-capture flow doesn't actually raise the log level today. To raise to trace, pass `{ level: "TRACE" }`. Fix on the cleanup list.
|
||||
|
||||
`Debug.Bundle` uploads when `uploadUrl != ""`. Result fields: `path` (local copy), `uploadedKey` (set on success), `uploadFailureReason` (set on upload failure — the local copy is still saved).
|
||||
`Debug.Bundle` uploads when `upload` is true; the daemon picks the destination from what the management server publishes, so the UI never names one. Result fields: `path` (local copy), `uploadedKey` (set on success), `uploadFailureReason` (set on upload failure, including a deployment that publishes no upload service — the local copy is still saved).
|
||||
|
||||
## `Update`
|
||||
|
||||
@@ -283,7 +283,7 @@ The tray also reads a tray-only synthetic `"Error"` for icon purposes; the front
|
||||
|
||||
`UpParams` / `LogoutParams` / `ProfileRef` / `ConfigParams` / `ActiveProfile`: all `{ profileName, username: string }` (different names but same shape — kept distinct by Wails for clarity).
|
||||
|
||||
`DebugBundleParams`: `{ anonymize, systemInfo: boolean; uploadUrl: string; logFileCount: number }`.
|
||||
`DebugBundleParams`: `{ anonymize, systemInfo, upload: boolean; logFileCount: number }`.
|
||||
|
||||
`DebugBundleResult`: `{ path, uploadedKey, uploadFailureReason: string }`.
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import i18next from "@/lib/i18n";
|
||||
import { errorDialog, formatErrorMessage } from "@/lib/errors.ts";
|
||||
import { startConnection } from "@/lib/connection.ts";
|
||||
|
||||
const NETBIRD_UPLOAD_URL = "https://upload.debug.netbird.io/upload-url";
|
||||
const TRACE_LOG_FILE_COUNT = 5;
|
||||
const PLAIN_LOG_FILE_COUNT = 1;
|
||||
const TRACE_LOG_LEVEL = "trace";
|
||||
@@ -70,7 +69,12 @@ type BundleOptions = {
|
||||
capturePackets: boolean;
|
||||
hasWindow: boolean;
|
||||
totalSec: number;
|
||||
uploadUrl: string;
|
||||
// Whether to upload at all. The destination is the daemon's to pick: it
|
||||
// takes the one the management server publishes, and only falls back to the
|
||||
// service NetBird runs for a peer enrolled with NetBird's cloud. The UI must
|
||||
// not name a vendor endpoint of its own, or a self-hosted deployment would
|
||||
// ship its bundles out of the operator's control sphere.
|
||||
upload: boolean;
|
||||
anonymizeLevel: AnonymizeLevel;
|
||||
systemInfo: boolean;
|
||||
};
|
||||
@@ -187,19 +191,19 @@ const runBundleFlow = async (
|
||||
setStage({ kind: "bundling" });
|
||||
const logFileCount = opts.trace ? TRACE_LOG_FILE_COUNT : PLAIN_LOG_FILE_COUNT;
|
||||
|
||||
if (opts.uploadUrl) setStage({ kind: "uploading" });
|
||||
if (opts.upload) setStage({ kind: "uploading" });
|
||||
const result = await DebugSvc.Bundle({
|
||||
anonymize: opts.anonymizeLevel !== "none",
|
||||
// The daemon only knows "default" and "strict"; "none" is expressed
|
||||
// through the anonymize flag being off.
|
||||
anonymizeLevel: opts.anonymizeLevel === "strict" ? "strict" : "default",
|
||||
systemInfo: opts.systemInfo,
|
||||
uploadUrl: opts.uploadUrl,
|
||||
upload: opts.upload,
|
||||
logFileCount,
|
||||
});
|
||||
throwIfAborted(signal);
|
||||
if (result.path) setLastBundlePath(result.path);
|
||||
setStage({ kind: "done", result, uploadAttempted: Boolean(opts.uploadUrl) });
|
||||
setStage({ kind: "done", result, uploadAttempted: opts.upload });
|
||||
};
|
||||
|
||||
const useDebugBundle = () => {
|
||||
@@ -244,7 +248,7 @@ const useDebugBundle = () => {
|
||||
capturePackets,
|
||||
hasWindow: capture && totalSec > 0,
|
||||
totalSec,
|
||||
uploadUrl: upload ? NETBIRD_UPLOAD_URL : "",
|
||||
upload,
|
||||
anonymizeLevel,
|
||||
systemInfo,
|
||||
};
|
||||
|
||||
@@ -20,8 +20,12 @@ type DebugBundleParams struct {
|
||||
// private IP ranges, peer names, and WireGuard public keys.
|
||||
AnonymizeLevel string `json:"anonymizeLevel"`
|
||||
SystemInfo bool `json:"systemInfo"`
|
||||
UploadURL string `json:"uploadUrl"`
|
||||
LogFileCount uint32 `json:"logFileCount"`
|
||||
// Upload asks the daemon to upload the bundle. The UI carries no
|
||||
// destination of its own: the daemon resolves it from what the management
|
||||
// server publishes, so a self-hosted deployment's bundles do not leave the
|
||||
// operator's control sphere.
|
||||
Upload bool `json:"upload"`
|
||||
LogFileCount uint32 `json:"logFileCount"`
|
||||
}
|
||||
|
||||
// DebugBundleResult: Path is set for local-only bundles, UploadedKey on upload
|
||||
@@ -54,7 +58,7 @@ func (s *Debug) Bundle(ctx context.Context, p DebugBundleParams) (DebugBundleRes
|
||||
Anonymize: p.Anonymize,
|
||||
AnonymizeLevel: p.AnonymizeLevel,
|
||||
SystemInfo: p.SystemInfo,
|
||||
UploadURL: p.UploadURL,
|
||||
Upload: p.Upload,
|
||||
LogFileCount: p.LogFileCount,
|
||||
CliVersion: version.NetbirdVersion(),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user