mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-27 11:09:54 +00:00
Compare commits
6 Commits
main
...
feat/dev_v
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a23722f459 | ||
|
|
496c459870 | ||
|
|
0546c55b1a | ||
|
|
c820a3a7f3 | ||
|
|
461f1cd96a | ||
|
|
67e4a13713 |
@@ -11,7 +11,7 @@ import (
|
|||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ func startManagement(t *testing.T, config *config.Config, testFile string) (*grp
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
iv, _ := validator.NewIntegratedValidator(ctx, peersmanager, settingsManagerMock, eventStore, cacheStore)
|
iv, _ := integrations.NewIntegratedValidator(ctx, peersmanager, settingsManagerMock, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -12,7 +12,13 @@ var (
|
|||||||
Short: "Print the NetBird's client application version",
|
Short: "Print the NetBird's client application version",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
cmd.SetOut(cmd.OutOrStdout())
|
cmd.SetOut(cmd.OutOrStdout())
|
||||||
cmd.Println(version.NetbirdVersion())
|
out := version.NetbirdVersion()
|
||||||
|
if version.IsDevelopmentVersion(out) {
|
||||||
|
if commit := version.NetbirdCommit(); commit != "" {
|
||||||
|
out += "-" + commit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Println(out)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
wgdevice "golang.zx2c4.com/wireguard/device"
|
|
||||||
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
wgnetstack "golang.zx2c4.com/wireguard/tun/netstack"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/iface"
|
"github.com/netbirdio/netbird/client/iface"
|
||||||
@@ -101,26 +100,6 @@ type Options struct {
|
|||||||
MTU *uint16
|
MTU *uint16
|
||||||
// DNSLabels defines additional DNS labels configured in the peer.
|
// DNSLabels defines additional DNS labels configured in the peer.
|
||||||
DNSLabels []string
|
DNSLabels []string
|
||||||
// Performance configures the tunnel's buffer pool cap and batch size.
|
|
||||||
Performance Performance
|
|
||||||
}
|
|
||||||
|
|
||||||
// Performance configures the embedded client's tunnel memory/throughput knobs.
|
|
||||||
//
|
|
||||||
// These settings are process-global: any non-nil field also becomes the
|
|
||||||
// default for Clients constructed by later embed.New calls in the same
|
|
||||||
// process. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
// PreallocatedBuffersPerPool caps the per-tunnel buffer pool. Zero
|
|
||||||
// leaves the pool unbounded. Lower values trade throughput for a
|
|
||||||
// tighter memory ceiling. May also be changed on a running Client via
|
|
||||||
// Client.SetPerformance, provided this field was nonzero at construction.
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
// MaxBatchSize overrides the number of packets the tunnel reads or
|
|
||||||
// writes per syscall, which also bounds eager buffer allocation per
|
|
||||||
// worker. Zero uses the platform default. Applied at construction
|
|
||||||
// only; ignored by Client.SetPerformance.
|
|
||||||
MaxBatchSize *uint32
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateCredentials checks that exactly one credential type is provided
|
// validateCredentials checks that exactly one credential type is provided
|
||||||
@@ -220,13 +199,6 @@ func New(opts Options) (*Client, error) {
|
|||||||
config.PrivateKey = opts.PrivateKey
|
config.PrivateKey = opts.PrivateKey
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.Performance.PreallocatedBuffersPerPool != nil {
|
|
||||||
wgdevice.SetPreallocatedBuffersPerPool(*opts.Performance.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
if opts.Performance.MaxBatchSize != nil {
|
|
||||||
wgdevice.SetMaxBatchSizeOverride(*opts.Performance.MaxBatchSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
deviceName: opts.DeviceName,
|
deviceName: opts.DeviceName,
|
||||||
setupKey: opts.SetupKey,
|
setupKey: opts.SetupKey,
|
||||||
@@ -523,25 +495,6 @@ func (c *Client) VerifySSHHostKey(peerAddress string, key []byte) error {
|
|||||||
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
return sshcommon.VerifyHostKey(storedKey, key, peerAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetPerformance retunes a running Client. Only PreallocatedBuffersPerPool
|
|
||||||
// takes effect, and only when it was nonzero at construction;
|
|
||||||
// MaxBatchSize is construction-only and returns an error if set here.
|
|
||||||
//
|
|
||||||
// Returns ErrClientNotStarted / ErrEngineNotStarted if the Client is not
|
|
||||||
// running yet.
|
|
||||||
func (c *Client) SetPerformance(t Performance) error {
|
|
||||||
if t.MaxBatchSize != nil {
|
|
||||||
return errors.New("MaxBatchSize is construction-only and cannot be changed at runtime")
|
|
||||||
}
|
|
||||||
engine, err := c.getEngine()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return engine.SetPerformance(internal.Performance{
|
|
||||||
PreallocatedBuffersPerPool: t.PreallocatedBuffersPerPool,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartCapture begins capturing packets on this client's tunnel device.
|
// StartCapture begins capturing packets on this client's tunnel device.
|
||||||
// Only one capture can be active at a time; starting a new one stops the previous.
|
// Only one capture can be active at a time; starting a new one stops the previous.
|
||||||
// Call StopCapture (or CaptureSession.Stop) to end it.
|
// Call StopCapture (or CaptureSession.Stop) to end it.
|
||||||
|
|||||||
@@ -1967,29 +1967,6 @@ func (e *Engine) GetClientMetrics() *metrics.ClientMetrics {
|
|||||||
return e.clientMetrics
|
return e.clientMetrics
|
||||||
}
|
}
|
||||||
|
|
||||||
// Performance bundles runtime-adjustable tunnel pool knobs.
|
|
||||||
// See Engine.SetPerformance. Nil fields are ignored.
|
|
||||||
type Performance struct {
|
|
||||||
PreallocatedBuffersPerPool *uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPerformance applies the given tuning to this engine's live Device.
|
|
||||||
func (e *Engine) SetPerformance(t Performance) error {
|
|
||||||
e.syncMsgMux.Lock()
|
|
||||||
defer e.syncMsgMux.Unlock()
|
|
||||||
if e.wgInterface == nil {
|
|
||||||
return fmt.Errorf("wg interface not initialized")
|
|
||||||
}
|
|
||||||
dev := e.wgInterface.GetWGDevice()
|
|
||||||
if dev == nil {
|
|
||||||
return fmt.Errorf("wg device not initialized")
|
|
||||||
}
|
|
||||||
if t.PreallocatedBuffersPerPool != nil {
|
|
||||||
dev.SetPreallocatedBuffersPerPool(*t.PreallocatedBuffersPerPool)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
func findIPFromInterfaceName(ifaceName string) (net.IP, error) {
|
||||||
iface, err := net.InterfaceByName(ifaceName)
|
iface, err := net.InterfaceByName(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/internal/stdnet"
|
"github.com/netbirdio/netbird/client/internal/stdnet"
|
||||||
"github.com/netbirdio/netbird/management/server/job"
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
@@ -66,8 +66,8 @@ import (
|
|||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
mgmt "github.com/netbirdio/netbird/shared/management/client"
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/shared/netiputil"
|
|
||||||
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
relayClient "github.com/netbirdio/netbird/shared/relay/client"
|
||||||
|
"github.com/netbirdio/netbird/shared/netiputil"
|
||||||
signal "github.com/netbirdio/netbird/shared/signal/client"
|
signal "github.com/netbirdio/netbird/shared/signal/client"
|
||||||
"github.com/netbirdio/netbird/shared/signal/proto"
|
"github.com/netbirdio/netbird/shared/signal/proto"
|
||||||
signalServer "github.com/netbirdio/netbird/signal/server"
|
signalServer "github.com/netbirdio/netbird/signal/server"
|
||||||
@@ -1641,7 +1641,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ia, _ := validator.NewIntegratedValidator(context.Background(), peersManager, nil, eventStore, cacheStore)
|
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, nil, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
|
||||||
|
nbversion "github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -11,7 +13,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func IsSupported(agentVersion string) bool {
|
func IsSupported(agentVersion string) bool {
|
||||||
if agentVersion == "development" {
|
if nbversion.IsDevelopmentVersion(agentVersion) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,6 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
latestVersion = "latest"
|
latestVersion = "latest"
|
||||||
// this version will be ignored
|
|
||||||
developmentVersion = "development"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var errNoUpdateState = errors.New("no update state found")
|
var errNoUpdateState = errors.New("no update state found")
|
||||||
@@ -483,7 +481,7 @@ func (m *Manager) loadAndDeleteUpdateState(ctx context.Context) (*UpdateState, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) shouldUpdate(updateVersion *v.Version, forceUpdate bool) bool {
|
func (m *Manager) shouldUpdate(updateVersion *v.Version, forceUpdate bool) bool {
|
||||||
if m.currentVersion == developmentVersion {
|
if version.IsDevelopmentVersion(m.currentVersion) {
|
||||||
log.Debugf("skipping auto-update, running development version")
|
log.Debugf("skipping auto-update, running development version")
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"go.opentelemetry.io/otel"
|
"go.opentelemetry.io/otel"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/update_channel"
|
||||||
@@ -315,7 +315,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve
|
|||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
ia, _ := validator.NewIntegratedValidator(context.Background(), peersManager, settingsManagerMock, eventStore, cacheStore)
|
ia, _ := integrations.NewIntegratedValidator(context.Background(), peersManager, settingsManagerMock, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
metrics, err := telemetry.NewDefaultAppMetrics(context.Background())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
certValidationTimeout = 5 * time.Minute
|
certValidationTimeout = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, certChain [][]byte) (bool, error) {
|
func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, certChain [][]byte) (bool, error) {
|
||||||
@@ -47,31 +46,17 @@ func (p *RDCleanPathProxy) validateCertificateWithJS(conn *proxyConnection, cert
|
|||||||
|
|
||||||
promise := conn.wsHandlers.Call("onCertificateRequest", certInfo)
|
promise := conn.wsHandlers.Call("onCertificateRequest", certInfo)
|
||||||
|
|
||||||
resultChan := make(chan bool, 1)
|
resultChan := make(chan bool)
|
||||||
errorChan := make(chan error, 1)
|
errorChan := make(chan error)
|
||||||
|
|
||||||
// Release from inside the callbacks so a post-timeout promise resolution
|
promise.Call("then", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
// does not invoke an already-released func.
|
result := args[0].Bool()
|
||||||
var thenFn, catchFn js.Func
|
resultChan <- result
|
||||||
var releaseOnce sync.Once
|
|
||||||
release := func() {
|
|
||||||
releaseOnce.Do(func() {
|
|
||||||
thenFn.Release()
|
|
||||||
catchFn.Release()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
thenFn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
||||||
defer release()
|
|
||||||
resultChan <- args[0].Bool()
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})).Call("catch", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
||||||
catchFn = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
|
|
||||||
defer release()
|
|
||||||
errorChan <- fmt.Errorf("certificate validation failed")
|
errorChan <- fmt.Errorf("certificate validation failed")
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
|
|
||||||
promise.Call("then", thenFn).Call("catch", catchFn)
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case result := <-resultChan:
|
case result := <-resultChan:
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
|
||||||
"syscall/js"
|
"syscall/js"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -58,8 +57,6 @@ type RDCleanPathProxy struct {
|
|||||||
}
|
}
|
||||||
activeConnections map[string]*proxyConnection
|
activeConnections map[string]*proxyConnection
|
||||||
destinations map[string]string
|
destinations map[string]string
|
||||||
pendingHandlers map[string]js.Func
|
|
||||||
nextID atomic.Uint64
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,15 +66,8 @@ type proxyConnection struct {
|
|||||||
rdpConn net.Conn
|
rdpConn net.Conn
|
||||||
tlsConn *tls.Conn
|
tlsConn *tls.Conn
|
||||||
wsHandlers js.Value
|
wsHandlers js.Value
|
||||||
// Go-side callbacks exposed to JS. js.FuncOf pins the Go closure in a
|
ctx context.Context
|
||||||
// global handle map and MUST be released, otherwise every connection
|
cancel context.CancelFunc
|
||||||
// leaks the Go memory the closure captures.
|
|
||||||
wsHandlerFn js.Func
|
|
||||||
onMessageFn js.Func
|
|
||||||
onCloseFn js.Func
|
|
||||||
cleanupOnce sync.Once
|
|
||||||
ctx context.Context
|
|
||||||
cancel context.CancelFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRDCleanPathProxy creates a new RDCleanPath proxy
|
// NewRDCleanPathProxy creates a new RDCleanPath proxy
|
||||||
@@ -90,11 +80,7 @@ func NewRDCleanPathProxy(client interface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateProxy creates a new proxy endpoint for the given destination.
|
// CreateProxy creates a new proxy endpoint for the given destination
|
||||||
// The registered handler fn and its destinations/pendingHandlers entries are
|
|
||||||
// only released once a connection is established and cleanupConnection runs.
|
|
||||||
// If a caller invokes CreateProxy but never connects to the returned URL,
|
|
||||||
// those entries stay pinned for the lifetime of the page.
|
|
||||||
func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
||||||
destination := net.JoinHostPort(hostname, port)
|
destination := net.JoinHostPort(hostname, port)
|
||||||
|
|
||||||
@@ -102,7 +88,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
resolve := args[0]
|
resolve := args[0]
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
proxyID := fmt.Sprintf("proxy_%d", p.nextID.Add(1))
|
proxyID := fmt.Sprintf("proxy_%d", len(p.activeConnections))
|
||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
if p.destinations == nil {
|
if p.destinations == nil {
|
||||||
@@ -114,7 +100,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
proxyURL := fmt.Sprintf("%s://%s/%s", RDCleanPathProxyScheme, RDCleanPathProxyHost, proxyID)
|
proxyURL := fmt.Sprintf("%s://%s/%s", RDCleanPathProxyScheme, RDCleanPathProxyHost, proxyID)
|
||||||
|
|
||||||
// Register the WebSocket handler for this specific proxy
|
// Register the WebSocket handler for this specific proxy
|
||||||
handlerFn := js.FuncOf(func(_ js.Value, args []js.Value) any {
|
js.Global().Set(fmt.Sprintf("handleRDCleanPathWebSocket_%s", proxyID), js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return js.ValueOf("error: requires WebSocket argument")
|
return js.ValueOf("error: requires WebSocket argument")
|
||||||
}
|
}
|
||||||
@@ -122,14 +108,7 @@ func (p *RDCleanPathProxy) CreateProxy(hostname, port string) js.Value {
|
|||||||
ws := args[0]
|
ws := args[0]
|
||||||
p.HandleWebSocketConnection(ws, proxyID)
|
p.HandleWebSocketConnection(ws, proxyID)
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
p.mu.Lock()
|
|
||||||
if p.pendingHandlers == nil {
|
|
||||||
p.pendingHandlers = make(map[string]js.Func)
|
|
||||||
}
|
|
||||||
p.pendingHandlers[proxyID] = handlerFn
|
|
||||||
p.mu.Unlock()
|
|
||||||
js.Global().Set(fmt.Sprintf("handleRDCleanPathWebSocket_%s", proxyID), handlerFn)
|
|
||||||
|
|
||||||
log.Infof("Created RDCleanPath proxy endpoint: %s for destination: %s", proxyURL, destination)
|
log.Infof("Created RDCleanPath proxy endpoint: %s for destination: %s", proxyURL, destination)
|
||||||
resolve.Invoke(proxyURL)
|
resolve.Invoke(proxyURL)
|
||||||
@@ -163,10 +142,6 @@ func (p *RDCleanPathProxy) HandleWebSocketConnection(ws js.Value, proxyID string
|
|||||||
|
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
p.activeConnections[proxyID] = conn
|
p.activeConnections[proxyID] = conn
|
||||||
if fn, ok := p.pendingHandlers[proxyID]; ok {
|
|
||||||
conn.wsHandlerFn = fn
|
|
||||||
delete(p.pendingHandlers, proxyID)
|
|
||||||
}
|
|
||||||
p.mu.Unlock()
|
p.mu.Unlock()
|
||||||
|
|
||||||
p.setupWebSocketHandlers(ws, conn)
|
p.setupWebSocketHandlers(ws, conn)
|
||||||
@@ -175,7 +150,7 @@ func (p *RDCleanPathProxy) HandleWebSocketConnection(ws js.Value, proxyID string
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnection) {
|
func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnection) {
|
||||||
conn.onMessageFn = js.FuncOf(func(this js.Value, args []js.Value) any {
|
ws.Set("onGoMessage", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -183,15 +158,13 @@ func (p *RDCleanPathProxy) setupWebSocketHandlers(ws js.Value, conn *proxyConnec
|
|||||||
data := args[0]
|
data := args[0]
|
||||||
go p.handleWebSocketMessage(conn, data)
|
go p.handleWebSocketMessage(conn, data)
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
ws.Set("onGoMessage", conn.onMessageFn)
|
|
||||||
|
|
||||||
conn.onCloseFn = js.FuncOf(func(_ js.Value, args []js.Value) any {
|
ws.Set("onGoClose", js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
log.Debug("WebSocket closed by JavaScript")
|
log.Debug("WebSocket closed by JavaScript")
|
||||||
conn.cancel()
|
conn.cancel()
|
||||||
return nil
|
return nil
|
||||||
})
|
}))
|
||||||
ws.Set("onGoClose", conn.onCloseFn)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) handleWebSocketMessage(conn *proxyConnection, data js.Value) {
|
func (p *RDCleanPathProxy) handleWebSocketMessage(conn *proxyConnection, data js.Value) {
|
||||||
@@ -288,49 +261,25 @@ func (p *RDCleanPathProxy) handleDirectRDP(conn *proxyConnection, firstPacket []
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) cleanupConnection(conn *proxyConnection) {
|
func (p *RDCleanPathProxy) cleanupConnection(conn *proxyConnection) {
|
||||||
conn.cleanupOnce.Do(func() {
|
log.Debugf("Cleaning up connection %s", conn.id)
|
||||||
log.Debugf("Cleaning up connection %s", conn.id)
|
conn.cancel()
|
||||||
conn.cancel()
|
if conn.tlsConn != nil {
|
||||||
if conn.tlsConn != nil {
|
log.Debug("Closing TLS connection")
|
||||||
log.Debug("Closing TLS connection")
|
if err := conn.tlsConn.Close(); err != nil {
|
||||||
if err := conn.tlsConn.Close(); err != nil {
|
log.Debugf("Error closing TLS connection: %v", err)
|
||||||
log.Debugf("Error closing TLS connection: %v", err)
|
|
||||||
}
|
|
||||||
conn.tlsConn = nil
|
|
||||||
}
|
}
|
||||||
if conn.rdpConn != nil {
|
conn.tlsConn = nil
|
||||||
log.Debug("Closing TCP connection")
|
}
|
||||||
if err := conn.rdpConn.Close(); err != nil {
|
if conn.rdpConn != nil {
|
||||||
log.Debugf("Error closing TCP connection: %v", err)
|
log.Debug("Closing TCP connection")
|
||||||
}
|
if err := conn.rdpConn.Close(); err != nil {
|
||||||
conn.rdpConn = nil
|
log.Debugf("Error closing TCP connection: %v", err)
|
||||||
}
|
}
|
||||||
js.Global().Delete(fmt.Sprintf("handleRDCleanPathWebSocket_%s", conn.id))
|
conn.rdpConn = nil
|
||||||
|
}
|
||||||
// Detach before releasing so late JS calls surface as TypeError instead
|
p.mu.Lock()
|
||||||
// of silent "call to released function".
|
delete(p.activeConnections, conn.id)
|
||||||
if conn.wsHandlers.Truthy() {
|
p.mu.Unlock()
|
||||||
conn.wsHandlers.Set("onGoMessage", js.Undefined())
|
|
||||||
conn.wsHandlers.Set("onGoClose", js.Undefined())
|
|
||||||
}
|
|
||||||
|
|
||||||
// wsHandlerFn may be zero-value if the pending handler lookup missed.
|
|
||||||
if conn.wsHandlerFn.Truthy() {
|
|
||||||
conn.wsHandlerFn.Release()
|
|
||||||
}
|
|
||||||
if conn.onMessageFn.Truthy() {
|
|
||||||
conn.onMessageFn.Release()
|
|
||||||
}
|
|
||||||
if conn.onCloseFn.Truthy() {
|
|
||||||
conn.onCloseFn.Release()
|
|
||||||
}
|
|
||||||
|
|
||||||
p.mu.Lock()
|
|
||||||
delete(p.activeConnections, conn.id)
|
|
||||||
delete(p.destinations, conn.id)
|
|
||||||
delete(p.pendingHandlers, conn.id)
|
|
||||||
p.mu.Unlock()
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *RDCleanPathProxy) sendToWebSocket(conn *proxyConnection, data []byte) {
|
func (p *RDCleanPathProxy) sendToWebSocket(conn *proxyConnection, data []byte) {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
func CreateJSInterface(client *Client) js.Value {
|
func CreateJSInterface(client *Client) js.Value {
|
||||||
jsInterface := js.Global().Get("Object").Call("create", js.Null())
|
jsInterface := js.Global().Get("Object").Call("create", js.Null())
|
||||||
|
|
||||||
writeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("write", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
return js.ValueOf(false)
|
return js.ValueOf(false)
|
||||||
}
|
}
|
||||||
@@ -32,10 +32,9 @@ func CreateJSInterface(client *Client) js.Value {
|
|||||||
|
|
||||||
_, err := client.Write(bytes)
|
_, err := client.Write(bytes)
|
||||||
return js.ValueOf(err == nil)
|
return js.ValueOf(err == nil)
|
||||||
})
|
}))
|
||||||
jsInterface.Set("write", writeFunc)
|
|
||||||
|
|
||||||
resizeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("resize", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
return js.ValueOf(false)
|
return js.ValueOf(false)
|
||||||
}
|
}
|
||||||
@@ -43,26 +42,14 @@ func CreateJSInterface(client *Client) js.Value {
|
|||||||
rows := args[1].Int()
|
rows := args[1].Int()
|
||||||
err := client.Resize(cols, rows)
|
err := client.Resize(cols, rows)
|
||||||
return js.ValueOf(err == nil)
|
return js.ValueOf(err == nil)
|
||||||
})
|
}))
|
||||||
jsInterface.Set("resize", resizeFunc)
|
|
||||||
|
|
||||||
closeFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
|
jsInterface.Set("close", js.FuncOf(func(this js.Value, args []js.Value) any {
|
||||||
client.Close()
|
client.Close()
|
||||||
return js.Undefined()
|
return js.Undefined()
|
||||||
})
|
}))
|
||||||
jsInterface.Set("close", closeFunc)
|
|
||||||
|
|
||||||
go func() {
|
go readLoop(client, jsInterface)
|
||||||
readLoop(client, jsInterface)
|
|
||||||
// Detach before releasing so late JS calls surface as TypeError instead
|
|
||||||
// of silent "call to released function".
|
|
||||||
jsInterface.Set("write", js.Undefined())
|
|
||||||
jsInterface.Set("resize", js.Undefined())
|
|
||||||
jsInterface.Set("close", js.Undefined())
|
|
||||||
writeFunc.Release()
|
|
||||||
resizeFunc.Release()
|
|
||||||
closeFunc.Release()
|
|
||||||
}()
|
|
||||||
|
|
||||||
return jsInterface
|
return jsInterface
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -332,7 +332,7 @@ func setupServerHooks(servers *serverInstances, cfg *CombinedConfig) {
|
|||||||
log.Infof("Signal server registered on port %s", cfg.Server.ListenAddress)
|
log.Infof("Signal server registered on port %s", cfg.Server.ListenAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.SetHandlerFunc(createCombinedHandler(grpcSrv, s.APIHandler(), s.IDPHandler(), servers.relaySrv, servers.metricsServer.Meter, cfg))
|
s.SetHandlerFunc(createCombinedHandler(grpcSrv, s.APIHandler(), servers.relaySrv, servers.metricsServer.Meter, cfg))
|
||||||
if servers.relaySrv != nil {
|
if servers.relaySrv != nil {
|
||||||
log.Infof("Relay WebSocket handler added (path: /relay)")
|
log.Infof("Relay WebSocket handler added (path: /relay)")
|
||||||
}
|
}
|
||||||
@@ -521,7 +521,7 @@ func createManagementServer(cfg *CombinedConfig, mgmtConfig *nbconfig.Config) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createCombinedHandler creates an HTTP handler that multiplexes Management, Signal (via wsproxy), and Relay WebSocket traffic
|
// createCombinedHandler creates an HTTP handler that multiplexes Management, Signal (via wsproxy), and Relay WebSocket traffic
|
||||||
func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, idpHandler http.Handler, relaySrv *relayServer.Server, meter metric.Meter, cfg *CombinedConfig) http.Handler {
|
func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, relaySrv *relayServer.Server, meter metric.Meter, cfg *CombinedConfig) http.Handler {
|
||||||
wsProxy := wsproxyserver.New(grpcServer, wsproxyserver.WithOTelMeter(meter))
|
wsProxy := wsproxyserver.New(grpcServer, wsproxyserver.WithOTelMeter(meter))
|
||||||
|
|
||||||
var relayAcceptFn func(conn listener.Conn)
|
var relayAcceptFn func(conn listener.Conn)
|
||||||
@@ -556,10 +556,6 @@ func createCombinedHandler(grpcServer *grpc.Server, httpHandler http.Handler, id
|
|||||||
http.Error(w, "Relay service not enabled", http.StatusNotFound)
|
http.Error(w, "Relay service not enabled", http.StatusNotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Embedded IdP (Dex)
|
|
||||||
case idpHandler != nil && strings.HasPrefix(r.URL.Path, "/oauth2"):
|
|
||||||
idpHandler.ServeHTTP(w, r)
|
|
||||||
|
|
||||||
// Management HTTP API (default)
|
// Management HTTP API (default)
|
||||||
default:
|
default:
|
||||||
httpHandler.ServeHTTP(w, r)
|
httpHandler.ServeHTTP(w, r)
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -335,7 +335,7 @@ replace github.com/kardianos/service => github.com/netbirdio/service v0.0.0-2024
|
|||||||
|
|
||||||
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
replace github.com/getlantern/systray => github.com/netbirdio/systray v0.0.0-20231030152038-ef1ed2a27949
|
||||||
|
|
||||||
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f
|
replace golang.zx2c4.com/wireguard => github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0
|
||||||
|
|
||||||
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
replace github.com/cloudflare/circl => codeberg.org/cunicu/circl v0.0.0-20230801113412-fec58fc7b5f6
|
||||||
|
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -499,8 +499,8 @@ github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9ax
|
|||||||
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45 h1:ujgviVYmx243Ksy7NdSwrdGPSRNE3pb8kEDSpH0QuAQ=
|
||||||
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20250805121659-6b4ac470ca45/go.mod h1:5/sjFmLb8O96B5737VCqhHyGRzNFIaN/Bu7ZodXc3qQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f h1:ff2D57RBjWtyQ2wVwJOxOgXAXOe/J2lJWtSX0Bz/BRk=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0 h1:h/QnNzm7xzHPm+gajcblYUOclrW2FeNeDlUNj6tTWKQ=
|
||||||
github.com/netbirdio/wireguard-go v0.0.0-20260523085312-4b4a4e36017f/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
github.com/netbirdio/wireguard-go v0.0.0-20260107100953-33b7c9d03db0/go.mod h1:rpwXGsirqLqN2L0JDJQlwOboGHmptD5ZD6T2VmcqhTw=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
|
||||||
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
github.com/nicksnyder/go-i18n/v2 v2.5.1 h1:IxtPxYsR9Gp60cGXjfuR/llTqV8aYMsC472zD0D1vHk=
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
"github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Controller struct {
|
type Controller struct {
|
||||||
@@ -510,7 +511,7 @@ func computeForwarderPort(peers []*nbpeer.Peer, requiredVersion string) int64 {
|
|||||||
for _, peer := range peers {
|
for _, peer := range peers {
|
||||||
|
|
||||||
// Development version is always supported
|
// Development version is always supported
|
||||||
if peer.Meta.WtVersion == "development" {
|
if version.IsDevelopmentVersion(peer.Meta.WtVersion) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
peerVersion := semver.Canonical("v" + peer.Meta.WtVersion)
|
peerVersion := semver.Canonical("v" + peer.Meta.WtVersion)
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ func (p *PeersUpdateManager) SendUpdate(ctx context.Context, peerID string, upda
|
|||||||
found = true
|
found = true
|
||||||
select {
|
select {
|
||||||
case channel <- update:
|
case channel <- update:
|
||||||
log.WithContext(ctx).Tracef("update was sent to channel for peer %s", peerID)
|
log.WithContext(ctx).Debugf("update was sent to channel for peer %s", peerID)
|
||||||
default:
|
default:
|
||||||
dropped = true
|
dropped = true
|
||||||
log.WithContext(ctx).Warnf("channel for peer %s is %d full or closed", peerID, len(channel))
|
log.WithContext(ctx).Warnf("channel for peer %s is %d full or closed", peerID, len(channel))
|
||||||
|
|||||||
@@ -10,10 +10,8 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware/v2"
|
grpcMiddleware "github.com/grpc-ecosystem/go-grpc-middleware/v2"
|
||||||
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
"github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/realip"
|
||||||
"github.com/rs/cors"
|
|
||||||
"github.com/rs/xid"
|
"github.com/rs/xid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
@@ -21,6 +19,7 @@ import (
|
|||||||
"google.golang.org/grpc/keepalive"
|
"google.golang.org/grpc/keepalive"
|
||||||
|
|
||||||
cachestore "github.com/eko/gocache/lib/v4/store"
|
cachestore "github.com/eko/gocache/lib/v4/store"
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/encryption"
|
"github.com/netbirdio/netbird/encryption"
|
||||||
"github.com/netbirdio/netbird/formatter/hook"
|
"github.com/netbirdio/netbird/formatter/hook"
|
||||||
@@ -28,20 +27,16 @@ import (
|
|||||||
accesslogsmanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs/manager"
|
accesslogsmanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs/manager"
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
"github.com/netbirdio/netbird/management/server/activity"
|
||||||
activitystore "github.com/netbirdio/netbird/management/server/activity/store"
|
|
||||||
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
nbcache "github.com/netbirdio/netbird/management/server/cache"
|
||||||
nbContext "github.com/netbirdio/netbird/management/server/context"
|
nbContext "github.com/netbirdio/netbird/management/server/context"
|
||||||
nbhttp "github.com/netbirdio/netbird/management/server/http"
|
nbhttp "github.com/netbirdio/netbird/management/server/http"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
"github.com/netbirdio/netbird/management/server/idp"
|
|
||||||
"github.com/netbirdio/netbird/management/server/store"
|
"github.com/netbirdio/netbird/management/server/store"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
|
||||||
"github.com/netbirdio/netbird/util/crypt"
|
"github.com/netbirdio/netbird/util/crypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const apiPrefix = "/api"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
kaep = keepalive.EnforcementPolicy{
|
kaep = keepalive.EnforcementPolicy{
|
||||||
MinTime: 15 * time.Second,
|
MinTime: 15 * time.Second,
|
||||||
@@ -99,17 +94,12 @@ func (s *BaseServer) Store() store.Store {
|
|||||||
|
|
||||||
func (s *BaseServer) EventStore() activity.Store {
|
func (s *BaseServer) EventStore() activity.Store {
|
||||||
return Create(s, func() activity.Store {
|
return Create(s, func() activity.Store {
|
||||||
var err error
|
integrationMetrics, err := integrations.InitIntegrationMetrics(context.Background(), s.Metrics())
|
||||||
key := s.Config.DataStoreEncryptionKey
|
if err != nil {
|
||||||
if key == "" {
|
log.Fatalf("failed to initialize integration metrics: %v", err)
|
||||||
log.Debugf("generate new activity store encryption key")
|
|
||||||
key, err = crypt.GenerateKey()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to generate event store encryption key: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
eventStore, err := activitystore.NewSqlStore(context.Background(), s.Config.Datadir, key)
|
eventStore, _, err := integrations.InitEventStore(context.Background(), s.Config.Datadir, s.Config.DataStoreEncryptionKey, integrationMetrics)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to initialize event store: %v", err)
|
log.Fatalf("failed to initialize event store: %v", err)
|
||||||
}
|
}
|
||||||
@@ -120,7 +110,7 @@ func (s *BaseServer) EventStore() activity.Store {
|
|||||||
|
|
||||||
func (s *BaseServer) APIHandler() http.Handler {
|
func (s *BaseServer) APIHandler() http.Handler {
|
||||||
return Create(s, func() http.Handler {
|
return Create(s, func() http.Handler {
|
||||||
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.Router(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.PermissionsManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies, s.RateLimiter(), s.IsValidChildAccount)
|
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), s.AccountManager(), s.NetworksManager(), s.ResourcesManager(), s.RoutesManager(), s.GroupsManager(), s.GeoLocationManager(), s.AuthManager(), s.Metrics(), s.IntegratedValidator(), s.ProxyController(), s.PermissionsManager(), s.PeersManager(), s.SettingsManager(), s.ZonesManager(), s.RecordsManager(), s.NetworkMapController(), s.IdpManager(), s.ServiceManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies, s.RateLimiter())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("failed to create API handler: %v", err)
|
log.Fatalf("failed to create API handler: %v", err)
|
||||||
}
|
}
|
||||||
@@ -128,22 +118,6 @@ func (s *BaseServer) APIHandler() http.Handler {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// IDPHandler returns the HTTP handler for the embedded IdP (Dex), or nil if
|
|
||||||
// the deployment isn't using the embedded variant.
|
|
||||||
func (s *BaseServer) IDPHandler() http.Handler {
|
|
||||||
embeddedIdP, ok := s.IdpManager().(*idp.EmbeddedIdPManager)
|
|
||||||
if !ok || embeddedIdP == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return cors.AllowAll().Handler(embeddedIdP.Handler())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaseServer) Router() *mux.Router {
|
|
||||||
return Create(s, func() *mux.Router {
|
|
||||||
return mux.NewRouter().PathPrefix(apiPrefix).Subrouter()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *BaseServer) RateLimiter() *middleware.APIRateLimiter {
|
func (s *BaseServer) RateLimiter() *middleware.APIRateLimiter {
|
||||||
return Create(s, func() *middleware.APIRateLimiter {
|
return Create(s, func() *middleware.APIRateLimiter {
|
||||||
cfg, enabled := middleware.RateLimiterConfigFromEnv()
|
cfg, enabled := middleware.RateLimiterConfigFromEnv()
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server"
|
"github.com/netbirdio/netbird/management/server"
|
||||||
"github.com/netbirdio/netbird/management/server/auth"
|
"github.com/netbirdio/netbird/management/server/auth"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
"github.com/netbirdio/netbird/management/server/job"
|
"github.com/netbirdio/netbird/management/server/job"
|
||||||
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
nbjwt "github.com/netbirdio/netbird/shared/auth/jwt"
|
||||||
@@ -39,7 +38,7 @@ func (s *BaseServer) JobManager() *job.Manager {
|
|||||||
|
|
||||||
func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValidator {
|
func (s *BaseServer) IntegratedValidator() integrated_validator.IntegratedValidator {
|
||||||
return Create(s, func() integrated_validator.IntegratedValidator {
|
return Create(s, func() integrated_validator.IntegratedValidator {
|
||||||
integratedPeerValidator, err := validator.NewIntegratedValidator(
|
integratedPeerValidator, err := integrations.NewIntegratedValidator(
|
||||||
context.Background(),
|
context.Background(),
|
||||||
s.PeersManager(),
|
s.PeersManager(),
|
||||||
s.SettingsManager(),
|
s.SettingsManager(),
|
||||||
|
|||||||
@@ -57,7 +57,13 @@ func (s *BaseServer) GeoLocationManager() geolocation.Geolocation {
|
|||||||
|
|
||||||
func (s *BaseServer) PermissionsManager() permissions.Manager {
|
func (s *BaseServer) PermissionsManager() permissions.Manager {
|
||||||
return Create(s, func() permissions.Manager {
|
return Create(s, func() permissions.Manager {
|
||||||
return permissions.NewManager(s.Store())
|
manager := integrations.InitPermissionsManager(s.Store(), s.Metrics().GetMeter())
|
||||||
|
|
||||||
|
s.AfterInit(func(s *BaseServer) {
|
||||||
|
manager.SetAccountManager(s.AccountManager())
|
||||||
|
})
|
||||||
|
|
||||||
|
return manager
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -147,6 +153,7 @@ func (s *BaseServer) IdpManager() idp.Manager {
|
|||||||
return idpManager
|
return idpManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -228,7 +235,3 @@ func (s *BaseServer) ReverseProxyDomainManager() *manager.Manager {
|
|||||||
return &m
|
return &m
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseServer) IsValidChildAccount(_ context.Context, _, _, _ string) bool {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ func (s *BaseServer) Start(ctx context.Context) error {
|
|||||||
log.WithContext(srvCtx).Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
log.WithContext(srvCtx).Infof("running gRPC backward compatibility server: %s", compatListener.Addr().String())
|
||||||
}
|
}
|
||||||
|
|
||||||
rootHandler := s.handlerFunc(srvCtx, s.GRPCServer(), s.APIHandler(), s.IDPHandler(), s.Metrics().GetMeter())
|
rootHandler := s.handlerFunc(srvCtx, s.GRPCServer(), s.APIHandler(), s.Metrics().GetMeter())
|
||||||
switch {
|
switch {
|
||||||
case s.certManager != nil:
|
case s.certManager != nil:
|
||||||
// a call to certManager.Listener() always creates a new listener so we do it once
|
// a call to certManager.Listener() always creates a new listener so we do it once
|
||||||
@@ -299,7 +299,7 @@ func (s *BaseServer) SetHandlerFunc(handler http.Handler) {
|
|||||||
log.Tracef("custom handler set successfully")
|
log.Tracef("custom handler set successfully")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, httpHandler http.Handler, idpHandler http.Handler, meter metric.Meter) http.Handler {
|
func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, httpHandler http.Handler, meter metric.Meter) http.Handler {
|
||||||
// Check if a custom handler was set (for multiplexing additional services)
|
// Check if a custom handler was set (for multiplexing additional services)
|
||||||
if customHandler, ok := s.GetContainer("customHandler"); ok {
|
if customHandler, ok := s.GetContainer("customHandler"); ok {
|
||||||
if handler, ok := customHandler.(http.Handler); ok {
|
if handler, ok := customHandler.(http.Handler); ok {
|
||||||
@@ -318,8 +318,6 @@ func (s *BaseServer) handlerFunc(_ context.Context, gRPCHandler *grpc.Server, ht
|
|||||||
gRPCHandler.ServeHTTP(writer, request)
|
gRPCHandler.ServeHTTP(writer, request)
|
||||||
case request.URL.Path == wsproxy.ProxyPath+wsproxy.ManagementComponent:
|
case request.URL.Path == wsproxy.ProxyPath+wsproxy.ManagementComponent:
|
||||||
wsProxy.Handler().ServeHTTP(writer, request)
|
wsProxy.Handler().ServeHTTP(writer, request)
|
||||||
case idpHandler != nil && strings.HasPrefix(request.URL.Path, "/oauth2"):
|
|
||||||
idpHandler.ServeHTTP(writer, request)
|
|
||||||
default:
|
default:
|
||||||
httpHandler.ServeHTTP(writer, request)
|
httpHandler.ServeHTTP(writer, request)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,7 +437,7 @@ func (s *Server) handleUpdates(ctx context.Context, accountID string, peerKey wg
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
log.WithContext(ctx).Tracef("received an update for peer %s", peerKey.String())
|
log.WithContext(ctx).Debugf("received an update for peer %s", peerKey.String())
|
||||||
if debouncer.ProcessUpdate(update) {
|
if debouncer.ProcessUpdate(update) {
|
||||||
// Send immediately (first update or after quiet period)
|
// Send immediately (first update or after quiet period)
|
||||||
if err := s.sendUpdate(ctx, accountID, peerKey, peer, update, srv, streamStartTime); err != nil {
|
if err := s.sendUpdate(ctx, accountID, peerKey, peer, update, srv, streamStartTime); err != nil {
|
||||||
@@ -492,7 +492,7 @@ func (s *Server) sendUpdate(ctx context.Context, accountID string, peerKey wgtyp
|
|||||||
s.cancelPeerRoutines(ctx, accountID, peer, streamStartTime)
|
s.cancelPeerRoutines(ctx, accountID, peer, streamStartTime)
|
||||||
return status.Errorf(codes.Internal, "failed sending update message")
|
return status.Errorf(codes.Internal, "failed sending update message")
|
||||||
}
|
}
|
||||||
log.WithContext(ctx).Tracef("sent an update to peer %s", peerKey.String())
|
log.WithContext(ctx).Debugf("sent an update to peer %s", peerKey.String())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,13 +15,15 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/types"
|
"github.com/netbirdio/netbird/management/server/types"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxytoken"
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service"
|
||||||
|
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/proxytoken"
|
||||||
reverseproxymanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service/manager"
|
reverseproxymanager "github.com/netbirdio/netbird/management/internals/modules/reverseproxy/service/manager"
|
||||||
|
|
||||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||||
idpmanager "github.com/netbirdio/netbird/management/server/idp"
|
idpmanager "github.com/netbirdio/netbird/management/server/idp"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/zones"
|
"github.com/netbirdio/netbird/management/internals/modules/zones"
|
||||||
zonesManager "github.com/netbirdio/netbird/management/internals/modules/zones/manager"
|
zonesManager "github.com/netbirdio/netbird/management/internals/modules/zones/manager"
|
||||||
@@ -30,10 +32,12 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/account"
|
"github.com/netbirdio/netbird/management/server/account"
|
||||||
"github.com/netbirdio/netbird/management/server/settings"
|
"github.com/netbirdio/netbird/management/server/settings"
|
||||||
|
|
||||||
|
"github.com/netbirdio/netbird/management/server/integrations/port_forwarding"
|
||||||
"github.com/netbirdio/netbird/management/server/permissions"
|
"github.com/netbirdio/netbird/management/server/permissions"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/http/handlers/proxy"
|
"github.com/netbirdio/netbird/management/server/http/handlers/proxy"
|
||||||
|
|
||||||
|
nbpeers "github.com/netbirdio/netbird/management/internals/modules/peers"
|
||||||
"github.com/netbirdio/netbird/management/server/auth"
|
"github.com/netbirdio/netbird/management/server/auth"
|
||||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||||
nbgroups "github.com/netbirdio/netbird/management/server/groups"
|
nbgroups "github.com/netbirdio/netbird/management/server/groups"
|
||||||
@@ -52,14 +56,17 @@ import (
|
|||||||
"github.com/netbirdio/netbird/management/server/http/middleware"
|
"github.com/netbirdio/netbird/management/server/http/middleware"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
||||||
nbinstance "github.com/netbirdio/netbird/management/server/instance"
|
nbinstance "github.com/netbirdio/netbird/management/server/instance"
|
||||||
|
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
|
||||||
nbnetworks "github.com/netbirdio/netbird/management/server/networks"
|
nbnetworks "github.com/netbirdio/netbird/management/server/networks"
|
||||||
"github.com/netbirdio/netbird/management/server/networks/resources"
|
"github.com/netbirdio/netbird/management/server/networks/resources"
|
||||||
"github.com/netbirdio/netbird/management/server/networks/routers"
|
"github.com/netbirdio/netbird/management/server/networks/routers"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const apiPrefix = "/api"
|
||||||
|
|
||||||
// NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
// NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints.
|
||||||
func NewAPIHandler(ctx context.Context, router *mux.Router, accountManager account.Manager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, authManager auth.Manager, appMetrics telemetry.AppMetrics, permissionsManager permissions.Manager, settingsManager settings.Manager, zManager zones.Manager, rManager records.Manager, networkMapController network_map.Controller, idpManager idpmanager.Manager, serviceManager service.Manager, reverseProxyDomainManager *manager.Manager, reverseProxyAccessLogsManager accesslogs.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, trustedHTTPProxies []netip.Prefix, rateLimiter *middleware.APIRateLimiter, isValidChildAccount middleware.IsValidChildAccountFunc) (http.Handler, error) {
|
func NewAPIHandler(ctx context.Context, accountManager account.Manager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, authManager auth.Manager, appMetrics telemetry.AppMetrics, integratedValidator integrated_validator.IntegratedValidator, proxyController port_forwarding.Controller, permissionsManager permissions.Manager, peersManager nbpeers.Manager, settingsManager settings.Manager, zManager zones.Manager, rManager records.Manager, networkMapController network_map.Controller, idpManager idpmanager.Manager, serviceManager service.Manager, reverseProxyDomainManager *manager.Manager, reverseProxyAccessLogsManager accesslogs.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, trustedHTTPProxies []netip.Prefix, rateLimiter *middleware.APIRateLimiter) (http.Handler, error) {
|
||||||
|
|
||||||
// Register bypass paths for unauthenticated endpoints
|
// Register bypass paths for unauthenticated endpoints
|
||||||
if err := bypass.AddBypassPath("/api/instance"); err != nil {
|
if err := bypass.AddBypassPath("/api/instance"); err != nil {
|
||||||
@@ -93,16 +100,25 @@ func NewAPIHandler(ctx context.Context, router *mux.Router, accountManager accou
|
|||||||
accountManager.GetUserFromUserAuth,
|
accountManager.GetUserFromUserAuth,
|
||||||
rateLimiter,
|
rateLimiter,
|
||||||
appMetrics.GetMeter(),
|
appMetrics.GetMeter(),
|
||||||
isValidChildAccount,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
corsMiddleware := cors.AllowAll()
|
corsMiddleware := cors.AllowAll()
|
||||||
|
|
||||||
|
rootRouter := mux.NewRouter()
|
||||||
metricsMiddleware := appMetrics.HTTPMiddleware()
|
metricsMiddleware := appMetrics.HTTPMiddleware()
|
||||||
|
|
||||||
|
prefix := apiPrefix
|
||||||
|
router := rootRouter.PathPrefix(prefix).Subrouter()
|
||||||
|
|
||||||
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler)
|
router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler)
|
||||||
|
|
||||||
instanceManager, err := nbinstance.NewManager(ctx, accountManager.GetStore(), idpManager)
|
if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, integratedValidator, appMetrics.GetMeter(), permissionsManager, peersManager, proxyController, settingsManager); err != nil {
|
||||||
|
return nil, fmt.Errorf("register integrations endpoints: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if embedded IdP is enabled for instance manager
|
||||||
|
embeddedIdP, embeddedIdpEnabled := idpManager.(*idpmanager.EmbeddedIdPManager)
|
||||||
|
instanceManager, err := nbinstance.NewManager(ctx, accountManager.GetStore(), embeddedIdP)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create instance manager: %w", err)
|
return nil, fmt.Errorf("failed to create instance manager: %w", err)
|
||||||
}
|
}
|
||||||
@@ -138,5 +154,10 @@ func NewAPIHandler(ctx context.Context, router *mux.Router, accountManager accou
|
|||||||
oauthHandler.RegisterEndpoints(router)
|
oauthHandler.RegisterEndpoints(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
return router, nil
|
// Mount embedded IdP handler at /oauth2 path if configured
|
||||||
|
if embeddedIdpEnabled {
|
||||||
|
rootRouter.PathPrefix("/oauth2").Handler(corsMiddleware.Handler(embeddedIdP.Handler()))
|
||||||
|
}
|
||||||
|
|
||||||
|
return rootRouter, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import (
|
|||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"go.opentelemetry.io/otel/metric"
|
"go.opentelemetry.io/otel/metric"
|
||||||
|
|
||||||
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
|
|
||||||
serverauth "github.com/netbirdio/netbird/management/server/auth"
|
serverauth "github.com/netbirdio/netbird/management/server/auth"
|
||||||
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
nbcontext "github.com/netbirdio/netbird/management/server/context"
|
||||||
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
"github.com/netbirdio/netbird/management/server/http/middleware/bypass"
|
||||||
@@ -25,8 +27,6 @@ type SyncUserJWTGroupsFunc func(ctx context.Context, userAuth auth.UserAuth) err
|
|||||||
|
|
||||||
type GetUserFromUserAuthFunc func(ctx context.Context, userAuth auth.UserAuth) (*types.User, error)
|
type GetUserFromUserAuthFunc func(ctx context.Context, userAuth auth.UserAuth) (*types.User, error)
|
||||||
|
|
||||||
type IsValidChildAccountFunc func(ctx context.Context, userID, accountID, childAccountID string) bool
|
|
||||||
|
|
||||||
// AuthMiddleware middleware to verify personal access tokens (PAT) and JWT tokens
|
// AuthMiddleware middleware to verify personal access tokens (PAT) and JWT tokens
|
||||||
type AuthMiddleware struct {
|
type AuthMiddleware struct {
|
||||||
authManager serverauth.Manager
|
authManager serverauth.Manager
|
||||||
@@ -35,7 +35,6 @@ type AuthMiddleware struct {
|
|||||||
syncUserJWTGroups SyncUserJWTGroupsFunc
|
syncUserJWTGroups SyncUserJWTGroupsFunc
|
||||||
rateLimiter *APIRateLimiter
|
rateLimiter *APIRateLimiter
|
||||||
patUsageTracker *PATUsageTracker
|
patUsageTracker *PATUsageTracker
|
||||||
isValidChildAccount IsValidChildAccountFunc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAuthMiddleware instance constructor
|
// NewAuthMiddleware instance constructor
|
||||||
@@ -46,7 +45,6 @@ func NewAuthMiddleware(
|
|||||||
getUserFromUserAuth GetUserFromUserAuthFunc,
|
getUserFromUserAuth GetUserFromUserAuthFunc,
|
||||||
rateLimiter *APIRateLimiter,
|
rateLimiter *APIRateLimiter,
|
||||||
meter metric.Meter,
|
meter metric.Meter,
|
||||||
isValidChildAccount IsValidChildAccountFunc,
|
|
||||||
) *AuthMiddleware {
|
) *AuthMiddleware {
|
||||||
var patUsageTracker *PATUsageTracker
|
var patUsageTracker *PATUsageTracker
|
||||||
if meter != nil {
|
if meter != nil {
|
||||||
@@ -64,7 +62,6 @@ func NewAuthMiddleware(
|
|||||||
getUserFromUserAuth: getUserFromUserAuth,
|
getUserFromUserAuth: getUserFromUserAuth,
|
||||||
rateLimiter: rateLimiter,
|
rateLimiter: rateLimiter,
|
||||||
patUsageTracker: patUsageTracker,
|
patUsageTracker: patUsageTracker,
|
||||||
isValidChildAccount: isValidChildAccount,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,7 +124,7 @@ func (m *AuthMiddleware) checkJWTFromRequest(r *http.Request, authHeaderParts []
|
|||||||
}
|
}
|
||||||
|
|
||||||
if impersonate, ok := r.URL.Query()["account"]; ok && len(impersonate) == 1 {
|
if impersonate, ok := r.URL.Query()["account"]; ok && len(impersonate) == 1 {
|
||||||
if m.isValidChildAccount(ctx, userAuth.UserId, userAuth.AccountId, impersonate[0]) {
|
if integrations.IsValidChildAccount(ctx, userAuth.UserId, userAuth.AccountId, impersonate[0]) {
|
||||||
userAuth.AccountId = impersonate[0]
|
userAuth.AccountId = impersonate[0]
|
||||||
userAuth.IsChild = true
|
userAuth.IsChild = true
|
||||||
}
|
}
|
||||||
@@ -206,7 +203,7 @@ func (m *AuthMiddleware) checkPATFromRequest(r *http.Request, authHeaderParts []
|
|||||||
}
|
}
|
||||||
|
|
||||||
if impersonate, ok := r.URL.Query()["account"]; ok && len(impersonate) == 1 {
|
if impersonate, ok := r.URL.Query()["account"]; ok && len(impersonate) == 1 {
|
||||||
if m.isValidChildAccount(r.Context(), userAuth.UserId, userAuth.AccountId, impersonate[0]) {
|
if integrations.IsValidChildAccount(r.Context(), userAuth.UserId, userAuth.AccountId, impersonate[0]) {
|
||||||
userAuth.AccountId = impersonate[0]
|
userAuth.AccountId = impersonate[0]
|
||||||
userAuth.IsChild = true
|
userAuth.IsChild = true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -211,7 +211,6 @@ func TestAuthMiddleware_Handler(t *testing.T) {
|
|||||||
},
|
},
|
||||||
disabledLimiter,
|
disabledLimiter,
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handlerToTest := authMiddleware.Handler(nextHandler)
|
handlerToTest := authMiddleware.Handler(nextHandler)
|
||||||
@@ -271,7 +270,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -324,7 +322,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -368,7 +365,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -413,7 +409,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -478,7 +473,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -538,7 +532,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -594,7 +587,6 @@ func TestAuthMiddleware_RateLimiting(t *testing.T) {
|
|||||||
},
|
},
|
||||||
NewAPIRateLimiter(rateLimitConfig),
|
NewAPIRateLimiter(rateLimitConfig),
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
handler := authMiddleware.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
@@ -695,7 +687,6 @@ func TestAuthMiddleware_Handler_Child(t *testing.T) {
|
|||||||
},
|
},
|
||||||
disabledLimiter,
|
disabledLimiter,
|
||||||
nil,
|
nil,
|
||||||
func(_ context.Context, _, _, _ string) bool { return false },
|
|
||||||
)
|
)
|
||||||
|
|
||||||
for _, tc := range tt {
|
for _, tc := range tt {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/gorilla/mux"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"go.opentelemetry.io/otel/metric/noop"
|
"go.opentelemetry.io/otel/metric/noop"
|
||||||
@@ -136,8 +135,7 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee
|
|||||||
customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "")
|
customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "")
|
||||||
zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager)
|
zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager)
|
||||||
|
|
||||||
apiRouter := mux.NewRouter().PathPrefix("/api").Subrouter()
|
apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil, nil)
|
||||||
apiHandler, err := http2.NewAPIHandler(context.Background(), apiRouter, am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, permissionsManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create API handler: %v", err)
|
t.Fatalf("Failed to create API handler: %v", err)
|
||||||
}
|
}
|
||||||
@@ -266,8 +264,7 @@ func BuildApiBlackBoxWithDBStateAndPeerChannel(t testing_tools.TB, sqlFile strin
|
|||||||
customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "")
|
customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "")
|
||||||
zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager)
|
zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager)
|
||||||
|
|
||||||
apiRouter := mux.NewRouter().PathPrefix("/api").Subrouter()
|
apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil, nil)
|
||||||
apiHandler, err := http2.NewAPIHandler(context.Background(), apiRouter, am, networksManager, resourcesManager, routersManager, groupsManager, geoMock, authManagerMock, metrics, permissionsManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, serviceManager, nil, nil, nil, nil, nil, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to create API handler: %v", err)
|
t.Fatalf("Failed to create API handler: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
package validator
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
|
|
||||||
cachestore "github.com/eko/gocache/lib/v4/store"
|
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/modules/peers"
|
|
||||||
"github.com/netbirdio/netbird/management/server/activity"
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
|
||||||
"github.com/netbirdio/netbird/management/server/settings"
|
|
||||||
"github.com/netbirdio/netbird/management/server/types"
|
|
||||||
"github.com/netbirdio/netbird/shared/management/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
type IntegratedValidatorImpl struct{}
|
|
||||||
|
|
||||||
func NewIntegratedValidator(_ context.Context, _ peers.Manager, _ settings.Manager, _ activity.Store, _ cachestore.StoreInterface) (*IntegratedValidatorImpl, error) {
|
|
||||||
return &IntegratedValidatorImpl{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) ValidateExtraSettings(context.Context, *types.ExtraSettings, *types.ExtraSettings, string, string) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) ValidatePeer(_ context.Context, update *nbpeer.Peer, _ *nbpeer.Peer, _ string, _ string, _ string, _ []string, _ *types.ExtraSettings) (*nbpeer.Peer, bool, error) {
|
|
||||||
return update, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) PreparePeer(_ context.Context, _ string, peer *nbpeer.Peer, _ []string, _ *types.ExtraSettings, _ bool) *nbpeer.Peer {
|
|
||||||
return peer.Copy()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) IsNotValidPeer(_ context.Context, _ string, _ *nbpeer.Peer, _ []string, _ *types.ExtraSettings) (bool, bool, error) {
|
|
||||||
return false, false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) GetValidatedPeers(_ context.Context, _ string, _ []*types.Group, peers []*nbpeer.Peer, _ *types.ExtraSettings) (map[string]struct{}, error) {
|
|
||||||
validatedPeers := make(map[string]struct{})
|
|
||||||
for _, p := range peers {
|
|
||||||
validatedPeers[p.ID] = struct{}{}
|
|
||||||
}
|
|
||||||
return validatedPeers, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) GetInvalidPeers(_ context.Context, _ string, _ *types.ExtraSettings) (map[string]string, error) {
|
|
||||||
return make(map[string]string), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) PeerDeleted(_ context.Context, _, _ string, _ *types.ExtraSettings) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) SetPeerInvalidationListener(_ func(accountID string, peerIDs []string)) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) Stop(_ context.Context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *IntegratedValidatorImpl) ValidateFlowResponse(_ context.Context, _ string, flowResponse *proto.PKCEAuthorizationFlow) *proto.PKCEAuthorizationFlow {
|
|
||||||
return flowResponse
|
|
||||||
}
|
|
||||||
@@ -30,6 +30,7 @@ import (
|
|||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
"github.com/netbirdio/netbird/management/server/telemetry"
|
"github.com/netbirdio/netbird/management/server/telemetry"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const remoteJobsMinVer = "0.64.0"
|
const remoteJobsMinVer = "0.64.0"
|
||||||
@@ -372,7 +373,7 @@ func (am *DefaultAccountManager) CreatePeerJob(ctx context.Context, accountID, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
meetMinVer, err := posture.MeetsMinVersion(remoteJobsMinVer, p.Meta.WtVersion)
|
meetMinVer, err := posture.MeetsMinVersion(remoteJobsMinVer, p.Meta.WtVersion)
|
||||||
if !strings.Contains(p.Meta.WtVersion, "dev") && (!meetMinVer || err != nil) {
|
if !version.IsDevelopmentVersion(p.Meta.WtVersion) && (!meetMinVer || err != nil) {
|
||||||
return status.Errorf(status.PreconditionFailed, "peer version %s does not meet the minimum required version %s for remote jobs", p.Meta.WtVersion, remoteJobsMinVer)
|
return status.Errorf(status.PreconditionFailed, "peer version %s does not meet the minimum required version %s for remote jobs", p.Meta.WtVersion, remoteJobsMinVer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/hashicorp/go-version"
|
"github.com/hashicorp/go-version"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
nbpeer "github.com/netbirdio/netbird/management/server/peer"
|
||||||
)
|
)
|
||||||
@@ -32,6 +33,9 @@ func (n *NBVersionCheck) Check(ctx context.Context, peer nbpeer.Peer) (bool, err
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.WithContext(ctx).Debugf("peer %s NB version %s is older than minimum allowed version %s",
|
||||||
|
peer.ID, peer.Meta.WtVersion, n.MinVersion)
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,8 @@ func checkMinVersion(ctx context.Context, peerGoOS, peerVersion string, check *M
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.WithContext(ctx).Debugf("peer %s OS version %s is older than minimum allowed version %s", peerGoOS, peerVersion, check.MinVersion)
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,5 +125,7 @@ func checkMinKernelVersion(ctx context.Context, peerGoOS, peerVersion string, ch
|
|||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.WithContext(ctx).Debugf("peer %s kernel version %s is older than minimum allowed version %s", peerGoOS, peerVersion, check.MinKernelVersion)
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/route"
|
"github.com/netbirdio/netbird/route"
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
"github.com/netbirdio/netbird/shared/management/status"
|
"github.com/netbirdio/netbird/shared/management/status"
|
||||||
|
"github.com/netbirdio/netbird/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -1804,7 +1805,7 @@ func shouldCheckRulesForNativeSSH(supportsNative bool, rule *PolicyRule, peer *n
|
|||||||
|
|
||||||
// peerSupportedFirewallFeatures checks if the peer version supports port ranges.
|
// peerSupportedFirewallFeatures checks if the peer version supports port ranges.
|
||||||
func peerSupportedFirewallFeatures(peerVer string) supportedFeatures {
|
func peerSupportedFirewallFeatures(peerVer string) supportedFeatures {
|
||||||
if strings.Contains(peerVer, "dev") {
|
if version.IsDevelopmentVersion(peerVer) {
|
||||||
return supportedFeatures{true, true}
|
return supportedFeatures{true, true}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -646,41 +646,7 @@ func Test_ExpandPortsAndRanges_SSHRuleExpansion(t *testing.T) {
|
|||||||
expectedPorts: []string{"20-25", "10-100", "22022"},
|
expectedPorts: []string{"20-25", "10-100", "22022"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "dev suffix version supports all features",
|
name: "development version supports all features",
|
||||||
peer: &nbpeer.Peer{
|
|
||||||
ID: "peer1",
|
|
||||||
SSHEnabled: true,
|
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
|
||||||
WtVersion: "0.50.0-dev",
|
|
||||||
Flags: nbpeer.Flags{ServerSSHAllowed: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rule: &PolicyRule{
|
|
||||||
Protocol: PolicyRuleProtocolTCP,
|
|
||||||
Ports: []string{"22"},
|
|
||||||
},
|
|
||||||
base: FirewallRule{PeerIP: "10.0.0.1", Direction: 0, Action: "accept", Protocol: "tcp"},
|
|
||||||
expectedPorts: []string{"22", "22022"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "dev suffix version supports all features",
|
|
||||||
peer: &nbpeer.Peer{
|
|
||||||
ID: "peer1",
|
|
||||||
SSHEnabled: true,
|
|
||||||
Meta: nbpeer.PeerSystemMeta{
|
|
||||||
WtVersion: "dev",
|
|
||||||
Flags: nbpeer.Flags{ServerSSHAllowed: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rule: &PolicyRule{
|
|
||||||
Protocol: PolicyRuleProtocolTCP,
|
|
||||||
Ports: []string{"22"},
|
|
||||||
},
|
|
||||||
base: FirewallRule{PeerIP: "10.0.0.1", Direction: 0, Action: "accept", Protocol: "tcp"},
|
|
||||||
expectedPorts: []string{"22", "22022"},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "development suffix version supports all features",
|
|
||||||
peer: &nbpeer.Peer{
|
peer: &nbpeer.Peer{
|
||||||
ID: "peer1",
|
ID: "peer1",
|
||||||
SSHEnabled: true,
|
SSHEnabled: true,
|
||||||
|
|||||||
@@ -762,7 +762,7 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the initiator still has admin privileges
|
// Ensure the initiator still has admin privileges
|
||||||
if !freshInitiator.HasAdminPower() {
|
if initiatorUser.HasAdminPower() && !freshInitiator.HasAdminPower() {
|
||||||
return false, nil, nil, nil, status.Errorf(status.PermissionDenied, "initiator role was changed during request processing")
|
return false, nil, nil, nil, status.Errorf(status.PermissionDenied, "initiator role was changed during request processing")
|
||||||
}
|
}
|
||||||
initiatorUser = freshInitiator
|
initiatorUser = freshInitiator
|
||||||
@@ -906,23 +906,19 @@ func validateUserUpdate(groupsMap map[string]*types.Group, initiatorUser, oldUse
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !initiatorUser.HasAdminPower() {
|
|
||||||
return status.Errorf(status.PermissionDenied, "only admins and owners can update users")
|
|
||||||
}
|
|
||||||
|
|
||||||
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && oldUser.Blocked != update.Blocked {
|
||||||
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
return status.Errorf(status.PermissionDenied, "admins can't block or unblock themselves")
|
||||||
}
|
}
|
||||||
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && update.Role != initiatorUser.Role {
|
if initiatorUser.HasAdminPower() && initiatorUser.Id == update.Id && update.Role != initiatorUser.Role {
|
||||||
return status.Errorf(status.PermissionDenied, "admins can't change their role")
|
return status.Errorf(status.PermissionDenied, "admins can't change their role")
|
||||||
}
|
}
|
||||||
if initiatorUser.Role != types.UserRoleOwner && oldUser.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
if initiatorUser.Role == types.UserRoleAdmin && oldUser.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||||
return status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
|
return status.Errorf(status.PermissionDenied, "only owners can remove owner role from their user")
|
||||||
}
|
}
|
||||||
if oldUser.Role == types.UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
|
if initiatorUser.Role == types.UserRoleAdmin && oldUser.Role == types.UserRoleOwner && update.IsBlocked() && !oldUser.IsBlocked() {
|
||||||
return status.Errorf(status.PermissionDenied, "unable to block owner user")
|
return status.Errorf(status.PermissionDenied, "unable to block owner user")
|
||||||
}
|
}
|
||||||
if initiatorUser.Role != types.UserRoleOwner && update.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
if initiatorUser.Role == types.UserRoleAdmin && update.Role == types.UserRoleOwner && update.Role != oldUser.Role {
|
||||||
return status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
|
return status.Errorf(status.PermissionDenied, "only owners can add owner role to other users")
|
||||||
}
|
}
|
||||||
if oldUser.IsServiceUser && update.Role == types.UserRoleOwner {
|
if oldUser.IsServiceUser && update.Role == types.UserRoleOwner {
|
||||||
|
|||||||
@@ -109,22 +109,6 @@ var debugStopCmd = &cobra.Command{
|
|||||||
SilenceUsage: true,
|
SilenceUsage: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
var debugPerfCmd = &cobra.Command{
|
|
||||||
Use: "perf <pool-cap>",
|
|
||||||
Short: "Live-retune the tunnel buffer pool cap on all running clients",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
RunE: runDebugPerfSet,
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugRuntimeCmd = &cobra.Command{
|
|
||||||
Use: "runtime",
|
|
||||||
Short: "Show runtime stats (heap, goroutines, RSS)",
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
RunE: runDebugRuntime,
|
|
||||||
SilenceUsage: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
var debugCaptureCmd = &cobra.Command{
|
var debugCaptureCmd = &cobra.Command{
|
||||||
Use: "capture <account-id> [filter expression]",
|
Use: "capture <account-id> [filter expression]",
|
||||||
Short: "Capture packets on a client's WireGuard interface",
|
Short: "Capture packets on a client's WireGuard interface",
|
||||||
@@ -175,8 +159,6 @@ func init() {
|
|||||||
debugCmd.AddCommand(debugLogCmd)
|
debugCmd.AddCommand(debugLogCmd)
|
||||||
debugCmd.AddCommand(debugStartCmd)
|
debugCmd.AddCommand(debugStartCmd)
|
||||||
debugCmd.AddCommand(debugStopCmd)
|
debugCmd.AddCommand(debugStopCmd)
|
||||||
debugCmd.AddCommand(debugPerfCmd)
|
|
||||||
debugCmd.AddCommand(debugRuntimeCmd)
|
|
||||||
debugCmd.AddCommand(debugCaptureCmd)
|
debugCmd.AddCommand(debugCaptureCmd)
|
||||||
|
|
||||||
rootCmd.AddCommand(debugCmd)
|
rootCmd.AddCommand(debugCmd)
|
||||||
@@ -238,18 +220,6 @@ func runDebugStop(cmd *cobra.Command, args []string) error {
|
|||||||
return getDebugClient(cmd).StopClient(cmd.Context(), args[0])
|
return getDebugClient(cmd).StopClient(cmd.Context(), args[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
func runDebugPerfSet(cmd *cobra.Command, args []string) error {
|
|
||||||
n, err := strconv.ParseUint(args[0], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid value %q: %w", args[0], err)
|
|
||||||
}
|
|
||||||
return getDebugClient(cmd).PerfSet(cmd.Context(), uint32(n))
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDebugRuntime(cmd *cobra.Command, _ []string) error {
|
|
||||||
return getDebugClient(cmd).Runtime(cmd.Context())
|
|
||||||
}
|
|
||||||
|
|
||||||
func runDebugCapture(cmd *cobra.Command, args []string) error {
|
func runDebugCapture(cmd *cobra.Command, args []string) error {
|
||||||
duration, _ := cmd.Flags().GetDuration("duration")
|
duration, _ := cmd.Flags().GetDuration("duration")
|
||||||
forcePcap, _ := cmd.Flags().GetBool("pcap")
|
forcePcap, _ := cmd.Flags().GetBool("pcap")
|
||||||
|
|||||||
@@ -15,22 +15,11 @@ import (
|
|||||||
|
|
||||||
"github.com/netbirdio/netbird/shared/management/domain"
|
"github.com/netbirdio/netbird/shared/management/domain"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy"
|
"github.com/netbirdio/netbird/proxy"
|
||||||
nbacme "github.com/netbirdio/netbird/proxy/internal/acme"
|
nbacme "github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// envPreallocatedBuffers caps the per-tunnel buffer pool. Zero (unset)
|
|
||||||
// keeps the upstream uncapped default.
|
|
||||||
envPreallocatedBuffers = "NB_PROXY_PREALLOCATED_BUFFERS"
|
|
||||||
// envMaxBatchSize overrides the per-tunnel batch size, which controls
|
|
||||||
// how many buffers each receive/TUN worker eagerly allocates. Zero
|
|
||||||
// (unset) keeps the platform default.
|
|
||||||
envMaxBatchSize = "NB_PROXY_MAX_BATCH_SIZE"
|
|
||||||
)
|
|
||||||
|
|
||||||
const DefaultManagementURL = "https://api.netbird.io:443"
|
const DefaultManagementURL = "https://api.netbird.io:443"
|
||||||
|
|
||||||
// envProxyToken is the environment variable name for the proxy access token.
|
// envProxyToken is the environment variable name for the proxy access token.
|
||||||
@@ -159,45 +148,6 @@ func runServer(cmd *cobra.Command, args []string) error {
|
|||||||
|
|
||||||
logger.Infof("configured log level: %s", level)
|
logger.Infof("configured log level: %s", level)
|
||||||
|
|
||||||
var wgPool, wgBatch uint64
|
|
||||||
var perf embed.Performance
|
|
||||||
if raw := os.Getenv(envPreallocatedBuffers); raw != "" {
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s %q: %w", envPreallocatedBuffers, raw, err)
|
|
||||||
}
|
|
||||||
wgPool = n
|
|
||||||
v := uint32(n)
|
|
||||||
perf.PreallocatedBuffersPerPool = &v
|
|
||||||
logger.Infof("tunnel preallocated buffers per pool: %d", n)
|
|
||||||
}
|
|
||||||
if raw := os.Getenv(envMaxBatchSize); raw != "" {
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("invalid %s %q: %w", envMaxBatchSize, raw, err)
|
|
||||||
}
|
|
||||||
wgBatch = n
|
|
||||||
v := uint32(n)
|
|
||||||
perf.MaxBatchSize = &v
|
|
||||||
logger.Infof("tunnel max batch size override: %d", n)
|
|
||||||
}
|
|
||||||
if wgPool > 0 {
|
|
||||||
// Each bind recv goroutine (IPv4 + IPv6 + ICE relay) plus
|
|
||||||
// RoutineReadFromTUN eagerly reserves `batch` message buffers for
|
|
||||||
// the lifetime of the Device. A pool cap below that floor blocks
|
|
||||||
// the receive pipeline at startup.
|
|
||||||
batch := wgBatch
|
|
||||||
if batch == 0 {
|
|
||||||
batch = 128
|
|
||||||
}
|
|
||||||
const recvGoroutines = 4
|
|
||||||
floor := batch * recvGoroutines
|
|
||||||
if wgPool < floor {
|
|
||||||
logger.Warnf("%s=%d is below the eager-allocation floor (~%d for batch=%d); startup may deadlock",
|
|
||||||
envPreallocatedBuffers, wgPool, floor, batch)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch forwardedProto {
|
switch forwardedProto {
|
||||||
case "auto", "http", "https":
|
case "auto", "http", "https":
|
||||||
default:
|
default:
|
||||||
@@ -238,7 +188,6 @@ func runServer(cmd *cobra.Command, args []string) error {
|
|||||||
CertLockMethod: nbacme.CertLockMethod(certLockMethod),
|
CertLockMethod: nbacme.CertLockMethod(certLockMethod),
|
||||||
WildcardCertDir: wildcardCertDir,
|
WildcardCertDir: wildcardCertDir,
|
||||||
WireguardPort: wgPort,
|
WireguardPort: wgPort,
|
||||||
Performance: perf,
|
|
||||||
ProxyProtocol: proxyProtocol,
|
ProxyProtocol: proxyProtocol,
|
||||||
PreSharedKey: preSharedKey,
|
PreSharedKey: preSharedKey,
|
||||||
SupportsCustomPorts: supportsCustomPorts,
|
SupportsCustomPorts: supportsCustomPorts,
|
||||||
|
|||||||
@@ -333,63 +333,6 @@ func (c *Client) printLogLevelResult(data map[string]any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PerfSet live-retunes the tunnel buffer pool cap on all running embedded
|
|
||||||
// clients. Batch size is not live-tunable; configure it at proxy startup.
|
|
||||||
func (c *Client) PerfSet(ctx context.Context, value uint32) error {
|
|
||||||
path := fmt.Sprintf("/debug/perf?value=%d", value)
|
|
||||||
return c.fetchAndPrint(ctx, path, c.printPerfSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) printPerfSet(data map[string]any) {
|
|
||||||
if errMsg, ok := data["error"].(string); ok && errMsg != "" {
|
|
||||||
c.printError(data)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val, _ := data["value"].(float64)
|
|
||||||
applied, _ := data["applied"].(float64)
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Pool cap set to: %d\n", uint32(val))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Applied to %d live clients\n", int(applied))
|
|
||||||
if failed, ok := data["failed"].(map[string]any); ok && len(failed) > 0 {
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Failed:")
|
|
||||||
for k, v := range failed {
|
|
||||||
_, _ = fmt.Fprintf(c.out, " %s: %v\n", k, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Runtime fetches runtime stats (heap, goroutines, RSS).
|
|
||||||
func (c *Client) Runtime(ctx context.Context) error {
|
|
||||||
return c.fetchAndPrint(ctx, "/debug/runtime", c.printRuntime)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) printRuntime(data map[string]any) {
|
|
||||||
i := func(k string) uint64 {
|
|
||||||
v, _ := data[k].(float64)
|
|
||||||
return uint64(v)
|
|
||||||
}
|
|
||||||
mb := func(n uint64) string { return fmt.Sprintf("%.1f MB", float64(n)/(1<<20)) }
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Uptime: %v\n", data["uptime"])
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Go: %v on %d CPU (GOMAXPROCS=%d)\n", data["go_version"], uint32(i("num_cpu")), uint32(i("gomaxprocs")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Goroutines: %d\n", i("goroutines"))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Live objects: %d\n", i("live_objects"))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "GC: %d cycles, %v pause total\n", i("num_gc"), time.Duration(i("pause_total_ns")))
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Heap:")
|
|
||||||
_, _ = fmt.Fprintf(c.out, " alloc: %s\n", mb(i("heap_alloc")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " in-use: %s\n", mb(i("heap_inuse")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " idle: %s\n", mb(i("heap_idle")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " released: %s\n", mb(i("heap_released")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " sys: %s\n", mb(i("heap_sys")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Total sys: %s\n", mb(i("sys")))
|
|
||||||
if _, ok := data["vm_rss"]; ok {
|
|
||||||
_, _ = fmt.Fprintln(c.out, "Process:")
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmRSS: %s\n", mb(i("vm_rss")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmSize: %s\n", mb(i("vm_size")))
|
|
||||||
_, _ = fmt.Fprintf(c.out, " VmData: %s\n", mb(i("vm_data")))
|
|
||||||
}
|
|
||||||
_, _ = fmt.Fprintf(c.out, "Clients: %d (%d started)\n", i("clients"), i("started"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StartClient starts a specific client.
|
// StartClient starts a specific client.
|
||||||
func (c *Client) StartClient(ctx context.Context, accountID string) error {
|
func (c *Client) StartClient(ctx context.Context, accountID string) error {
|
||||||
path := "/debug/clients/" + url.PathEscape(accountID) + "/start"
|
path := "/debug/clients/" + url.PathEscape(accountID) + "/start"
|
||||||
|
|||||||
@@ -11,8 +11,6 @@ import (
|
|||||||
"maps"
|
"maps"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"runtime"
|
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -61,7 +59,6 @@ func sortedAccountIDs(m map[types.AccountID]roundtrip.ClientDebugInfo) []types.A
|
|||||||
type clientProvider interface {
|
type clientProvider interface {
|
||||||
GetClient(accountID types.AccountID) (*nbembed.Client, bool)
|
GetClient(accountID types.AccountID) (*nbembed.Client, bool)
|
||||||
ListClientsForDebug() map[types.AccountID]roundtrip.ClientDebugInfo
|
ListClientsForDebug() map[types.AccountID]roundtrip.ClientDebugInfo
|
||||||
ListClientsForStartup() map[types.AccountID]*nbembed.Client
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// InboundListenerInfo describes a per-account inbound listener as
|
// InboundListenerInfo describes a per-account inbound listener as
|
||||||
@@ -168,10 +165,6 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
h.handleListClients(w, r, wantJSON)
|
h.handleListClients(w, r, wantJSON)
|
||||||
case "/debug/health":
|
case "/debug/health":
|
||||||
h.handleHealth(w, r, wantJSON)
|
h.handleHealth(w, r, wantJSON)
|
||||||
case "/debug/perf":
|
|
||||||
h.handlePerf(w, r)
|
|
||||||
case "/debug/runtime":
|
|
||||||
h.handleRuntime(w, r)
|
|
||||||
default:
|
default:
|
||||||
if h.handleClientRoutes(w, r, path, wantJSON) {
|
if h.handleClientRoutes(w, r, path, wantJSON) {
|
||||||
return
|
return
|
||||||
@@ -265,10 +258,10 @@ func (h *Handler) handleIndex(w http.ResponseWriter, _ *http.Request, wantJSON b
|
|||||||
}
|
}
|
||||||
|
|
||||||
if wantJSON {
|
if wantJSON {
|
||||||
clientsJSON := make([]map[string]any, 0, len(clients))
|
clientsJSON := make([]map[string]interface{}, 0, len(clients))
|
||||||
for _, id := range sortedIDs {
|
for _, id := range sortedIDs {
|
||||||
info := clients[id]
|
info := clients[id]
|
||||||
clientsJSON = append(clientsJSON, map[string]any{
|
clientsJSON = append(clientsJSON, map[string]interface{}{
|
||||||
"account_id": info.AccountID,
|
"account_id": info.AccountID,
|
||||||
"service_count": info.ServiceCount,
|
"service_count": info.ServiceCount,
|
||||||
"service_keys": info.ServiceKeys,
|
"service_keys": info.ServiceKeys,
|
||||||
@@ -277,7 +270,7 @@ func (h *Handler) handleIndex(w http.ResponseWriter, _ *http.Request, wantJSON b
|
|||||||
"age": time.Since(info.CreatedAt).Round(time.Second).String(),
|
"age": time.Since(info.CreatedAt).Round(time.Second).String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"version": version.NetbirdVersion(),
|
"version": version.NetbirdVersion(),
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
||||||
"client_count": len(clients),
|
"client_count": len(clients),
|
||||||
@@ -359,10 +352,10 @@ func (h *Handler) handleListClients(w http.ResponseWriter, _ *http.Request, want
|
|||||||
if h.inbound != nil {
|
if h.inbound != nil {
|
||||||
inboundAll = h.inbound.InboundListeners()
|
inboundAll = h.inbound.InboundListeners()
|
||||||
}
|
}
|
||||||
clientsJSON := make([]map[string]any, 0, len(clients))
|
clientsJSON := make([]map[string]interface{}, 0, len(clients))
|
||||||
for _, id := range sortedIDs {
|
for _, id := range sortedIDs {
|
||||||
info := clients[id]
|
info := clients[id]
|
||||||
row := map[string]any{
|
row := map[string]interface{}{
|
||||||
"account_id": info.AccountID,
|
"account_id": info.AccountID,
|
||||||
"service_count": info.ServiceCount,
|
"service_count": info.ServiceCount,
|
||||||
"service_keys": info.ServiceKeys,
|
"service_keys": info.ServiceKeys,
|
||||||
@@ -375,7 +368,7 @@ func (h *Handler) handleListClients(w http.ResponseWriter, _ *http.Request, want
|
|||||||
}
|
}
|
||||||
clientsJSON = append(clientsJSON, row)
|
clientsJSON = append(clientsJSON, row)
|
||||||
}
|
}
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
||||||
"client_count": len(clients),
|
"client_count": len(clients),
|
||||||
"clients": clientsJSON,
|
"clients": clientsJSON,
|
||||||
@@ -465,7 +458,7 @@ func (h *Handler) handleClientStatus(w http.ResponseWriter, r *http.Request, acc
|
|||||||
})
|
})
|
||||||
|
|
||||||
if wantJSON {
|
if wantJSON {
|
||||||
resp := map[string]any{
|
resp := map[string]interface{}{
|
||||||
"account_id": accountID,
|
"account_id": accountID,
|
||||||
"status": overview.FullDetailSummary(),
|
"status": overview.FullDetailSummary(),
|
||||||
}
|
}
|
||||||
@@ -564,20 +557,20 @@ func (h *Handler) handleClientTools(w http.ResponseWriter, _ *http.Request, acco
|
|||||||
func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
host := r.URL.Query().Get("host")
|
host := r.URL.Query().Get("host")
|
||||||
portStr := r.URL.Query().Get("port")
|
portStr := r.URL.Query().Get("port")
|
||||||
if host == "" || portStr == "" {
|
if host == "" || portStr == "" {
|
||||||
h.writeJSON(w, map[string]any{"error": "host and port parameters required"})
|
h.writeJSON(w, map[string]interface{}{"error": "host and port parameters required"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
port, err := strconv.Atoi(portStr)
|
port, err := strconv.Atoi(portStr)
|
||||||
if err != nil || port < 1 || port > 65535 {
|
if err != nil || port < 1 || port > 65535 {
|
||||||
h.writeJSON(w, map[string]any{"error": "invalid port"})
|
h.writeJSON(w, map[string]interface{}{"error": "invalid port"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,7 +594,7 @@ func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountI
|
|||||||
|
|
||||||
conn, err := client.Dial(ctx, network, address)
|
conn, err := client.Dial(ctx, network, address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": port,
|
"port": port,
|
||||||
@@ -616,38 +609,39 @@ func (h *Handler) handlePingTCP(w http.ResponseWriter, r *http.Request, accountI
|
|||||||
}
|
}
|
||||||
|
|
||||||
latency := time.Since(start)
|
latency := time.Since(start)
|
||||||
h.writeJSON(w, map[string]any{
|
resp := map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"host": host,
|
"host": host,
|
||||||
"port": port,
|
"port": port,
|
||||||
"remote": remote,
|
"remote": remote,
|
||||||
"latency_ms": latency.Milliseconds(),
|
"latency_ms": latency.Milliseconds(),
|
||||||
"latency": formatDuration(latency),
|
"latency": formatDuration(latency),
|
||||||
})
|
}
|
||||||
|
h.writeJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handleLogLevel(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleLogLevel(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
level := r.URL.Query().Get("level")
|
level := r.URL.Query().Get("level")
|
||||||
if level == "" {
|
if level == "" {
|
||||||
h.writeJSON(w, map[string]any{"error": "level parameter required (trace, debug, info, warn, error)"})
|
h.writeJSON(w, map[string]interface{}{"error": "level parameter required (trace, debug, info, warn, error)"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.SetLogLevel(level); err != nil {
|
if err := client.SetLogLevel(level); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"level": level,
|
"level": level,
|
||||||
})
|
})
|
||||||
@@ -658,7 +652,7 @@ const clientActionTimeout = 30 * time.Second
|
|||||||
func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -666,14 +660,14 @@ func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, acco
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := client.Start(ctx); err != nil {
|
if err := client.Start(ctx); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "client started",
|
"message": "client started",
|
||||||
})
|
})
|
||||||
@@ -682,7 +676,7 @@ func (h *Handler) handleClientStart(w http.ResponseWriter, r *http.Request, acco
|
|||||||
func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accountID types.AccountID) {
|
||||||
client, ok := h.provider.GetClient(accountID)
|
client, ok := h.provider.GetClient(accountID)
|
||||||
if !ok {
|
if !ok {
|
||||||
h.writeJSON(w, map[string]any{"error": "client not found"})
|
h.writeJSON(w, map[string]interface{}{"error": "client not found"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -690,125 +684,19 @@ func (h *Handler) handleClientStop(w http.ResponseWriter, r *http.Request, accou
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if err := client.Stop(ctx); err != nil {
|
if err := client.Stop(ctx); err != nil {
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": false,
|
"success": false,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
h.writeJSON(w, map[string]any{
|
h.writeJSON(w, map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"message": "client stopped",
|
"message": "client stopped",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) handlePerf(w http.ResponseWriter, r *http.Request) {
|
|
||||||
raw := r.URL.Query().Get("value")
|
|
||||||
if raw == "" {
|
|
||||||
http.Error(w, "value parameter is required", http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseUint(raw, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, fmt.Sprintf("invalid value %q: %v", raw, err), http.StatusBadRequest)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
capN := uint32(n)
|
|
||||||
applied := 0
|
|
||||||
failed := map[string]string{}
|
|
||||||
for accountID, client := range h.provider.ListClientsForStartup() {
|
|
||||||
if err := client.SetPerformance(nbembed.Performance{PreallocatedBuffersPerPool: &capN}); err != nil {
|
|
||||||
failed[string(accountID)] = err.Error()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
applied++
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := map[string]any{
|
|
||||||
"success": true,
|
|
||||||
"value": capN,
|
|
||||||
"applied": applied,
|
|
||||||
}
|
|
||||||
if len(failed) > 0 {
|
|
||||||
resp["failed"] = failed
|
|
||||||
}
|
|
||||||
h.writeJSON(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// handleRuntime returns cheap runtime and process stats. Safe to hit on a
|
|
||||||
// running proxy; does not read pprof profiles.
|
|
||||||
func (h *Handler) handleRuntime(w http.ResponseWriter, _ *http.Request) {
|
|
||||||
var m runtime.MemStats
|
|
||||||
runtime.ReadMemStats(&m)
|
|
||||||
|
|
||||||
clients := h.provider.ListClientsForDebug()
|
|
||||||
started := 0
|
|
||||||
for _, c := range clients {
|
|
||||||
if c.HasClient {
|
|
||||||
started++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resp := map[string]any{
|
|
||||||
"uptime": time.Since(h.startTime).Round(time.Second).String(),
|
|
||||||
"goroutines": runtime.NumGoroutine(),
|
|
||||||
"num_cpu": runtime.NumCPU(),
|
|
||||||
"gomaxprocs": runtime.GOMAXPROCS(0),
|
|
||||||
"go_version": runtime.Version(),
|
|
||||||
"heap_alloc": m.HeapAlloc,
|
|
||||||
"heap_inuse": m.HeapInuse,
|
|
||||||
"heap_idle": m.HeapIdle,
|
|
||||||
"heap_released": m.HeapReleased,
|
|
||||||
"heap_sys": m.HeapSys,
|
|
||||||
"sys": m.Sys,
|
|
||||||
"live_objects": m.Mallocs - m.Frees,
|
|
||||||
"num_gc": m.NumGC,
|
|
||||||
"pause_total_ns": m.PauseTotalNs,
|
|
||||||
"clients": len(clients),
|
|
||||||
"started": started,
|
|
||||||
}
|
|
||||||
|
|
||||||
if proc := readProcStatus(); proc != nil {
|
|
||||||
resp["vm_rss"] = proc["VmRSS"]
|
|
||||||
resp["vm_size"] = proc["VmSize"]
|
|
||||||
resp["vm_data"] = proc["VmData"]
|
|
||||||
}
|
|
||||||
|
|
||||||
h.writeJSON(w, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
// readProcStatus parses /proc/self/status on Linux and returns size fields
|
|
||||||
// in bytes. Returns nil on non-Linux or read failure.
|
|
||||||
func readProcStatus() map[string]uint64 {
|
|
||||||
raw, err := os.ReadFile("/proc/self/status")
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
out := map[string]uint64{}
|
|
||||||
for _, line := range strings.Split(string(raw), "\n") {
|
|
||||||
k, v, ok := strings.Cut(line, ":")
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if k != "VmRSS" && k != "VmSize" && k != "VmData" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fields := strings.Fields(v)
|
|
||||||
if len(fields) < 1 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
n, err := strconv.ParseUint(fields[0], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Values are reported in kB.
|
|
||||||
out[k] = n * 1024
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
const maxCaptureDuration = 30 * time.Minute
|
const maxCaptureDuration = 30 * time.Minute
|
||||||
|
|
||||||
// handleCapture streams a pcap or text packet capture for the given client.
|
// handleCapture streams a pcap or text packet capture for the given client.
|
||||||
@@ -937,7 +825,7 @@ func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request, wantJSON
|
|||||||
h.writeJSON(w, resp)
|
h.writeJSON(w, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data any) {
|
func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data interface{}) {
|
||||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||||
tmpl := h.getTemplates()
|
tmpl := h.getTemplates()
|
||||||
if tmpl == nil {
|
if tmpl == nil {
|
||||||
@@ -950,7 +838,7 @@ func (h *Handler) renderTemplate(w http.ResponseWriter, name string, data any) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Handler) writeJSON(w http.ResponseWriter, v any) {
|
func (h *Handler) writeJSON(w http.ResponseWriter, v interface{}) {
|
||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
enc := json.NewEncoder(w)
|
enc := json.NewEncoder(w)
|
||||||
enc.SetIndent("", " ")
|
enc.SetIndent("", " ")
|
||||||
|
|||||||
@@ -131,7 +131,6 @@ type ClientConfig struct {
|
|||||||
MgmtAddr string
|
MgmtAddr string
|
||||||
WGPort uint16
|
WGPort uint16
|
||||||
PreSharedKey string
|
PreSharedKey string
|
||||||
Performance embed.Performance
|
|
||||||
// BlockInbound mirrors embed.Options.BlockInbound. Set to true on the
|
// BlockInbound mirrors embed.Options.BlockInbound. Set to true on the
|
||||||
// standalone proxy where the embedded client never accepts inbound;
|
// standalone proxy where the embedded client never accepts inbound;
|
||||||
// set to false on the private/embedded proxy so the engine creates
|
// set to false on the private/embedded proxy so the engine creates
|
||||||
@@ -307,7 +306,7 @@ func (n *NetBird) createClientEntry(ctx context.Context, accountID types.Account
|
|||||||
ManagementURL: n.clientCfg.MgmtAddr,
|
ManagementURL: n.clientCfg.MgmtAddr,
|
||||||
PrivateKey: privateKey.String(),
|
PrivateKey: privateKey.String(),
|
||||||
LogLevel: log.WarnLevel.String(),
|
LogLevel: log.WarnLevel.String(),
|
||||||
BlockInbound: n.clientCfg.BlockInbound,
|
BlockInbound: n.clientCfg.BlockInbound,
|
||||||
// The embedded proxy peer must never be a stepping stone into
|
// The embedded proxy peer must never be a stepping stone into
|
||||||
// the proxy host's LAN: it only exists to reach NetBird mesh
|
// the proxy host's LAN: it only exists to reach NetBird mesh
|
||||||
// targets or, when direct_upstream is set, the host network
|
// targets or, when direct_upstream is set, the host network
|
||||||
@@ -316,7 +315,6 @@ func (n *NetBird) createClientEntry(ctx context.Context, accountID types.Account
|
|||||||
BlockLANAccess: true,
|
BlockLANAccess: true,
|
||||||
WireguardPort: &wgPort,
|
WireguardPort: &wgPort,
|
||||||
PreSharedKey: n.clientCfg.PreSharedKey,
|
PreSharedKey: n.clientCfg.PreSharedKey,
|
||||||
Performance: n.clientCfg.Performance,
|
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("create netbird client: %w", err)
|
return nil, fmt.Errorf("create netbird client: %w", err)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/acme"
|
"github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -90,10 +89,6 @@ type Config struct {
|
|||||||
// PreSharedKey is the WireGuard pre-shared key used between the
|
// PreSharedKey is the WireGuard pre-shared key used between the
|
||||||
// proxy's embedded clients and peers.
|
// proxy's embedded clients and peers.
|
||||||
PreSharedKey string
|
PreSharedKey string
|
||||||
// Performance configures the tunnel pool/batch sizes for every
|
|
||||||
// embedded client this proxy creates. Zero values fall back to
|
|
||||||
// upstream defaults.
|
|
||||||
Performance embed.Performance
|
|
||||||
|
|
||||||
// SupportsCustomPorts indicates whether the proxy can bind arbitrary
|
// SupportsCustomPorts indicates whether the proxy can bind arbitrary
|
||||||
// ports for TCP/UDP/TLS services.
|
// ports for TCP/UDP/TLS services.
|
||||||
@@ -153,7 +148,6 @@ func New(cfg Config) *Server {
|
|||||||
WireguardPort: cfg.WireguardPort,
|
WireguardPort: cfg.WireguardPort,
|
||||||
ProxyProtocol: cfg.ProxyProtocol,
|
ProxyProtocol: cfg.ProxyProtocol,
|
||||||
PreSharedKey: cfg.PreSharedKey,
|
PreSharedKey: cfg.PreSharedKey,
|
||||||
Performance: cfg.Performance,
|
|
||||||
SupportsCustomPorts: cfg.SupportsCustomPorts,
|
SupportsCustomPorts: cfg.SupportsCustomPorts,
|
||||||
RequireSubdomain: cfg.RequireSubdomain,
|
RequireSubdomain: cfg.RequireSubdomain,
|
||||||
Private: cfg.Private,
|
Private: cfg.Private,
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ import (
|
|||||||
goproto "google.golang.org/protobuf/proto"
|
goproto "google.golang.org/protobuf/proto"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/client/embed"
|
|
||||||
"github.com/netbirdio/netbird/proxy/internal/accesslog"
|
"github.com/netbirdio/netbird/proxy/internal/accesslog"
|
||||||
"github.com/netbirdio/netbird/proxy/internal/acme"
|
"github.com/netbirdio/netbird/proxy/internal/acme"
|
||||||
"github.com/netbirdio/netbird/proxy/internal/auth"
|
"github.com/netbirdio/netbird/proxy/internal/auth"
|
||||||
@@ -186,9 +185,6 @@ type Server struct {
|
|||||||
// single-account deployments; multiple accounts will fail to bind
|
// single-account deployments; multiple accounts will fail to bind
|
||||||
// the same port.
|
// the same port.
|
||||||
WireguardPort uint16
|
WireguardPort uint16
|
||||||
// Performance configures the tunnel pool/batch sizes for every
|
|
||||||
// embedded client this proxy spawns.
|
|
||||||
Performance embed.Performance
|
|
||||||
// ProxyProtocol enables PROXY protocol (v1/v2) on TCP listeners.
|
// ProxyProtocol enables PROXY protocol (v1/v2) on TCP listeners.
|
||||||
// When enabled, the real client IP is extracted from the PROXY header
|
// When enabled, the real client IP is extracted from the PROXY header
|
||||||
// sent by upstream L4 proxies that support PROXY protocol.
|
// sent by upstream L4 proxies that support PROXY protocol.
|
||||||
@@ -337,8 +333,6 @@ func (s *Server) Start(ctx context.Context) error {
|
|||||||
s.runCancel = runCancel
|
s.runCancel = runCancel
|
||||||
|
|
||||||
s.initNetBirdClient()
|
s.initNetBirdClient()
|
||||||
// Create health checker before the mapping worker so it can track
|
|
||||||
// management connectivity from the first stream connection.
|
|
||||||
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
|
s.healthChecker = health.NewChecker(s.Logger, s.netbird)
|
||||||
|
|
||||||
s.crowdsecRegistry = crowdsec.NewRegistry(s.CrowdSecAPIURL, s.CrowdSecAPIKey, log.NewEntry(s.Logger))
|
s.crowdsecRegistry = crowdsec.NewRegistry(s.CrowdSecAPIURL, s.CrowdSecAPIKey, log.NewEntry(s.Logger))
|
||||||
@@ -535,7 +529,6 @@ func (s *Server) initNetBirdClient() {
|
|||||||
MgmtAddr: s.ManagementAddress,
|
MgmtAddr: s.ManagementAddress,
|
||||||
WGPort: s.WireguardPort,
|
WGPort: s.WireguardPort,
|
||||||
PreSharedKey: s.PreSharedKey,
|
PreSharedKey: s.PreSharedKey,
|
||||||
Performance: s.Performance,
|
|
||||||
// On --private the embedded client serves per-account inbound
|
// On --private the embedded client serves per-account inbound
|
||||||
// listeners and must apply management's ACL: keep BlockInbound off
|
// listeners and must apply management's ACL: keep BlockInbound off
|
||||||
// so the engine creates the ACL manager. On the standalone proxy
|
// so the engine creates the ACL manager. On the standalone proxy
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import (
|
|||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
"google.golang.org/grpc/status"
|
"google.golang.org/grpc/status"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/server/integrations/integrated_validator/validator"
|
"github.com/netbirdio/management-integrations/integrations"
|
||||||
ephemeral_manager "github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
ephemeral_manager "github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral/manager"
|
||||||
|
|
||||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
"github.com/netbirdio/netbird/management/internals/controllers/network_map/controller"
|
||||||
@@ -103,7 +103,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ia, _ := validator.NewIntegratedValidator(ctx, peersManger, settingsManagerMock, eventStore, cacheStore)
|
ia, _ := integrations.NewIntegratedValidator(ctx, peersManger, settingsManagerMock, eventStore, cacheStore)
|
||||||
|
|
||||||
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
metrics, err := telemetry.NewDefaultAppMetrics(ctx)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|||||||
@@ -2,19 +2,75 @@ package version
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime/debug"
|
||||||
|
"strings"
|
||||||
|
|
||||||
v "github.com/hashicorp/go-version"
|
v "github.com/hashicorp/go-version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DevelopmentVersion is the value of NetbirdVersion() for non-release builds.
|
||||||
|
// Wire-format consumers (management server, dashboard) match against this
|
||||||
|
// string, so it must not change without coordinating those consumers.
|
||||||
|
const DevelopmentVersion = "development"
|
||||||
|
|
||||||
// will be replaced with the release version when using goreleaser
|
// will be replaced with the release version when using goreleaser
|
||||||
var version = "development"
|
var version = DevelopmentVersion
|
||||||
|
|
||||||
var (
|
var (
|
||||||
VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$")
|
VersionRegexp = regexp.MustCompile("^" + v.VersionRegexpRaw + "$")
|
||||||
SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$")
|
SemverRegexp = regexp.MustCompile("^" + v.SemverRegexpRaw + "$")
|
||||||
)
|
)
|
||||||
|
|
||||||
// NetbirdVersion returns the Netbird version
|
// NetbirdVersion returns the Netbird version. For non-release builds the
|
||||||
|
// value is the literal DevelopmentVersion constant; the VCS revision is
|
||||||
|
// exposed separately via NetbirdCommit so the wire format stays stable.
|
||||||
func NetbirdVersion() string {
|
func NetbirdVersion() string {
|
||||||
return version
|
return version
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NetbirdCommit returns the VCS revision (truncated to 12 chars) of the
|
||||||
|
// build, with a "-dirty" suffix when the working tree was modified.
|
||||||
|
// Returns an empty string when no build info is embedded (e.g. release
|
||||||
|
// builds compiled by goreleaser without -buildvcs).
|
||||||
|
func NetbirdCommit() string {
|
||||||
|
info, ok := debug.ReadBuildInfo()
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var revision string
|
||||||
|
var modified bool
|
||||||
|
for _, s := range info.Settings {
|
||||||
|
switch s.Key {
|
||||||
|
case "vcs.revision":
|
||||||
|
revision = s.Value
|
||||||
|
case "vcs.modified":
|
||||||
|
modified = s.Value == "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if revision == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(revision) > 12 {
|
||||||
|
revision = revision[:12]
|
||||||
|
}
|
||||||
|
|
||||||
|
if modified {
|
||||||
|
revision += "-dirty"
|
||||||
|
}
|
||||||
|
return revision
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsDevelopmentVersion reports whether the given version string identifies
|
||||||
|
// a non-release / development build. It is the single source of truth for
|
||||||
|
// "is this a dev build" checks across the codebase; use it instead of
|
||||||
|
// comparing against the "development" literal or ad-hoc substring checks.
|
||||||
|
//
|
||||||
|
// Matches the bare DevelopmentVersion constant as well as any future
|
||||||
|
// extension such as "development-<sha>" or "development-<sha>-dirty",
|
||||||
|
// while excluding tagged prereleases like "v0.31.1-dev".
|
||||||
|
func IsDevelopmentVersion(v string) bool {
|
||||||
|
return strings.HasPrefix(v, DevelopmentVersion)
|
||||||
|
}
|
||||||
|
|||||||
26
version/version_test.go
Normal file
26
version/version_test.go
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package version
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestIsDevelopmentVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
version string
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{"development", true},
|
||||||
|
{"development-0823f3ff9ab1", true},
|
||||||
|
{"development-0823f3ff9ab1-dirty", true},
|
||||||
|
{"0.50.0", false},
|
||||||
|
{"v0.31.1-dev", false},
|
||||||
|
{"1.0.0-dev", false},
|
||||||
|
{"dev", false},
|
||||||
|
{"", false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.version, func(t *testing.T) {
|
||||||
|
if got := IsDevelopmentVersion(tt.version); got != tt.want {
|
||||||
|
t.Errorf("IsDevelopmentVersion(%q) = %v, want %v", tt.version, got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user