diff --git a/client/cmd/testutil_test.go b/client/cmd/testutil_test.go index e3e644357..ee67d2501 100644 --- a/client/cmd/testutil_test.go +++ b/client/cmd/testutil_test.go @@ -10,6 +10,7 @@ import ( "go.opentelemetry.io/otel" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -89,7 +90,7 @@ func startManagement(t *testing.T, config *mgmt.Config, testFile string) (*grpc. metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics) + accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, iv, metrics, port_forwarding.NewControllerMock()) if err != nil { t.Fatal(err) } diff --git a/client/internal/engine_test.go b/client/internal/engine_test.go index ca49eca09..1ac9a0430 100644 --- a/client/internal/engine_test.go +++ b/client/internal/engine_test.go @@ -39,6 +39,7 @@ import ( mgmtProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -1220,7 +1221,7 @@ func startManagement(t *testing.T, dataDir, testFile string) (*grpc.Server, stri metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics) + accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock()) if err != nil { return nil, "", err } diff --git a/client/server/server_test.go b/client/server/server_test.go index 128de8e02..278ec246c 100644 --- a/client/server/server_test.go +++ b/client/server/server_test.go @@ -20,6 +20,7 @@ import ( mgmtProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -128,7 +129,7 @@ func startManagement(t *testing.T, signalAddr string, counter *int) (*grpc.Serve metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics) + accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock()) if err != nil { return nil, "", err } diff --git a/go.mod b/go.mod index e65296a53..2da42a1be 100644 --- a/go.mod +++ b/go.mod @@ -60,7 +60,7 @@ require ( github.com/miekg/dns v1.1.59 github.com/mitchellh/hashstructure/v2 v2.0.2 github.com/nadoo/ipset v0.5.0 - github.com/netbirdio/management-integrations/integrations v0.0.0-20250115083837-a09722b8d2a6 + github.com/netbirdio/management-integrations/integrations v0.0.0-20250131123435-e2ad9f2cb38b github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20241010133937-e0df50df217d github.com/okta/okta-sdk-golang/v2 v2.18.0 github.com/oschwald/maxminddb-golang v1.12.0 diff --git a/go.sum b/go.sum index e3670b99e..b2cb334c6 100644 --- a/go.sum +++ b/go.sum @@ -527,8 +527,8 @@ github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944 h1:TDtJKmM6S github.com/netbirdio/go-netroute v0.0.0-20240611143515-f59b0e1d3944/go.mod h1:sHA6TRxjQ6RLbnI+3R4DZo2Eseg/iKiPRfNmcuNySVQ= github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e h1:PURA50S8u4mF6RrkYYCAvvPCixhqqEiEy3Ej6avh04c= github.com/netbirdio/ice/v3 v3.0.0-20240315174635-e72a50fcb64e/go.mod h1:YMLU7qbKfVjmEv7EoZPIVEI+kNYxWCdPK3VS0BU+U4Q= -github.com/netbirdio/management-integrations/integrations v0.0.0-20250115083837-a09722b8d2a6 h1:I/ODkZ8rSDOzlJbhEjD2luSI71zl+s5JgNvFHY0+mBU= -github.com/netbirdio/management-integrations/integrations v0.0.0-20250115083837-a09722b8d2a6/go.mod h1:izUUs1NT7ja+PwSX3kJ7ox8Kkn478tboBJSjL4kU6J0= +github.com/netbirdio/management-integrations/integrations v0.0.0-20250131123435-e2ad9f2cb38b h1:oFlch4DytiKz0rYB45Sx9+nmfh+z4tf9Jt3eSAeiwFY= +github.com/netbirdio/management-integrations/integrations v0.0.0-20250131123435-e2ad9f2cb38b/go.mod h1:qaApwbKpiX1vknyXb5x49qnQNBx8X67E1Pvn5sJ6gQU= github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502 h1:3tHlFmhTdX9axERMVN63dqyFqnvuD+EMJHzM7mNGON8= github.com/netbirdio/service v0.0.0-20240911161631-f62744f42502/go.mod h1:CIMRFEJVL+0DS1a3Nx06NaMn4Dz63Ng6O7dl0qH0zVM= github.com/netbirdio/signal-dispatcher/dispatcher v0.0.0-20241010133937-e0df50df217d h1:bRq5TKgC7Iq20pDiuC54yXaWnAVeS5PdGpSokFTlR28= diff --git a/management/client/client_test.go b/management/client/client_test.go index 8bd8af8d2..dc000be54 100644 --- a/management/client/client_test.go +++ b/management/client/client_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/require" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -72,7 +73,7 @@ func startManagement(t *testing.T) (*grpc.Server, net.Listener) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics) + accountManager, err := mgmt.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, ia, metrics, port_forwarding.NewControllerMock()) if err != nil { t.Fatal(err) } diff --git a/management/cmd/management.go b/management/cmd/management.go index 1c8fca8dc..469be4d67 100644 --- a/management/cmd/management.go +++ b/management/cmd/management.go @@ -35,6 +35,8 @@ import ( "github.com/netbirdio/management-integrations/integrations" + "github.com/netbirdio/netbird/management/server/peers" + "github.com/netbirdio/netbird/encryption" "github.com/netbirdio/netbird/formatter" mgmtProto "github.com/netbirdio/netbird/management/proto" @@ -200,8 +202,15 @@ var ( if err != nil { return fmt.Errorf("failed to initialize integrated peer validator: %v", err) } + + userManager := users.NewManager(store) + settingsManager := settings.NewManager(store) + permissionsManager := permissions.NewManager(userManager, settingsManager) + peersManager := peers.NewManager(store, permissionsManager) + proxyController := integrations.NewController(store) + accountManager, err := server.BuildManager(ctx, store, peersUpdateManager, idpManager, mgmtSingleAccModeDomain, - dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator, appMetrics) + dnsDomain, eventStore, geo, userDeleteFromIDPEnabled, integratedPeerValidator, appMetrics, proxyController) if err != nil { return fmt.Errorf("failed to build default manager: %v", err) } @@ -273,15 +282,12 @@ var ( KeysLocation: config.HttpConfig.AuthKeysLocation, } - userManager := users.NewManager(store) - settingsManager := settings.NewManager(store) - permissionsManager := permissions.NewManager(userManager, settingsManager) groupsManager := groups.NewManager(store, permissionsManager, accountManager) resourcesManager := resources.NewManager(store, permissionsManager, groupsManager, accountManager) routersManager := routers.NewManager(store, permissionsManager, accountManager) networksManager := networks.NewManager(store, permissionsManager, resourcesManager, routersManager, accountManager) - httpAPIHandler, err := nbhttp.NewAPIHandler(ctx, accountManager, networksManager, resourcesManager, routersManager, groupsManager, geo, jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator) + httpAPIHandler, err := nbhttp.NewAPIHandler(ctx, accountManager, networksManager, resourcesManager, routersManager, groupsManager, geo, jwtValidator, appMetrics, httpAPIAuthCfg, integratedPeerValidator, proxyController, permissionsManager, peersManager) if err != nil { return fmt.Errorf("failed creating HTTP API handler: %v", err) } diff --git a/management/server/account.go b/management/server/account.go index 2c62a2453..f123b50ed 100644 --- a/management/server/account.go +++ b/management/server/account.go @@ -30,7 +30,8 @@ import ( "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/geolocation" "github.com/netbirdio/netbird/management/server/idp" - "github.com/netbirdio/netbird/management/server/integrated_validator" + "github.com/netbirdio/netbird/management/server/integrations/integrated_validator" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/jwtclaims" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/posture" @@ -167,6 +168,8 @@ type DefaultAccountManager struct { requestBuffer *AccountRequestBuffer + proxyController port_forwarding.Controller + // singleAccountMode indicates whether the instance has a single account. // If true, then every new user will end up under the same account. // This value will be set to false if management service has more than one account. @@ -250,6 +253,7 @@ func BuildManager( userDeleteFromIDPEnabled bool, integratedPeerValidator integrated_validator.IntegratedValidator, metrics telemetry.AppMetrics, + proxyController port_forwarding.Controller, ) (*DefaultAccountManager, error) { am := &DefaultAccountManager{ Store: store, @@ -267,6 +271,7 @@ func BuildManager( integratedPeerValidator: integratedPeerValidator, metrics: metrics, requestBuffer: NewAccountRequestBuffer(ctx, store), + proxyController: proxyController, } allAccounts := store.GetAllAccounts(ctx) // enable single account mode only if configured by user and number of existing accounts is not grater than 1 diff --git a/management/server/account_test.go b/management/server/account_test.go index 1fc1ceb92..accbf7689 100644 --- a/management/server/account_test.go +++ b/management/server/account_test.go @@ -17,6 +17,7 @@ import ( "github.com/golang-jwt/jwt" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/util" resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" @@ -2900,7 +2901,7 @@ func createManager(t TB) (*DefaultAccountManager, error) { return nil, err } - manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics) + manager, err := BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) if err != nil { return nil, err } diff --git a/management/server/dns_test.go b/management/server/dns_test.go index 6fb9f6a29..429f430b6 100644 --- a/management/server/dns_test.go +++ b/management/server/dns_test.go @@ -11,6 +11,7 @@ import ( "github.com/stretchr/testify/assert" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" "github.com/netbirdio/netbird/management/server/types" @@ -208,7 +209,7 @@ func createDNSManager(t *testing.T) (*DefaultAccountManager, error) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics) + return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.test", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) } func createDNSStore(t *testing.T) (store.Store, error) { diff --git a/management/server/grpcserver.go b/management/server/grpcserver.go index a21dcd5b8..bd6628d6a 100644 --- a/management/server/grpcserver.go +++ b/management/server/grpcserver.go @@ -648,6 +648,14 @@ func toSyncResponse(ctx context.Context, config *Config, peer *nbpeer.Peer, turn response.NetworkMap.RoutesFirewallRules = routesFirewallRules response.NetworkMap.RoutesFirewallRulesIsEmpty = len(routesFirewallRules) == 0 + if networkMap.ForwardingRules != nil { + forwardingRules := make([]*proto.ForwardingRule, 0, len(networkMap.ForwardingRules)) + for _, rule := range networkMap.ForwardingRules { + forwardingRules = append(forwardingRules, rule.ToProto()) + } + response.NetworkMap.ForwardingRules = forwardingRules + } + return response } diff --git a/management/server/http/handler.go b/management/server/http/handler.go index cc2ad00b7..a082f50df 100644 --- a/management/server/http/handler.go +++ b/management/server/http/handler.go @@ -9,6 +9,8 @@ import ( "github.com/rs/cors" "github.com/netbirdio/management-integrations/integrations" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" + "github.com/netbirdio/netbird/management/server/permissions" s "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/geolocation" @@ -25,18 +27,19 @@ import ( "github.com/netbirdio/netbird/management/server/http/handlers/setup_keys" "github.com/netbirdio/netbird/management/server/http/handlers/users" "github.com/netbirdio/netbird/management/server/http/middleware" - "github.com/netbirdio/netbird/management/server/integrated_validator" + "github.com/netbirdio/netbird/management/server/integrations/integrated_validator" "github.com/netbirdio/netbird/management/server/jwtclaims" nbnetworks "github.com/netbirdio/netbird/management/server/networks" "github.com/netbirdio/netbird/management/server/networks/resources" "github.com/netbirdio/netbird/management/server/networks/routers" + nbpeers "github.com/netbirdio/netbird/management/server/peers" "github.com/netbirdio/netbird/management/server/telemetry" ) const apiPrefix = "/api" // NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints. -func NewAPIHandler(ctx context.Context, accountManager s.AccountManager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg configs.AuthCfg, integratedValidator integrated_validator.IntegratedValidator) (http.Handler, error) { +func NewAPIHandler(ctx context.Context, accountManager s.AccountManager, networksManager nbnetworks.Manager, resourceManager resources.Manager, routerManager routers.Manager, groupsManager nbgroups.Manager, LocationManager geolocation.Geolocation, jwtValidator jwtclaims.JWTValidator, appMetrics telemetry.AppMetrics, authCfg configs.AuthCfg, integratedValidator integrated_validator.IntegratedValidator, proxyController port_forwarding.Controller, permissionsManager permissions.Manager, peersManager nbpeers.Manager) (http.Handler, error) { claimsExtractor := jwtclaims.NewClaimsExtractor( jwtclaims.WithAudience(authCfg.Audience), jwtclaims.WithUserIDClaim(authCfg.UserIDClaim), @@ -71,7 +74,7 @@ func NewAPIHandler(ctx context.Context, accountManager s.AccountManager, network router := rootRouter.PathPrefix(prefix).Subrouter() router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler, acMiddleware.Handler) - if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, claimsExtractor, integratedValidator, appMetrics.GetMeter()); err != nil { + if _, err := integrations.RegisterHandlers(ctx, prefix, router, accountManager, claimsExtractor, integratedValidator, appMetrics.GetMeter(), permissionsManager, peersManager, proxyController); err != nil { return nil, fmt.Errorf("register integrations endpoints: %w", err) } diff --git a/management/server/http/testing/testing_tools/tools.go b/management/server/http/testing/testing_tools/tools.go index 006d5679c..a1e121fdf 100644 --- a/management/server/http/testing/testing_tools/tools.go +++ b/management/server/http/testing/testing_tools/tools.go @@ -13,10 +13,14 @@ import ( "testing" "time" - "github.com/netbirdio/netbird/management/server/util" + "github.com/netbirdio/management-integrations/integrations" "github.com/stretchr/testify/assert" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "github.com/netbirdio/netbird/management/server/peers" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/util" + "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" "github.com/netbirdio/netbird/management/server/geolocation" @@ -110,7 +114,8 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve geoMock := &geolocation.Mock{} validatorMock := server.MocIntegratedValidator{} - am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics) + proxyController := integrations.NewController(store) + am, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "", &activity.InMemoryEventStore{}, geoMock, false, validatorMock, metrics, proxyController) if err != nil { t.Fatalf("Failed to create manager: %v", err) } @@ -119,7 +124,9 @@ func BuildApiBlackBoxWithDBState(t TB, sqlFile string, expectedPeerUpdate *serve resourcesManagerMock := resources.NewManagerMock() routersManagerMock := routers.NewManagerMock() groupsManagerMock := groups.NewManagerMock() - apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, &jwtclaims.JwtValidatorMock{}, metrics, configs.AuthCfg{}, validatorMock) + permissionsManagerMock := permissions.NewManagerMock() + peersManager := peers.NewManager(store, permissionsManagerMock) + apiHandler, err := nbhttp.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, &jwtclaims.JwtValidatorMock{}, metrics, configs.AuthCfg{}, validatorMock, proxyController, permissionsManagerMock, peersManager) if err != nil { t.Fatalf("Failed to create API handler: %v", err) } diff --git a/management/server/integrated_validator/interface.go b/management/server/integrations/integrated_validator/interface.go similarity index 100% rename from management/server/integrated_validator/interface.go rename to management/server/integrations/integrated_validator/interface.go diff --git a/management/server/integrations/port_forwarding/controller.go b/management/server/integrations/port_forwarding/controller.go new file mode 100644 index 000000000..6390868fe --- /dev/null +++ b/management/server/integrations/port_forwarding/controller.go @@ -0,0 +1,27 @@ +package port_forwarding + +import ( + "context" + + nbtypes "github.com/netbirdio/netbird/management/server/types" +) + +type Controller interface { + SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string) + GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error) +} + +type ControllerMock struct { +} + +func NewControllerMock() *ControllerMock { + return &ControllerMock{} +} + +func (c *ControllerMock) SendUpdate(ctx context.Context, accountID string, affectedProxyID string, affectedPeerIDs []string) { + // noop +} + +func (c *ControllerMock) GetProxyNetworkMaps(ctx context.Context, accountID string) (map[string]*nbtypes.NetworkMap, error) { + return make(map[string]*nbtypes.NetworkMap), nil +} diff --git a/management/server/management_proto_test.go b/management/server/management_proto_test.go index 0df2462f4..7b578cc23 100644 --- a/management/server/management_proto_test.go +++ b/management/server/management_proto_test.go @@ -23,6 +23,7 @@ import ( "github.com/netbirdio/netbird/formatter" mgmtProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -430,7 +431,7 @@ func startManagementForTest(t *testing.T, testFile string, config *Config) (*grp require.NoError(t, err) accountManager, err := BuildManager(ctx, store, peersUpdateManager, nil, "", "netbird.selfhosted", - eventStore, nil, false, MocIntegratedValidator{}, metrics) + eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) if err != nil { cleanup() diff --git a/management/server/management_test.go b/management/server/management_test.go index cfa2c138f..cd484e023 100644 --- a/management/server/management_test.go +++ b/management/server/management_test.go @@ -22,6 +22,7 @@ import ( mgmtProto "github.com/netbirdio/netbird/management/proto" "github.com/netbirdio/netbird/management/server" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/settings" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -507,7 +508,7 @@ func startServer(config *server.Config, dataDir string, testFile string) (*grpc. log.Fatalf("failed creating metrics: %v", err) } - accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, server.MocIntegratedValidator{}, metrics) + accountManager, err := server.BuildManager(context.Background(), store, peersUpdateManager, nil, "", "netbird.selfhosted", eventStore, nil, false, server.MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) if err != nil { log.Fatalf("failed creating a manager: %v", err) } diff --git a/management/server/nameserver_test.go b/management/server/nameserver_test.go index 0743db513..c699e1444 100644 --- a/management/server/nameserver_test.go +++ b/management/server/nameserver_test.go @@ -11,6 +11,7 @@ import ( nbdns "github.com/netbirdio/netbird/dns" "github.com/netbirdio/netbird/management/server/activity" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/store" "github.com/netbirdio/netbird/management/server/telemetry" @@ -770,7 +771,7 @@ func createNSManager(t *testing.T) (*DefaultAccountManager, error) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics) + return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) } func createNSStore(t *testing.T) (store.Store, error) { diff --git a/management/server/peer.go b/management/server/peer.go index efd9c64e3..ef40baa27 100644 --- a/management/server/peer.go +++ b/management/server/peer.go @@ -430,7 +430,21 @@ func (am *DefaultAccountManager) GetNetworkMap(ctx context.Context, peerID strin return nil, err } customZone := account.GetPeersCustomZone(ctx, am.dnsDomain) - return account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil), nil + + proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, account.Id) + if err != nil { + log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err) + return nil, err + } + + networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, validatedPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil) + + proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] + if ok { + networkMap.Merge(proxyNetworkMap) + } + + return networkMap, nil } // GetPeerNetwork returns the Network for a given peer @@ -1005,7 +1019,21 @@ func (am *DefaultAccountManager) getValidatedPeerWithMap(ctx context.Context, is } customZone := account.GetPeersCustomZone(ctx, am.dnsDomain) - return peer, account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics()), postureChecks, nil + + proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, account.Id) + if err != nil { + log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err) + return nil, nil, nil, err + } + + networkMap := account.GetPeerNetworkMap(ctx, peer.ID, customZone, approvedPeersMap, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), am.metrics.AccountManagerMetrics()) + + proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] + if ok { + networkMap.Merge(proxyNetworkMap) + } + + return peer, networkMap, postureChecks, nil } func (am *DefaultAccountManager) handleExpiredPeer(ctx context.Context, transaction store.Store, user *types.User, peer *nbpeer.Peer) error { @@ -1145,6 +1173,12 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account resourcePolicies := account.GetResourcePoliciesMap() routers := account.GetResourceRoutersMap() + proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, accountID) + if err != nil { + log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err) + return + } + for _, peer := range account.Peers { if !am.peersUpdateManager.HasChannel(peer.ID) { log.WithContext(ctx).Tracef("peer %s doesn't have a channel, skipping network map update", peer.ID) @@ -1164,11 +1198,19 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account } remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, p.ID, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + + proxyNetworkMap, ok := proxyNetworkMaps[p.ID] + if ok { + remotePeerNetworkMap.Merge(proxyNetworkMap) + } + update := toSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled) am.peersUpdateManager.SendUpdate(ctx, p.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap}) }(peer) } + // + wg.Wait() if am.metrics != nil { am.metrics.AccountManagerMetrics().CountUpdateAccountPeersDuration(time.Since(start)) @@ -1212,7 +1254,19 @@ func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountI return } + proxyNetworkMaps, err := am.proxyController.GetProxyNetworkMaps(ctx, accountId) + if err != nil { + log.WithContext(ctx).Errorf("failed to get proxy network maps: %v", err) + return + } + remotePeerNetworkMap := account.GetPeerNetworkMap(ctx, peerId, customZone, approvedPeersMap, resourcePolicies, routers, am.metrics.AccountManagerMetrics()) + + proxyNetworkMap, ok := proxyNetworkMaps[peer.ID] + if ok { + remotePeerNetworkMap.Merge(proxyNetworkMap) + } + update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, am.GetDNSDomain(), postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled) am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap}) } diff --git a/management/server/peer_test.go b/management/server/peer_test.go index 40f8d15d5..49bd043de 100644 --- a/management/server/peer_test.go +++ b/management/server/peer_test.go @@ -19,6 +19,7 @@ import ( "github.com/stretchr/testify/require" "golang.zx2c4.com/wireguard/wgctrl/wgtypes" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" "github.com/netbirdio/netbird/management/server/util" resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" @@ -1079,6 +1080,20 @@ func TestToSyncResponse(t *testing.T) { FirewallRules: []*types.FirewallRule{ {PeerIP: "192.168.1.2", Direction: types.FirewallRuleDirectionIN, Action: string(types.PolicyTrafficActionAccept), Protocol: string(types.PolicyRuleProtocolTCP), Port: "80"}, }, + ForwardingRules: []*types.ForwardingRule{ + { + RuleProtocol: "tcp", + DestinationPorts: types.RulePortRange{ + Start: 1000, + End: 2000, + }, + TranslatedAddress: net.IPv4(192, 168, 1, 2), + TranslatedPorts: types.RulePortRange{ + Start: 11000, + End: 12000, + }, + }, + }, } dnsName := "example.com" checks := []*posture.Checks{ @@ -1170,6 +1185,14 @@ func TestToSyncResponse(t *testing.T) { // assert posture checks assert.Equal(t, 1, len(response.Checks)) assert.Equal(t, "/usr/bin/netbird", response.Checks[0].Files[0]) + // assert network map ForwardingRules + assert.Equal(t, 1, len(response.NetworkMap.ForwardingRules)) + assert.Equal(t, proto.RuleProtocol_TCP, response.NetworkMap.ForwardingRules[0].Protocol) + assert.Equal(t, uint32(1000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().Start) + assert.Equal(t, uint32(2000), response.NetworkMap.ForwardingRules[0].DestinationPort.GetRange().End) + assert.Equal(t, net.IPv4(192, 168, 1, 2).To4(), net.IP(response.NetworkMap.ForwardingRules[0].TranslatedAddress)) + assert.Equal(t, uint32(11000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().Start) + assert.Equal(t, uint32(12000), response.NetworkMap.ForwardingRules[0].TranslatedPort.GetRange().End) } func Test_RegisterPeerByUser(t *testing.T) { @@ -1188,7 +1211,7 @@ func Test_RegisterPeerByUser(t *testing.T) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) assert.NoError(t, err) - am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics) + am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) assert.NoError(t, err) existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" @@ -1252,7 +1275,7 @@ func Test_RegisterPeerBySetupKey(t *testing.T) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) assert.NoError(t, err) - am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics) + am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) assert.NoError(t, err) existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" @@ -1319,7 +1342,7 @@ func Test_RegisterPeerRollbackOnFailure(t *testing.T) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) assert.NoError(t, err) - am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics) + am, err := BuildManager(context.Background(), s, NewPeersUpdateManager(nil), nil, "", "netbird.cloud", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) assert.NoError(t, err) existingAccountID := "bf1c8084-ba50-4ce7-9439-34653001fc3b" diff --git a/management/server/peers/manager.go b/management/server/peers/manager.go new file mode 100644 index 000000000..6a188e8b7 --- /dev/null +++ b/management/server/peers/manager.go @@ -0,0 +1,54 @@ +package peers + +import ( + "context" + "fmt" + + "github.com/netbirdio/netbird/management/server/peer" + "github.com/netbirdio/netbird/management/server/permissions" + "github.com/netbirdio/netbird/management/server/status" + "github.com/netbirdio/netbird/management/server/store" +) + +type Manager interface { + GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) + GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) +} + +type managerImpl struct { + store store.Store + permissionsManager permissions.Manager +} + +func NewManager(store store.Store, permissionsManager permissions.Manager) Manager { + return &managerImpl{ + store: store, + permissionsManager: permissionsManager, + } +} + +func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) { + allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read) + if err != nil { + return nil, fmt.Errorf("failed to validate user permissions: %w", err) + } + + if !allowed { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetPeerByID(ctx, store.LockingStrengthShare, accountID, peerID) +} + +func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) { + allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, permissions.Peers, permissions.Read) + if err != nil { + return nil, fmt.Errorf("failed to validate user permissions: %w", err) + } + + if !allowed { + return nil, status.NewPermissionDeniedError() + } + + return m.store.GetAccountPeers(ctx, store.LockingStrengthShare, accountID) +} diff --git a/management/server/route_test.go b/management/server/route_test.go index 1c5c56f60..7169316d4 100644 --- a/management/server/route_test.go +++ b/management/server/route_test.go @@ -13,6 +13,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/netbirdio/netbird/management/server/integrations/port_forwarding" resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types" routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types" networkTypes "github.com/netbirdio/netbird/management/server/networks/types" @@ -1257,7 +1258,7 @@ func createRouterManager(t *testing.T) (*DefaultAccountManager, error) { metrics, err := telemetry.NewDefaultAppMetrics(context.Background()) require.NoError(t, err) - return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics) + return BuildManager(context.Background(), store, NewPeersUpdateManager(nil), nil, "", "netbird.selfhosted", eventStore, nil, false, MocIntegratedValidator{}, metrics, port_forwarding.NewControllerMock()) } func createRouterStore(t *testing.T) (store.Store, error) { diff --git a/management/server/types/firewall_rule.go b/management/server/types/firewall_rule.go index 4e405152c..b96ccea42 100644 --- a/management/server/types/firewall_rule.go +++ b/management/server/types/firewall_rule.go @@ -33,6 +33,9 @@ type FirewallRule struct { // Port of the traffic Port string + + // PortRange represents the range of ports for a firewall rule + PortRange RulePortRange } // IsEqual checks if two firewall rules are equal. diff --git a/management/server/types/network.go b/management/server/types/network.go index d1fccd149..26153a7d5 100644 --- a/management/server/types/network.go +++ b/management/server/types/network.go @@ -10,6 +10,7 @@ import ( "github.com/rs/xid" nbdns "github.com/netbirdio/netbird/dns" + "github.com/netbirdio/netbird/management/proto" nbpeer "github.com/netbirdio/netbird/management/server/peer" "github.com/netbirdio/netbird/management/server/status" "github.com/netbirdio/netbird/route" @@ -33,6 +34,52 @@ type NetworkMap struct { OfflinePeers []*nbpeer.Peer FirewallRules []*FirewallRule RoutesFirewallRules []*RouteFirewallRule + ForwardingRules []*ForwardingRule +} + +func (nm *NetworkMap) Merge(other *NetworkMap) { + nm.Peers = append(nm.Peers, other.Peers...) + nm.Routes = append(nm.Routes, other.Routes...) + nm.OfflinePeers = append(nm.OfflinePeers, other.OfflinePeers...) + nm.FirewallRules = append(nm.FirewallRules, other.FirewallRules...) + nm.RoutesFirewallRules = append(nm.RoutesFirewallRules, other.RoutesFirewallRules...) + nm.ForwardingRules = append(nm.ForwardingRules, other.ForwardingRules...) +} + +type ForwardingRule struct { + RuleProtocol string + DestinationPorts RulePortRange + TranslatedAddress net.IP + TranslatedPorts RulePortRange +} + +func (f *ForwardingRule) ToProto() *proto.ForwardingRule { + var protocol proto.RuleProtocol + switch f.RuleProtocol { + case "icmp": + protocol = proto.RuleProtocol_ICMP + case "tcp": + protocol = proto.RuleProtocol_TCP + case "udp": + protocol = proto.RuleProtocol_UDP + case "all": + protocol = proto.RuleProtocol_ALL + default: + protocol = proto.RuleProtocol_UNKNOWN + } + return &proto.ForwardingRule{ + Protocol: protocol, + DestinationPort: f.DestinationPorts.ToProto(), + TranslatedAddress: ipToBytes(f.TranslatedAddress), + TranslatedPort: f.TranslatedPorts.ToProto(), + } +} + +func ipToBytes(ip net.IP) []byte { + if ip4 := ip.To4(); ip4 != nil { + return ip4 + } + return ip.To16() } type Network struct { diff --git a/management/server/types/policyrule.go b/management/server/types/policyrule.go index 721621a4b..fc34a0b6f 100644 --- a/management/server/types/policyrule.go +++ b/management/server/types/policyrule.go @@ -1,5 +1,9 @@ package types +import ( + "github.com/netbirdio/netbird/management/proto" +) + // PolicyUpdateOperationType operation type type PolicyUpdateOperationType int @@ -18,6 +22,17 @@ type RulePortRange struct { End uint16 } +func (r *RulePortRange) ToProto() *proto.PortInfo { + return &proto.PortInfo{ + PortSelection: &proto.PortInfo_Range_{ + Range: &proto.PortInfo_Range{ + Start: uint32(r.Start), + End: uint32(r.End), + }, + }, + } +} + // PolicyRule is the metadata of the policy type PolicyRule struct { // ID of the policy rule