Merge branch 'main' into refactor/permissions-manager

# Conflicts:
#	management/internals/modules/reverseproxy/service/manager/api.go
#	management/server/http/testing/testing_tools/channel/channel.go
This commit is contained in:
pascal
2026-03-27 14:37:29 +01:00
125 changed files with 11320 additions and 413 deletions
+1
View File
@@ -22,6 +22,7 @@ type Client interface {
GetDeviceAuthorizationFlow(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
GetPKCEAuthorizationFlow(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error)
GetNetworkMap(sysInfo *system.Info) (*proto.NetworkMap, error)
GetServerURL() string
IsHealthy() bool
SyncMeta(sysInfo *system.Info) error
Logout() error
+42 -1
View File
@@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"io"
"os"
"strconv"
"sync"
"time"
@@ -29,6 +31,10 @@ import (
const ConnectTimeout = 10 * time.Second
const (
// EnvMaxRecvMsgSize overrides the default gRPC max receive message size (4 MB)
// for the management client connection. Value is in bytes.
EnvMaxRecvMsgSize = "NB_MANAGEMENT_GRPC_MAX_MSG_SIZE"
errMsgMgmtPublicKey = "failed getting Management Service public key: %s"
errMsgNoMgmtConnection = "no connection to management"
)
@@ -46,6 +52,7 @@ type GrpcClient struct {
conn *grpc.ClientConn
connStateCallback ConnStateNotifier
connStateCallbackLock sync.RWMutex
serverURL string
}
type ExposeRequest struct {
@@ -66,13 +73,41 @@ type ExposeResponse struct {
PortAutoAssigned bool
}
// MaxRecvMsgSize returns the configured max gRPC receive message size from
// the environment, or 0 if unset (which uses the gRPC default of 4 MB).
func MaxRecvMsgSize() int {
val := os.Getenv(EnvMaxRecvMsgSize)
if val == "" {
return 0
}
size, err := strconv.Atoi(val)
if err != nil {
log.Warnf("invalid %s value %q, using default: %v", EnvMaxRecvMsgSize, val, err)
return 0
}
if size <= 0 {
log.Warnf("invalid %s value %d, must be positive, using default", EnvMaxRecvMsgSize, size)
return 0
}
return size
}
// NewClient creates a new client to Management service
func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsEnabled bool) (*GrpcClient, error) {
var conn *grpc.ClientConn
var extraOpts []grpc.DialOption
if maxSize := MaxRecvMsgSize(); maxSize > 0 {
extraOpts = append(extraOpts, grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(maxSize)))
log.Infof("management gRPC max receive message size set to %d bytes", maxSize)
}
operation := func() error {
var err error
conn, err = nbgrpc.CreateConnection(ctx, addr, tlsEnabled, wsproxy.ManagementComponent)
conn, err = nbgrpc.CreateConnection(ctx, addr, tlsEnabled, wsproxy.ManagementComponent, extraOpts...)
if err != nil {
return fmt.Errorf("create connection: %w", err)
}
@@ -93,9 +128,15 @@ func NewClient(ctx context.Context, addr string, ourPrivateKey wgtypes.Key, tlsE
ctx: ctx,
conn: conn,
connStateCallbackLock: sync.RWMutex{},
serverURL: addr,
}, nil
}
// GetServerURL returns the management server URL
func (c *GrpcClient) GetServerURL() string {
return c.serverURL
}
// Close closes connection to the Management Service
func (c *GrpcClient) Close() error {
return c.conn.Close()
+95
View File
@@ -0,0 +1,95 @@
package client
import (
"context"
"net"
"os"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
mgmtProto "github.com/netbirdio/netbird/shared/management/proto"
)
func TestMaxRecvMsgSize(t *testing.T) {
tests := []struct {
name string
envValue string
expected int
}{
{name: "unset returns 0", envValue: "", expected: 0},
{name: "valid value", envValue: "10485760", expected: 10485760},
{name: "non-numeric returns 0", envValue: "abc", expected: 0},
{name: "negative returns 0", envValue: "-1", expected: 0},
{name: "zero returns 0", envValue: "0", expected: 0},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv(EnvMaxRecvMsgSize, tt.envValue)
if tt.envValue == "" {
os.Unsetenv(EnvMaxRecvMsgSize)
}
assert.Equal(t, tt.expected, MaxRecvMsgSize())
})
}
}
// largeSyncServer implements just the Sync RPC, returning a response larger than the default 4MB limit.
type largeSyncServer struct {
mgmtProto.UnimplementedManagementServiceServer
responseSize int
}
func (s *largeSyncServer) GetServerKey(_ context.Context, _ *mgmtProto.Empty) (*mgmtProto.ServerKeyResponse, error) {
// Return a response with a large WiretrusteeConfig to exceed the default limit.
padding := strings.Repeat("x", s.responseSize)
return &mgmtProto.ServerKeyResponse{
Key: padding,
}, nil
}
func TestMaxRecvMsgSizeIntegration(t *testing.T) {
const payloadSize = 5 * 1024 * 1024 // 5MB, exceeds 4MB default
lis, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
srv := grpc.NewServer()
mgmtProto.RegisterManagementServiceServer(srv, &largeSyncServer{responseSize: payloadSize})
go func() { _ = srv.Serve(lis) }()
t.Cleanup(srv.Stop)
t.Run("default limit rejects large message", func(t *testing.T) {
conn, err := grpc.NewClient(
lis.Addr().String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
defer conn.Close()
client := mgmtProto.NewManagementServiceClient(conn)
_, err = client.GetServerKey(context.Background(), &mgmtProto.Empty{})
require.Error(t, err)
assert.Contains(t, err.Error(), "received message larger than max")
})
t.Run("increased limit accepts large message", func(t *testing.T) {
conn, err := grpc.NewClient(
lis.Addr().String(),
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(10*1024*1024)),
)
require.NoError(t, err)
defer conn.Close()
client := mgmtProto.NewManagementServiceClient(conn)
resp, err := client.GetServerKey(context.Background(), &mgmtProto.Empty{})
require.NoError(t, err)
assert.Len(t, resp.Key, payloadSize)
})
}
+9
View File
@@ -19,6 +19,7 @@ type MockClient struct {
LoginFunc func(serverKey wgtypes.Key, info *system.Info, sshKey []byte, dnsLabels domain.List) (*proto.LoginResponse, error)
GetDeviceAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.DeviceAuthorizationFlow, error)
GetPKCEAuthorizationFlowFunc func(serverKey wgtypes.Key) (*proto.PKCEAuthorizationFlow, error)
GetServerURLFunc func() string
SyncMetaFunc func(sysInfo *system.Info) error
LogoutFunc func() error
JobFunc func(ctx context.Context, msgHandler func(msg *proto.JobRequest) *proto.JobResponse) error
@@ -92,6 +93,14 @@ func (m *MockClient) GetNetworkMap(_ *system.Info) (*proto.NetworkMap, error) {
return nil, nil
}
// GetServerURL mock implementation of GetServerURL from mgm.Client interface
func (m *MockClient) GetServerURL() string {
if m.GetServerURLFunc == nil {
return ""
}
return m.GetServerURLFunc()
}
func (m *MockClient) SyncMeta(sysInfo *system.Info) error {
if m.SyncMetaFunc == nil {
return nil