Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion client/internal/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ import (
"github.com/netbirdio/netbird/client/firewall"
firewallManager "github.com/netbirdio/netbird/client/firewall/manager"
"github.com/netbirdio/netbird/client/iface"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/device"
nbnetstack "github.com/netbirdio/netbird/client/iface/netstack"
"github.com/netbirdio/netbird/client/iface/udpmux"
"github.com/netbirdio/netbird/client/internal/acl"
"github.com/netbirdio/netbird/client/internal/debug"
Expand Down
26 changes: 25 additions & 1 deletion management/internals/server/boot.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,31 @@ func (s *BaseServer) EventStore() activity.Store {

func (s *BaseServer) APIHandler() http.Handler {
return Create(s, func() http.Handler {
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.ReverseProxyManager(), s.ReverseProxyDomainManager(), s.AccessLogsManager(), s.ReverseProxyGRPCServer(), s.Config.ReverseProxy.TrustedHTTPProxies)
httpAPIHandler, err := nbhttp.NewAPIHandler(context.Background(), nbhttp.APIHandlerDeps{
AccountManager: s.AccountManager(),
NetworksManager: s.NetworksManager(),
ResourceManager: s.ResourcesManager(),
RouterManager: s.RoutesManager(),
GroupsManager: s.GroupsManager(),
LocationManager: s.GeoLocationManager(),
AuthManager: s.AuthManager(),
AppMetrics: s.Metrics(),
IntegratedValidator: s.IntegratedValidator(),
ProxyController: s.ProxyController(),
PermissionsManager: s.PermissionsManager(),
PeersManager: s.PeersManager(),
SettingsManager: s.SettingsManager(),
ZonesManager: s.ZonesManager(),
RecordsManager: s.RecordsManager(),
NetworkMapController: s.NetworkMapController(),
IdpManager: s.IdpManager(),
ReverseProxyManager: s.ReverseProxyManager(),
ReverseProxyDomainManager: s.ReverseProxyDomainManager(),
ReverseProxyAccessLogs: s.AccessLogsManager(),
ProxyGRPCServer: s.ReverseProxyGRPCServer(),
TrustedHTTPProxies: s.Config.ReverseProxy.TrustedHTTPProxies,
EnableDeploymentMaturity: s.Config.EnableDeploymentMaturity,
})
if err != nil {
log.Fatalf("failed to create API handler: %v", err)
}
Expand Down
5 changes: 5 additions & 0 deletions management/internals/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ type Config struct {
// disable default all-to-all policy
DisableDefaultPolicy bool

// EnableDeploymentMaturity enables computation of an optional
// informational deployment_maturity field in the account response.
// When disabled, the field is omitted entirely.
EnableDeploymentMaturity bool

// EmbeddedIdP contains configuration for the embedded Dex OIDC provider.
// When set, Dex will be embedded in the management server and serve requests at /oauth2/
EmbeddedIdP *idp.EmbeddedIdPConfig
Expand Down
197 changes: 136 additions & 61 deletions management/server/http/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,32 @@ import (
"github.com/netbirdio/netbird/management/server/telemetry"
)

type APIHandlerDeps struct {
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
ZonesManager zones.Manager
RecordsManager records.Manager
NetworkMapController network_map.Controller
IdpManager idpmanager.Manager
ReverseProxyManager reverseproxy.Manager
ReverseProxyDomainManager *manager.Manager
ReverseProxyAccessLogs accesslogs.Manager
ProxyGRPCServer *nbgrpc.ProxyServiceServer
TrustedHTTPProxies []netip.Prefix
EnableDeploymentMaturity bool
}

const (
apiPrefix = "/api"
rateLimitingEnabledKey = "NB_API_RATE_LIMITING_ENABLED"
Expand All @@ -73,27 +99,63 @@ const (
)

// NewAPIHandler creates the Management service HTTP API handler registering all the available endpoints.
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, reverseProxyManager reverseproxy.Manager, reverseProxyDomainManager *manager.Manager, reverseProxyAccessLogsManager accesslogs.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, trustedHTTPProxies []netip.Prefix) (http.Handler, error) {
func NewAPIHandler(ctx context.Context, deps APIHandlerDeps) (http.Handler, error) {
if err := registerBypassPaths(apiPrefix); err != nil {
return nil, err
}

rootRouter := mux.NewRouter()
prefix := apiPrefix
router := rootRouter.PathPrefix(prefix).Subrouter()

setupMiddleware(router, deps)

if err := registerIntegrations(ctx, router, deps); err != nil {
return nil, err
}

embeddedIdP, embeddedIdpEnabled := deps.IdpManager.(*idpmanager.EmbeddedIdPManager)
instanceManager, err := nbinstance.NewManager(ctx, deps.AccountManager.GetStore(), embeddedIdP)
if err != nil {
return nil, fmt.Errorf("failed to create instance manager: %w", err)
}

registerCoreEndpoints(router, deps, instanceManager)
registerReverseProxyAndOAuth(router, deps)

if embeddedIdpEnabled {
corsMiddleware := cors.AllowAll()
rootRouter.PathPrefix("/oauth2").Handler(corsMiddleware.Handler(embeddedIdP.Handler()))
}

return rootRouter, nil
}

// Register bypass paths for unauthenticated endpoints
if err := bypass.AddBypassPath("/api/instance"); err != nil {
return nil, fmt.Errorf("failed to add bypass path: %w", err)
func registerBypassPaths(prefix string) error {
if err := bypass.AddBypassPath(prefix + "/instance"); err != nil {
return fmt.Errorf("failed to add bypass path: %w", err)
}
if err := bypass.AddBypassPath("/api/setup"); err != nil {
return nil, fmt.Errorf("failed to add bypass path: %w", err)

if err := bypass.AddBypassPath(prefix + "/setup"); err != nil {
return fmt.Errorf("failed to add bypass path: %w", err)
}
// Public invite endpoints (tokens start with nbi_)
if err := bypass.AddBypassPath("/api/users/invites/nbi_*"); err != nil {
return nil, fmt.Errorf("failed to add bypass path: %w", err)

if err := bypass.AddBypassPath(prefix + "/users/invites/nbi_*"); err != nil {
return fmt.Errorf("failed to add bypass path: %w", err)
}
if err := bypass.AddBypassPath("/api/users/invites/nbi_*/accept"); err != nil {
return nil, fmt.Errorf("failed to add bypass path: %w", err)

if err := bypass.AddBypassPath(prefix + "/users/invites/nbi_*/accept"); err != nil {
return fmt.Errorf("failed to add bypass path: %w", err)
}
// OAuth callback for proxy authentication

if err := bypass.AddBypassPath(types.ProxyCallbackEndpointFull); err != nil {
return nil, fmt.Errorf("failed to add bypass path: %w", err)
return fmt.Errorf("failed to add bypass path: %w", err)
}

return nil
}

func setupMiddleware(router *mux.Router, deps APIHandlerDeps) {
var rateLimitingConfig *middleware.RateLimiterConfig
if os.Getenv(rateLimitingEnabledKey) == "true" {
rpm := 6
Expand Down Expand Up @@ -125,68 +187,81 @@ func NewAPIHandler(ctx context.Context, accountManager account.Manager, networks
}

authMiddleware := middleware.NewAuthMiddleware(
authManager,
accountManager.GetAccountIDFromUserAuth,
accountManager.SyncUserJWTGroups,
accountManager.GetUserFromUserAuth,
deps.AuthManager,
deps.AccountManager.GetAccountIDFromUserAuth,
deps.AccountManager.SyncUserJWTGroups,
deps.AccountManager.GetUserFromUserAuth,
rateLimitingConfig,
appMetrics.GetMeter(),
deps.AppMetrics.GetMeter(),
)

corsMiddleware := cors.AllowAll()

rootRouter := mux.NewRouter()
metricsMiddleware := appMetrics.HTTPMiddleware()

prefix := apiPrefix
router := rootRouter.PathPrefix(prefix).Subrouter()
metricsMiddleware := deps.AppMetrics.HTTPMiddleware()

router.Use(metricsMiddleware.Handler, corsMiddleware.Handler, authMiddleware.Handler)
}

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)
func registerIntegrations(ctx context.Context, router *mux.Router, deps APIHandlerDeps) error {
prefix := apiPrefix
if _, err := integrations.RegisterHandlers(
ctx,
prefix,
router,
deps.AccountManager,
deps.IntegratedValidator,
deps.AppMetrics.GetMeter(),
deps.PermissionsManager,
deps.PeersManager,
deps.ProxyController,
deps.SettingsManager,
); err != nil {
return 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 {
return nil, fmt.Errorf("failed to create instance manager: %w", err)
}
return nil
}

accounts.AddEndpoints(accountManager, settingsManager, router)
peers.AddEndpoints(accountManager, router, networkMapController, permissionsManager)
users.AddEndpoints(accountManager, router)
users.AddInvitesEndpoints(accountManager, router)
users.AddPublicInvitesEndpoints(accountManager, router)
setup_keys.AddEndpoints(accountManager, router)
policies.AddEndpoints(accountManager, LocationManager, router)
policies.AddPostureCheckEndpoints(accountManager, LocationManager, router)
policies.AddLocationsEndpoints(accountManager, LocationManager, permissionsManager, router)
groups.AddEndpoints(accountManager, router)
routes.AddEndpoints(accountManager, router)
dns.AddEndpoints(accountManager, router)
events.AddEndpoints(accountManager, router)
networks.AddEndpoints(networksManager, resourceManager, routerManager, groupsManager, accountManager, router)
zonesManager.RegisterEndpoints(router, zManager)
recordsManager.RegisterEndpoints(router, rManager)
idp.AddEndpoints(accountManager, router)
func registerCoreEndpoints(router *mux.Router, deps APIHandlerDeps, instanceManager nbinstance.Manager) {
accounts.AddEndpoints(deps.AccountManager, deps.SettingsManager, router, deps.EnableDeploymentMaturity)
peers.AddEndpoints(deps.AccountManager, router, deps.NetworkMapController, deps.PermissionsManager)
users.AddEndpoints(deps.AccountManager, router)
users.AddInvitesEndpoints(deps.AccountManager, router)
users.AddPublicInvitesEndpoints(deps.AccountManager, router)
setup_keys.AddEndpoints(deps.AccountManager, router)
policies.AddEndpoints(deps.AccountManager, deps.LocationManager, router)
policies.AddPostureCheckEndpoints(deps.AccountManager, deps.LocationManager, router)
policies.AddLocationsEndpoints(deps.AccountManager, deps.LocationManager, deps.PermissionsManager, router)
groups.AddEndpoints(deps.AccountManager, router)
routes.AddEndpoints(deps.AccountManager, router)
dns.AddEndpoints(deps.AccountManager, router)
events.AddEndpoints(deps.AccountManager, router)
networks.AddEndpoints(
deps.NetworksManager,
deps.ResourceManager,
deps.RouterManager,
deps.GroupsManager,
deps.AccountManager,
router,
)
zonesManager.RegisterEndpoints(router, deps.ZonesManager)
recordsManager.RegisterEndpoints(router, deps.RecordsManager)
idp.AddEndpoints(deps.AccountManager, router)
instance.AddEndpoints(instanceManager, router)
instance.AddVersionEndpoint(instanceManager, router)
if reverseProxyManager != nil && reverseProxyDomainManager != nil {
reverseproxymanager.RegisterEndpoints(reverseProxyManager, *reverseProxyDomainManager, reverseProxyAccessLogsManager, router)
}
}

// Register OAuth callback handler for proxy authentication
if proxyGRPCServer != nil {
oauthHandler := proxy.NewAuthCallbackHandler(proxyGRPCServer, trustedHTTPProxies)
oauthHandler.RegisterEndpoints(router)
func registerReverseProxyAndOAuth(router *mux.Router, deps APIHandlerDeps) {
if deps.ReverseProxyManager != nil && deps.ReverseProxyDomainManager != nil {
reverseproxymanager.RegisterEndpoints(
deps.ReverseProxyManager,
*deps.ReverseProxyDomainManager,
deps.ReverseProxyAccessLogs,
router,
)
}
Comment on lines 254 to 261
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find RegisterEndpoints function in reverseproxymanager
rg -n "func RegisterEndpoints" --type=go -A 20 -B 2

Repository: netbirdio/netbird

Length of output: 11526


deps.ReverseProxyAccessLogs must be checked for nil before passing to RegisterEndpoints.

The guard on line 202 ensures ReverseProxyManager and ReverseProxyDomainManager are non-nil, but deps.ReverseProxyAccessLogs is passed unchecked to RegisterEndpoints, which passes it directly to accesslogsmanager.RegisterEndpoints (line 32 of manager/api.go). The accesslogs handler stores this manager without validation, and calling the /events/proxy endpoint with a nil manager will panic when GetAllAccessLogs is invoked.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@management/server/http/handler.go` around lines 202 - 204, The call to
reverseproxymanager.RegisterEndpoints passes deps.ReverseProxyAccessLogs without
nil-checking, which can lead to a panic when accesslogsmanager.GetAllAccessLogs
is later invoked; update the guard in the handler so that RegisterEndpoints is
only called when deps.ReverseProxyManager, deps.ReverseProxyDomainManager, and
deps.ReverseProxyAccessLogs are all non-nil (or alternatively pass a safe
non-nil stub), i.e. add deps.ReverseProxyAccessLogs != nil to the existing if
condition that surrounds reverseproxymanager.RegisterEndpoints so the access
logs manager is validated before being stored/used.


// Mount embedded IdP handler at /oauth2 path if configured
if embeddedIdpEnabled {
rootRouter.PathPrefix("/oauth2").Handler(corsMiddleware.Handler(embeddedIdP.Handler()))
if deps.ProxyGRPCServer != nil {
oauthHandler := proxy.NewAuthCallbackHandler(deps.ProxyGRPCServer, deps.TrustedHTTPProxies)
oauthHandler.RegisterEndpoints(router)
}

return rootRouter, nil
}
Loading