mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-21 21:09:07 +02:00
rewrite peer creation and network map calc [WIP]
This commit is contained in:
@@ -180,6 +180,8 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
groupIDToUserIDs := account.GetActiveGroupUsers()
|
||||
exposedServices := account.GetExposedServicesMap()
|
||||
proxyPeers := account.GetProxyPeers()
|
||||
|
||||
if c.experimentalNetworkMap(accountID) {
|
||||
c.initNetworkMapBuilderIfNeeded(account, approvedPeersMap)
|
||||
@@ -232,7 +234,7 @@ func (c *Controller) sendUpdateAccountPeers(ctx context.Context, accountID strin
|
||||
if c.experimentalNetworkMap(accountID) {
|
||||
remotePeerNetworkMap = c.getPeerNetworkMapExp(ctx, p.AccountID, p.ID, approvedPeersMap, peersCustomZone, accountZones, c.accountManagerMetrics)
|
||||
} else {
|
||||
remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs)
|
||||
remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, p.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs, exposedServices, proxyPeers)
|
||||
}
|
||||
|
||||
c.metrics.CountCalcPeerNetworkMapDuration(time.Since(start))
|
||||
@@ -353,7 +355,7 @@ func (c *Controller) UpdateAccountPeer(ctx context.Context, accountId string, pe
|
||||
if c.experimentalNetworkMap(accountId) {
|
||||
remotePeerNetworkMap = c.getPeerNetworkMapExp(ctx, peer.AccountID, peer.ID, approvedPeersMap, peersCustomZone, accountZones, c.accountManagerMetrics)
|
||||
} else {
|
||||
remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, peerId, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs)
|
||||
remotePeerNetworkMap = account.GetPeerNetworkMap(ctx, peerId, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, groupIDToUserIDs, account.GetExposedServicesMap(), account.GetProxyPeers())
|
||||
}
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
@@ -469,7 +471,7 @@ func (c *Controller) GetValidatedPeerWithMap(ctx context.Context, isRequiresAppr
|
||||
} else {
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, account.GetActiveGroupUsers())
|
||||
networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, approvedPeersMap, resourcePolicies, routers, c.accountManagerMetrics, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers())
|
||||
}
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
@@ -842,7 +844,7 @@ func (c *Controller) GetNetworkMap(ctx context.Context, peerID string) (*types.N
|
||||
} else {
|
||||
resourcePolicies := account.GetResourcePoliciesMap()
|
||||
routers := account.GetResourceRoutersMap()
|
||||
networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, validatedPeers, resourcePolicies, routers, nil, account.GetActiveGroupUsers())
|
||||
networkMap = account.GetPeerNetworkMap(ctx, peer.ID, peersCustomZone, accountZones, validatedPeers, resourcePolicies, routers, nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers())
|
||||
}
|
||||
|
||||
proxyNetworkMap, ok := proxyNetworkMaps[peer.ID]
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/rs/xid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/controllers/network_map"
|
||||
@@ -32,6 +33,7 @@ type Manager interface {
|
||||
SetIntegratedPeerValidator(integratedPeerValidator integrated_validator.IntegratedValidator)
|
||||
SetAccountManager(accountManager account.Manager)
|
||||
GetPeerID(ctx context.Context, peerKey string) (string, error)
|
||||
CreateProxyPeer(ctx context.Context, accountID string, peerKey string) error
|
||||
}
|
||||
|
||||
type managerImpl struct {
|
||||
@@ -182,3 +184,33 @@ func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs
|
||||
func (m *managerImpl) GetPeerID(ctx context.Context, peerKey string) (string, error) {
|
||||
return m.store.GetPeerIDByKey(ctx, store.LockingStrengthNone, peerKey)
|
||||
}
|
||||
|
||||
func (m *managerImpl) CreateProxyPeer(ctx context.Context, accountID string, peerKey string) error {
|
||||
existingPeerID, err := m.store.GetPeerIDByKey(ctx, store.LockingStrengthNone, peerKey)
|
||||
if err == nil && existingPeerID != "" {
|
||||
// Peer already exists
|
||||
return nil
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("proxy-%s", xid.New().String())
|
||||
peer := &peer.Peer{
|
||||
Ephemeral: true,
|
||||
ProxyEmbedded: true,
|
||||
Name: name,
|
||||
Key: peerKey,
|
||||
LoginExpirationEnabled: false,
|
||||
InactivityExpirationEnabled: false,
|
||||
Meta: peer.PeerSystemMeta{
|
||||
Hostname: name,
|
||||
GoOS: "proxy",
|
||||
OS: "proxy",
|
||||
},
|
||||
}
|
||||
|
||||
_, _, _, err = m.accountManager.AddPeer(ctx, accountID, "", "", peer, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create proxy peer: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/rs/xid"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/sessionkey"
|
||||
nbgrpc "github.com/netbirdio/netbird/management/internals/shared/grpc"
|
||||
@@ -17,7 +14,6 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
"github.com/netbirdio/netbird/shared/management/status"
|
||||
)
|
||||
|
||||
@@ -26,14 +22,16 @@ type managerImpl struct {
|
||||
accountManager account.Manager
|
||||
permissionsManager permissions.Manager
|
||||
proxyGRPCServer *nbgrpc.ProxyServiceServer
|
||||
tokenStore *nbgrpc.OneTimeTokenStore
|
||||
}
|
||||
|
||||
func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer) reverseproxy.Manager {
|
||||
func NewManager(store store.Store, accountManager account.Manager, permissionsManager permissions.Manager, proxyGRPCServer *nbgrpc.ProxyServiceServer, tokenStore *nbgrpc.OneTimeTokenStore) reverseproxy.Manager {
|
||||
return &managerImpl{
|
||||
store: store,
|
||||
accountManager: accountManager,
|
||||
permissionsManager: permissionsManager,
|
||||
proxyGRPCServer: proxyGRPCServer,
|
||||
tokenStore: tokenStore,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,56 +104,14 @@ func (m *managerImpl) CreateReverseProxy(ctx context.Context, accountID, userID
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := m.tokenStore.GenerateToken(accountID, reverseProxy.ID, 5*time.Minute)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate authentication token: %w", err)
|
||||
}
|
||||
|
||||
m.accountManager.StoreEvent(ctx, userID, reverseProxy.ID, accountID, activity.ReverseProxyCreated, reverseProxy.EventMeta())
|
||||
|
||||
// TODO: refactor to avoid policy and group creation here
|
||||
group := &types.Group{
|
||||
ID: xid.New().String(),
|
||||
Name: reverseProxy.Name,
|
||||
Issued: types.GroupIssuedAPI,
|
||||
}
|
||||
err = m.accountManager.CreateGroup(ctx, accountID, activity.SystemInitiator, group)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create default group for reverse proxy: %w", err)
|
||||
}
|
||||
|
||||
for _, target := range reverseProxy.Targets {
|
||||
policyID := uuid.New().String()
|
||||
// TODO: support other resource types in the future
|
||||
targetType := types.ResourceTypePeer
|
||||
if target.TargetType == "resource" {
|
||||
targetType = types.ResourceTypeHost
|
||||
}
|
||||
policyRule := &types.PolicyRule{
|
||||
ID: policyID,
|
||||
PolicyID: policyID,
|
||||
Name: reverseProxy.Name,
|
||||
Enabled: true,
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
Protocol: types.PolicyRuleProtocolALL,
|
||||
Sources: []string{group.ID},
|
||||
DestinationResource: types.Resource{Type: targetType, ID: target.TargetId},
|
||||
Bidirectional: false,
|
||||
}
|
||||
|
||||
policy := &types.Policy{
|
||||
AccountID: accountID,
|
||||
Name: reverseProxy.Name,
|
||||
Enabled: true,
|
||||
Rules: []*types.PolicyRule{policyRule},
|
||||
}
|
||||
_, err = m.accountManager.SavePolicy(ctx, accountID, activity.SystemInitiator, policy, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create default policy for reverse proxy: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
key, err := m.accountManager.CreateSetupKey(ctx, accountID, reverseProxy.Name, types.SetupKeyReusable, 0, []string{group.ID}, 0, activity.SystemInitiator, true, false)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create setup key for reverse proxy: %w", err)
|
||||
}
|
||||
|
||||
m.proxyGRPCServer.SendReverseProxyUpdate(reverseProxy.ToProtoMapping(reverseproxy.Create, key.Key, m.proxyGRPCServer.GetOIDCValidationConfig()))
|
||||
m.proxyGRPCServer.SendReverseProxyUpdate(reverseProxy.ToProtoMapping(reverseproxy.Create, token, m.proxyGRPCServer.GetOIDCValidationConfig()))
|
||||
|
||||
return reverseProxy, nil
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ const (
|
||||
StatusCertificatePending ProxyStatus = "certificate_pending"
|
||||
StatusCertificateFailed ProxyStatus = "certificate_failed"
|
||||
StatusError ProxyStatus = "error"
|
||||
|
||||
TargetTypePeer = "peer"
|
||||
TargetTypeResource = "resource"
|
||||
)
|
||||
|
||||
type Target struct {
|
||||
@@ -164,7 +167,7 @@ func (r *ReverseProxy) ToAPIResponse() *api.ReverseProxy {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string, oidcConfig OIDCValidationConfig) *proto.ProxyMapping {
|
||||
func (r *ReverseProxy) ToProtoMapping(operation Operation, authToken string, oidcConfig OIDCValidationConfig) *proto.ProxyMapping {
|
||||
pathMappings := make([]*proto.PathMapping, 0, len(r.Targets))
|
||||
for _, target := range r.Targets {
|
||||
if !target.Enabled {
|
||||
@@ -213,7 +216,7 @@ func (r *ReverseProxy) ToProtoMapping(operation Operation, setupKey string, oidc
|
||||
Id: r.ID,
|
||||
Domain: r.Domain,
|
||||
Path: pathMappings,
|
||||
SetupKey: setupKey,
|
||||
AuthToken: authToken,
|
||||
Auth: auth,
|
||||
AccountId: r.AccountID,
|
||||
}
|
||||
|
||||
@@ -162,7 +162,7 @@ func (s *BaseServer) GRPCServer() *grpc.Server {
|
||||
|
||||
func (s *BaseServer) ReverseProxyGRPCServer() *nbgrpc.ProxyServiceServer {
|
||||
return Create(s, func() *nbgrpc.ProxyServiceServer {
|
||||
proxyService := nbgrpc.NewProxyServiceServer(s.Store(), s.AccountManager(), s.AccessLogsManager(), s.proxyOIDCConfig())
|
||||
proxyService := nbgrpc.NewProxyServiceServer(s.Store(), s.AccessLogsManager(), s.ProxyTokenStore(), s.proxyOIDCConfig(), s.PeersManager())
|
||||
s.AfterInit(func(s *BaseServer) {
|
||||
proxyService.SetProxyManager(s.ReverseProxyManager())
|
||||
})
|
||||
@@ -191,6 +191,14 @@ func (s *BaseServer) proxyOIDCConfig() nbgrpc.ProxyOIDCConfig {
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BaseServer) ProxyTokenStore() *nbgrpc.OneTimeTokenStore {
|
||||
return Create(s, func() *nbgrpc.OneTimeTokenStore {
|
||||
tokenStore := nbgrpc.NewOneTimeTokenStore(1 * time.Minute)
|
||||
log.Info("One-time token store initialized for proxy authentication")
|
||||
return tokenStore
|
||||
})
|
||||
}
|
||||
|
||||
func (s *BaseServer) AccessLogsManager() accesslogs.Manager {
|
||||
return Create(s, func() accesslogs.Manager {
|
||||
accessLogManager := accesslogsmanager.NewManager(s.Store(), s.PermissionsManager(), s.GeoLocationManager())
|
||||
|
||||
@@ -180,7 +180,7 @@ func (s *BaseServer) RecordsManager() records.Manager {
|
||||
|
||||
func (s *BaseServer) ReverseProxyManager() reverseproxy.Manager {
|
||||
return Create(s, func() reverseproxy.Manager {
|
||||
return nbreverseproxy.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager(), s.ReverseProxyGRPCServer())
|
||||
return nbreverseproxy.NewManager(s.Store(), s.AccountManager(), s.PermissionsManager(), s.ReverseProxyGRPCServer(), s.ProxyTokenStore())
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// OneTimeTokenStore manages short-lived, single-use authentication tokens
|
||||
// for proxy-to-management RPC authentication. Tokens are generated when
|
||||
// a reverse proxy is created and must be used exactly once by the proxy
|
||||
// to authenticate a subsequent RPC call.
|
||||
type OneTimeTokenStore struct {
|
||||
tokens map[string]*tokenMetadata
|
||||
mu sync.RWMutex
|
||||
cleanup *time.Ticker
|
||||
cleanupDone chan struct{}
|
||||
}
|
||||
|
||||
// tokenMetadata stores information about a one-time token
|
||||
type tokenMetadata struct {
|
||||
ReverseProxyID string
|
||||
AccountID string
|
||||
ExpiresAt time.Time
|
||||
CreatedAt time.Time
|
||||
}
|
||||
|
||||
// NewOneTimeTokenStore creates a new token store with automatic cleanup
|
||||
// of expired tokens. The cleanupInterval determines how often expired
|
||||
// tokens are removed from memory.
|
||||
func NewOneTimeTokenStore(cleanupInterval time.Duration) *OneTimeTokenStore {
|
||||
store := &OneTimeTokenStore{
|
||||
tokens: make(map[string]*tokenMetadata),
|
||||
cleanup: time.NewTicker(cleanupInterval),
|
||||
cleanupDone: make(chan struct{}),
|
||||
}
|
||||
|
||||
// Start background cleanup goroutine
|
||||
go store.cleanupExpired()
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
// GenerateToken creates a new cryptographically secure one-time token
|
||||
// with the specified TTL. The token is associated with a specific
|
||||
// accountID and reverseProxyID for validation purposes.
|
||||
//
|
||||
// Returns the generated token string or an error if random generation fails.
|
||||
func (s *OneTimeTokenStore) GenerateToken(accountID, reverseProxyID string, ttl time.Duration) (string, error) {
|
||||
// Generate 32 bytes (256 bits) of cryptographically secure random data
|
||||
randomBytes := make([]byte, 32)
|
||||
if _, err := rand.Read(randomBytes); err != nil {
|
||||
return "", fmt.Errorf("failed to generate random token: %w", err)
|
||||
}
|
||||
|
||||
// Encode as URL-safe base64 for easy transmission in gRPC
|
||||
token := base64.URLEncoding.EncodeToString(randomBytes)
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.tokens[token] = &tokenMetadata{
|
||||
ReverseProxyID: reverseProxyID,
|
||||
AccountID: accountID,
|
||||
ExpiresAt: time.Now().Add(ttl),
|
||||
CreatedAt: time.Now(),
|
||||
}
|
||||
|
||||
log.Debugf("Generated one-time token for proxy %s in account %s (expires in %s)",
|
||||
reverseProxyID, accountID, ttl)
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
// ValidateAndConsume verifies the token against the provided accountID and
|
||||
// reverseProxyID, checks expiration, and then deletes it to enforce single-use.
|
||||
//
|
||||
// This method uses constant-time comparison to prevent timing attacks.
|
||||
//
|
||||
// Returns nil on success, or an error if:
|
||||
// - Token doesn't exist
|
||||
// - Token has expired
|
||||
// - Account ID doesn't match
|
||||
// - Reverse proxy ID doesn't match
|
||||
func (s *OneTimeTokenStore) ValidateAndConsume(token, accountID, reverseProxyID string) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
metadata, exists := s.tokens[token]
|
||||
if !exists {
|
||||
log.Warnf("Token validation failed: token not found (proxy: %s, account: %s)",
|
||||
reverseProxyID, accountID)
|
||||
return fmt.Errorf("invalid token")
|
||||
}
|
||||
|
||||
// Check expiration
|
||||
if time.Now().After(metadata.ExpiresAt) {
|
||||
delete(s.tokens, token)
|
||||
log.Warnf("Token validation failed: token expired (proxy: %s, account: %s)",
|
||||
reverseProxyID, accountID)
|
||||
return fmt.Errorf("token expired")
|
||||
}
|
||||
|
||||
// Validate account ID using constant-time comparison (prevents timing attacks)
|
||||
if subtle.ConstantTimeCompare([]byte(metadata.AccountID), []byte(accountID)) != 1 {
|
||||
log.Warnf("Token validation failed: account ID mismatch (expected: %s, got: %s)",
|
||||
metadata.AccountID, accountID)
|
||||
return fmt.Errorf("account ID mismatch")
|
||||
}
|
||||
|
||||
// Validate reverse proxy ID using constant-time comparison
|
||||
if subtle.ConstantTimeCompare([]byte(metadata.ReverseProxyID), []byte(reverseProxyID)) != 1 {
|
||||
log.Warnf("Token validation failed: reverse proxy ID mismatch (expected: %s, got: %s)",
|
||||
metadata.ReverseProxyID, reverseProxyID)
|
||||
return fmt.Errorf("reverse proxy ID mismatch")
|
||||
}
|
||||
|
||||
// Delete token immediately to enforce single-use
|
||||
delete(s.tokens, token)
|
||||
|
||||
log.Infof("Token validated and consumed for proxy %s in account %s",
|
||||
reverseProxyID, accountID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupExpired removes expired tokens in the background to prevent memory leaks
|
||||
func (s *OneTimeTokenStore) cleanupExpired() {
|
||||
for {
|
||||
select {
|
||||
case <-s.cleanup.C:
|
||||
s.mu.Lock()
|
||||
now := time.Now()
|
||||
removed := 0
|
||||
for token, metadata := range s.tokens {
|
||||
if now.After(metadata.ExpiresAt) {
|
||||
delete(s.tokens, token)
|
||||
removed++
|
||||
}
|
||||
}
|
||||
if removed > 0 {
|
||||
log.Debugf("Cleaned up %d expired one-time tokens", removed)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
case <-s.cleanupDone:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close stops the cleanup goroutine and releases resources
|
||||
func (s *OneTimeTokenStore) Close() {
|
||||
s.cleanup.Stop()
|
||||
close(s.cleanupDone)
|
||||
}
|
||||
|
||||
// GetTokenCount returns the current number of tokens in the store (for debugging/metrics)
|
||||
func (s *OneTimeTokenStore) GetTokenCount() int {
|
||||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
return len(s.tokens)
|
||||
}
|
||||
@@ -21,12 +21,11 @@ import (
|
||||
"google.golang.org/grpc/peer"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/peers"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/sessionkey"
|
||||
"github.com/netbirdio/netbird/management/server/activity"
|
||||
"github.com/netbirdio/netbird/management/server/store"
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
proxyauth "github.com/netbirdio/netbird/proxy/auth"
|
||||
"github.com/netbirdio/netbird/shared/management/proto"
|
||||
)
|
||||
@@ -53,11 +52,6 @@ type reverseProxyManager interface {
|
||||
SetStatus(ctx context.Context, accountID, reverseProxyID string, status reverseproxy.ProxyStatus) error
|
||||
}
|
||||
|
||||
type keyStore interface {
|
||||
GetGroupByName(ctx context.Context, groupName string, accountID string) (*types.Group, error)
|
||||
CreateSetupKey(ctx context.Context, accountID string, keyName string, keyType types.SetupKeyType, expiresIn time.Duration, autoGroups []string, usageLimit int, userID string, ephemeral bool, allowExtraDNSLabels bool) (*types.SetupKey, error)
|
||||
}
|
||||
|
||||
// ProxyServiceServer implements the ProxyService gRPC server
|
||||
type ProxyServiceServer struct {
|
||||
proto.UnimplementedProxyServiceServer
|
||||
@@ -71,15 +65,18 @@ type ProxyServiceServer struct {
|
||||
// Store of reverse proxies
|
||||
reverseProxyStore reverseProxyStore
|
||||
|
||||
// Store for client setup keys
|
||||
keyStore keyStore
|
||||
|
||||
// Manager for access logs
|
||||
accessLogManager accesslogs.Manager
|
||||
|
||||
// Manager for reverse proxy operations
|
||||
reverseProxyManager reverseProxyManager
|
||||
|
||||
// Manager for peers
|
||||
peersManager peers.Manager
|
||||
|
||||
// Store for one-time authentication tokens
|
||||
tokenStore *OneTimeTokenStore
|
||||
|
||||
// OIDC configuration for proxy authentication
|
||||
oidcConfig ProxyOIDCConfig
|
||||
|
||||
@@ -99,13 +96,14 @@ type proxyConnection struct {
|
||||
}
|
||||
|
||||
// NewProxyServiceServer creates a new proxy service server
|
||||
func NewProxyServiceServer(store reverseProxyStore, keys keyStore, accessLogMgr accesslogs.Manager, oidcConfig ProxyOIDCConfig) *ProxyServiceServer {
|
||||
func NewProxyServiceServer(store reverseProxyStore, accessLogMgr accesslogs.Manager, tokenStore *OneTimeTokenStore, oidcConfig ProxyOIDCConfig, peersManager peers.Manager) *ProxyServiceServer {
|
||||
return &ProxyServiceServer{
|
||||
updatesChan: make(chan *proto.ProxyMapping, 100),
|
||||
reverseProxyStore: store,
|
||||
keyStore: keys,
|
||||
accessLogManager: accessLogMgr,
|
||||
oidcConfig: oidcConfig,
|
||||
tokenStore: tokenStore,
|
||||
peersManager: peersManager,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -179,33 +177,14 @@ func (s *ProxyServiceServer) sendSnapshot(ctx context.Context, conn *proxyConnec
|
||||
continue
|
||||
}
|
||||
|
||||
group, err := s.keyStore.GetGroupByName(ctx, rp.Name, rp.AccountID)
|
||||
// Generate one-time authentication token for each proxy in the snapshot
|
||||
// Tokens are not persistent on the proxy, so we need to generate new ones on reconnection
|
||||
token, err := s.tokenStore.GenerateToken(rp.AccountID, rp.ID, 5*time.Minute)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"proxy": rp.Name,
|
||||
"account": rp.AccountID,
|
||||
}).WithError(err).Error("Failed to get group by name")
|
||||
continue
|
||||
}
|
||||
|
||||
// TODO: should this even be here? We're running in a loop, and on each proxy, this will create a LOT of setup key entries that we currently have no way to remove.
|
||||
key, err := s.keyStore.CreateSetupKey(ctx,
|
||||
rp.AccountID,
|
||||
rp.Name,
|
||||
types.SetupKeyReusable,
|
||||
0,
|
||||
[]string{group.ID},
|
||||
0,
|
||||
activity.SystemInitiator,
|
||||
true,
|
||||
false,
|
||||
)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"proxy": rp.Name,
|
||||
"account": rp.AccountID,
|
||||
"group": group.ID,
|
||||
}).WithError(err).Error("Failed to create setup key")
|
||||
}).WithError(err).Error("Failed to generate auth token for snapshot")
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -213,7 +192,7 @@ func (s *ProxyServiceServer) sendSnapshot(ctx context.Context, conn *proxyConnec
|
||||
Mapping: []*proto.ProxyMapping{
|
||||
rp.ToProtoMapping(
|
||||
reverseproxy.Create, // Initial snapshot, all records are "new" for the proxy.
|
||||
key.Key,
|
||||
token,
|
||||
s.GetOIDCValidationConfig(),
|
||||
),
|
||||
},
|
||||
@@ -433,6 +412,59 @@ func protoStatusToInternal(protoStatus proto.ProxyStatus) reverseproxy.ProxyStat
|
||||
}
|
||||
}
|
||||
|
||||
// CreateProxyPeer handles proxy peer creation with one-time token authentication
|
||||
func (s *ProxyServiceServer) CreateProxyPeer(ctx context.Context, req *proto.CreateProxyPeerRequest) (*proto.CreateProxyPeerResponse, error) {
|
||||
reverseProxyID := req.GetReverseProxyId()
|
||||
accountID := req.GetAccountId()
|
||||
token := req.GetToken()
|
||||
key := req.WireguardPublicKey
|
||||
|
||||
log.WithFields(log.Fields{
|
||||
"reverse_proxy_id": reverseProxyID,
|
||||
"account_id": accountID,
|
||||
}).Debug("CreateProxyPeer request received")
|
||||
|
||||
if reverseProxyID == "" || accountID == "" || token == "" {
|
||||
log.Warn("CreateProxyPeer: missing required fields")
|
||||
return &proto.CreateProxyPeerResponse{
|
||||
Success: false,
|
||||
ErrorMessage: strPtr("missing required fields: reverse_proxy_id, account_id, and token are required"),
|
||||
}, nil
|
||||
}
|
||||
|
||||
if err := s.tokenStore.ValidateAndConsume(token, accountID, reverseProxyID); err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"reverse_proxy_id": reverseProxyID,
|
||||
"account_id": accountID,
|
||||
}).WithError(err).Warn("CreateProxyPeer: token validation failed")
|
||||
return &proto.CreateProxyPeerResponse{
|
||||
Success: false,
|
||||
ErrorMessage: strPtr("authentication failed: invalid or expired token"),
|
||||
}, status.Errorf(codes.Unauthenticated, "token validation failed: %v", err)
|
||||
}
|
||||
|
||||
err := s.peersManager.CreateProxyPeer(ctx, accountID, key)
|
||||
if err != nil {
|
||||
log.WithFields(log.Fields{
|
||||
"reverse_proxy_id": reverseProxyID,
|
||||
"account_id": accountID,
|
||||
}).WithError(err).Error("CreateProxyPeer: failed to create proxy peer")
|
||||
return &proto.CreateProxyPeerResponse{
|
||||
Success: false,
|
||||
ErrorMessage: strPtr(fmt.Sprintf("failed to create proxy peer: %v", err)),
|
||||
}, status.Errorf(codes.Internal, "failed to create proxy peer: %v", err)
|
||||
}
|
||||
|
||||
return &proto.CreateProxyPeerResponse{
|
||||
Success: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// strPtr is a helper to create a string pointer for optional proto fields
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *ProxyServiceServer) GetOIDCURL(ctx context.Context, req *proto.GetOIDCURLRequest) (*proto.GetOIDCURLResponse, error) {
|
||||
redirectURL, err := url.Parse(req.GetRedirectUrl())
|
||||
if err != nil {
|
||||
|
||||
@@ -395,7 +395,7 @@ func (h *Handler) GetAccessiblePeers(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
dnsDomain := h.networkMapController.GetDNSDomain(account.Settings)
|
||||
|
||||
netMap := account.GetPeerNetworkMap(r.Context(), peerID, dns.CustomZone{}, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil, account.GetActiveGroupUsers())
|
||||
netMap := account.GetPeerNetworkMap(r.Context(), peerID, dns.CustomZone{}, nil, validPeers, account.GetResourcePoliciesMap(), account.GetResourceRoutersMap(), nil, account.GetActiveGroupUsers(), account.GetExposedServicesMap(), account.GetProxyPeers())
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, toAccessiblePeers(netMap, dnsDomain))
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import (
|
||||
|
||||
"github.com/netbirdio/management-integrations/integrations"
|
||||
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/accesslogs"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy/domain"
|
||||
zonesManager "github.com/netbirdio/netbird/management/internals/modules/zones/manager"
|
||||
recordsManager "github.com/netbirdio/netbird/management/internals/modules/zones/records/manager"
|
||||
@@ -103,7 +105,12 @@ func BuildApiBlackBoxWithDBState(t testing_tools.TB, sqlFile string, expectedPee
|
||||
customZonesManager := zonesManager.NewManager(store, am, permissionsManager, "")
|
||||
zoneRecordsManager := recordsManager.NewManager(store, am, permissionsManager)
|
||||
|
||||
apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, nil, domain.Manager{}, nil, nil)
|
||||
// Create empty managers for reverse proxy functionality (not used in channel tests)
|
||||
var reverseProxyManager reverseproxy.Manager
|
||||
reverseProxyDomainManager := domain.NewManager(store, nil)
|
||||
var accessLogsManager accesslogs.Manager
|
||||
|
||||
apiHandler, err := http2.NewAPIHandler(context.Background(), am, networksManagerMock, resourcesManagerMock, routersManagerMock, groupsManagerMock, geoMock, authManagerMock, metrics, validatorMock, proxyController, permissionsManager, peersManager, settingsManager, customZonesManager, zoneRecordsManager, networkMapController, nil, reverseProxyManager, reverseProxyDomainManager, accessLogsManager)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create API handler: %v", err)
|
||||
}
|
||||
|
||||
@@ -545,7 +545,7 @@ func (am *DefaultAccountManager) GetPeerNetwork(ctx context.Context, peerID stri
|
||||
// Each new Peer will be assigned a new next net.IP from the Account.Network and Account.Network.LastIP will be updated (IP's are not reused).
|
||||
// The peer property is just a placeholder for the Peer properties to pass further
|
||||
func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKey, userID string, peer *nbpeer.Peer, temporary bool) (*nbpeer.Peer, *types.NetworkMap, []*posture.Checks, error) {
|
||||
if setupKey == "" && userID == "" {
|
||||
if setupKey == "" && userID == "" && !peer.ProxyEmbedded {
|
||||
// no auth method provided => reject access
|
||||
return nil, nil, nil, status.Errorf(status.Unauthenticated, "no peer auth method provided, please use a setup key or interactive SSO login")
|
||||
}
|
||||
@@ -554,6 +554,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
hashedKey := sha256.Sum256([]byte(upperKey))
|
||||
encodedHashedKey := b64.StdEncoding.EncodeToString(hashedKey[:])
|
||||
addedByUser := len(userID) > 0
|
||||
addedBySetupKey := len(setupKey) > 0
|
||||
|
||||
// This is a handling for the case when the same machine (with the same WireGuard pub key) tries to register twice.
|
||||
// Such case is possible when AddPeer function takes long time to finish after AcquireWriteLockByUID (e.g., database is slow)
|
||||
@@ -576,7 +577,8 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
var ephemeral bool
|
||||
var groupsToAdd []string
|
||||
var allowExtraDNSLabels bool
|
||||
if addedByUser {
|
||||
switch {
|
||||
case addedByUser:
|
||||
user, err := am.Store.GetUserByUserID(ctx, store.LockingStrengthNone, userID)
|
||||
if err != nil {
|
||||
return nil, nil, nil, status.Errorf(status.NotFound, "failed adding new peer: user not found")
|
||||
@@ -599,7 +601,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
}
|
||||
opEvent.InitiatorID = userID
|
||||
opEvent.Activity = activity.PeerAddedByUser
|
||||
} else {
|
||||
case addedBySetupKey:
|
||||
// Validate the setup key
|
||||
sk, err := am.Store.GetSetupKeyBySecret(ctx, store.LockingStrengthNone, encodedHashedKey)
|
||||
if err != nil {
|
||||
@@ -622,6 +624,12 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
if !sk.AllowExtraDNSLabels && len(peer.ExtraDNSLabels) > 0 {
|
||||
return nil, nil, nil, status.Errorf(status.PreconditionFailed, "couldn't add peer: setup key doesn't allow extra DNS labels")
|
||||
}
|
||||
default:
|
||||
if peer.ProxyEmbedded {
|
||||
log.WithContext(ctx).Debugf("adding peer for proxy embedded, accountID: %s", accountID)
|
||||
} else {
|
||||
log.WithContext(ctx).Warnf("adding peer without setup key or userID, accountID: %s", accountID)
|
||||
}
|
||||
}
|
||||
opEvent.AccountID = accountID
|
||||
|
||||
@@ -657,6 +665,7 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
CreatedAt: registrationTime,
|
||||
LoginExpirationEnabled: addedByUser && !temporary,
|
||||
Ephemeral: ephemeral,
|
||||
ProxyEmbedded: peer.ProxyEmbedded,
|
||||
Location: peer.Location,
|
||||
InactivityExpirationEnabled: addedByUser && !temporary,
|
||||
ExtraDNSLabels: peer.ExtraDNSLabels,
|
||||
@@ -728,12 +737,13 @@ func (am *DefaultAccountManager) AddPeer(ctx context.Context, accountID, setupKe
|
||||
return fmt.Errorf("failed adding peer to All group: %w", err)
|
||||
}
|
||||
|
||||
if addedByUser {
|
||||
switch {
|
||||
case addedByUser:
|
||||
err := transaction.SaveUserLastLogin(ctx, accountID, userID, newPeer.GetLastLogin())
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Debugf("failed to update user last login: %v", err)
|
||||
}
|
||||
} else {
|
||||
case addedBySetupKey:
|
||||
sk, err := transaction.GetSetupKeyBySecret(ctx, store.LockingStrengthUpdate, encodedHashedKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get setup key: %w", err)
|
||||
|
||||
@@ -48,6 +48,8 @@ type Peer struct {
|
||||
CreatedAt time.Time
|
||||
// Indicate ephemeral peer attribute
|
||||
Ephemeral bool `gorm:"index"`
|
||||
// ProxyEmbedded indicates whether the peer is embedded in a reverse proxy
|
||||
ProxyEmbedded bool `gorm:"index"`
|
||||
// Geo location based on connection IP
|
||||
Location Location `gorm:"embedded;embeddedPrefix:location_"`
|
||||
|
||||
@@ -224,6 +226,7 @@ func (p *Peer) Copy() *Peer {
|
||||
LastLogin: p.LastLogin,
|
||||
CreatedAt: p.CreatedAt,
|
||||
Ephemeral: p.Ephemeral,
|
||||
ProxyEmbedded: p.ProxyEmbedded,
|
||||
Location: p.Location,
|
||||
InactivityExpirationEnabled: p.InactivityExpirationEnabled,
|
||||
ExtraDNSLabels: slices.Clone(p.ExtraDNSLabels),
|
||||
|
||||
@@ -1099,6 +1099,7 @@ func (s *SqlStore) getAccountGorm(ctx context.Context, accountID string) (*types
|
||||
Preload("NetworkRouters").
|
||||
Preload("NetworkResources").
|
||||
Preload("Onboarding").
|
||||
Preload("ReverseProxies").
|
||||
Take(&account, idQueryCondition, accountID)
|
||||
if result.Error != nil {
|
||||
log.WithContext(ctx).Errorf("error when getting account %s from the store: %s", accountID, result.Error)
|
||||
@@ -1276,6 +1277,17 @@ func (s *SqlStore) getAccountPgx(ctx context.Context, accountID string) (*types.
|
||||
account.PostureChecks = checks
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
proxies, err := s.getProxies(ctx, accountID)
|
||||
if err != nil {
|
||||
errChan <- err
|
||||
return
|
||||
}
|
||||
account.ReverseProxies = proxies
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
@@ -1677,7 +1689,7 @@ func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Pee
|
||||
meta_kernel_version, meta_network_addresses, meta_system_serial_number, meta_system_product_name, meta_system_manufacturer,
|
||||
meta_environment, meta_flags, meta_files, peer_status_last_seen, peer_status_connected, peer_status_login_expired,
|
||||
peer_status_requires_approval, location_connection_ip, location_country_code, location_city_name,
|
||||
location_geo_name_id FROM peers WHERE account_id = $1`
|
||||
location_geo_name_id, proxy_embedded FROM peers WHERE account_id = $1`
|
||||
rows, err := s.pool.Query(ctx, query, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -1690,7 +1702,7 @@ func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Pee
|
||||
lastLogin, createdAt sql.NullTime
|
||||
sshEnabled, loginExpirationEnabled, inactivityExpirationEnabled, ephemeral, allowExtraDNSLabels sql.NullBool
|
||||
peerStatusLastSeen sql.NullTime
|
||||
peerStatusConnected, peerStatusLoginExpired, peerStatusRequiresApproval sql.NullBool
|
||||
peerStatusConnected, peerStatusLoginExpired, peerStatusRequiresApproval, proxyEmbedded sql.NullBool
|
||||
ip, extraDNS, netAddr, env, flags, files, connIP []byte
|
||||
metaHostname, metaGoOS, metaKernel, metaCore, metaPlatform sql.NullString
|
||||
metaOS, metaOSVersion, metaWtVersion, metaUIVersion, metaKernelVersion sql.NullString
|
||||
@@ -1705,7 +1717,7 @@ func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Pee
|
||||
&metaOS, &metaOSVersion, &metaWtVersion, &metaUIVersion, &metaKernelVersion, &netAddr,
|
||||
&metaSystemSerialNumber, &metaSystemProductName, &metaSystemManufacturer, &env, &flags, &files,
|
||||
&peerStatusLastSeen, &peerStatusConnected, &peerStatusLoginExpired, &peerStatusRequiresApproval, &connIP,
|
||||
&locationCountryCode, &locationCityName, &locationGeoNameID)
|
||||
&locationCountryCode, &locationCityName, &locationGeoNameID, &proxyEmbedded)
|
||||
|
||||
if err == nil {
|
||||
if lastLogin.Valid {
|
||||
@@ -1789,6 +1801,9 @@ func (s *SqlStore) getPeers(ctx context.Context, accountID string) ([]nbpeer.Pee
|
||||
if locationGeoNameID.Valid {
|
||||
p.Location.GeoNameID = uint(locationGeoNameID.Int64)
|
||||
}
|
||||
if proxyEmbedded.Valid {
|
||||
p.ProxyEmbedded = proxyEmbedded.Bool
|
||||
}
|
||||
if ip != nil {
|
||||
_ = json.Unmarshal(ip, &p.IP)
|
||||
}
|
||||
@@ -2044,6 +2059,52 @@ func (s *SqlStore) getPostureChecks(ctx context.Context, accountID string) ([]*p
|
||||
return checks, nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) getProxies(ctx context.Context, accountID string) ([]*reverseproxy.ReverseProxy, error) {
|
||||
const query = `SELECT id, account_id, name, domain, targets, enabled, auth,
|
||||
meta_created_at, meta_certificate_issued_at, meta_status
|
||||
FROM reverse_proxies WHERE account_id = $1`
|
||||
rows, err := s.pool.Query(ctx, query, accountID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
proxies, err := pgx.CollectRows(rows, func(row pgx.CollectableRow) (*reverseproxy.ReverseProxy, error) {
|
||||
var p reverseproxy.ReverseProxy
|
||||
var auth []byte
|
||||
var targets []byte
|
||||
err := row.Scan(
|
||||
&p.ID,
|
||||
&p.AccountID,
|
||||
&p.Name,
|
||||
&p.Domain,
|
||||
&targets,
|
||||
&p.Enabled,
|
||||
&auth,
|
||||
&p.Meta.CreatedAt,
|
||||
&p.Meta.CertificateIssuedAt,
|
||||
&p.Meta.Status,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Unmarshal JSON fields
|
||||
if auth != nil {
|
||||
if err := json.Unmarshal(auth, &p.Auth); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if targets != nil {
|
||||
if err := json.Unmarshal(targets, &p.Targets); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &p, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return proxies, nil
|
||||
}
|
||||
|
||||
func (s *SqlStore) getNetworks(ctx context.Context, accountID string) ([]*networkTypes.Network, error) {
|
||||
const query = `SELECT id, account_id, name, description FROM networks WHERE account_id = $1`
|
||||
rows, err := s.pool.Query(ctx, query, accountID)
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/client/ssh/auth"
|
||||
nbdns "github.com/netbirdio/netbird/dns"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/reverseproxy"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/zones"
|
||||
"github.com/netbirdio/netbird/management/internals/modules/zones/records"
|
||||
resourceTypes "github.com/netbirdio/netbird/management/server/networks/resources/types"
|
||||
@@ -99,6 +100,7 @@ type Account struct {
|
||||
NameServerGroupsG []nbdns.NameServerGroup `json:"-" gorm:"foreignKey:AccountID;references:id"`
|
||||
DNSSettings DNSSettings `gorm:"embedded;embeddedPrefix:dns_settings_"`
|
||||
PostureChecks []*posture.Checks `gorm:"foreignKey:AccountID;references:id"`
|
||||
ReverseProxies []*reverseproxy.ReverseProxy `gorm:"foreignKey:AccountID;references:id"`
|
||||
// Settings is a dictionary of Account settings
|
||||
Settings *Settings `gorm:"embedded;embeddedPrefix:settings_"`
|
||||
Networks []*networkTypes.Network `gorm:"foreignKey:AccountID;references:id"`
|
||||
@@ -283,6 +285,8 @@ func (a *Account) GetPeerNetworkMap(
|
||||
routers map[string]map[string]*routerTypes.NetworkRouter,
|
||||
metrics *telemetry.AccountManagerMetrics,
|
||||
groupIDToUserIDs map[string][]string,
|
||||
exposedServices map[string][]*reverseproxy.ReverseProxy, // routerPeer -> list of exposed services
|
||||
proxyPeers []*nbpeer.Peer,
|
||||
) *NetworkMap {
|
||||
start := time.Now()
|
||||
peer := a.Peers[peerID]
|
||||
@@ -300,10 +304,21 @@ func (a *Account) GetPeerNetworkMap(
|
||||
|
||||
peerGroups := a.GetPeerGroups(peerID)
|
||||
|
||||
aclPeers, firewallRules, authorizedUsers, enableSSH := a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs)
|
||||
var aclPeers []*nbpeer.Peer
|
||||
var firewallRules []*FirewallRule
|
||||
var authorizedUsers map[string]map[string]struct{}
|
||||
var enableSSH bool
|
||||
if peer.ProxyEmbedded {
|
||||
aclPeers, firewallRules = a.GetProxyConnectionResources(exposedServices)
|
||||
} else {
|
||||
aclPeers, firewallRules, authorizedUsers, enableSSH = a.GetPeerConnectionResources(ctx, peer, validatedPeersMap, groupIDToUserIDs)
|
||||
proxyAclPeers, proxyFirewallRules := a.GetPeerProxyResources(exposedServices[peerID], proxyPeers)
|
||||
aclPeers = append(aclPeers, proxyAclPeers...)
|
||||
firewallRules = append(firewallRules, proxyFirewallRules...)
|
||||
}
|
||||
|
||||
var peersToConnect, expiredPeers []*nbpeer.Peer
|
||||
// exclude expired peers
|
||||
var peersToConnect []*nbpeer.Peer
|
||||
var expiredPeers []*nbpeer.Peer
|
||||
for _, p := range aclPeers {
|
||||
expired, _ := p.LoginExpired(a.Settings.PeerLoginExpiration)
|
||||
if a.Settings.PeerLoginExpirationEnabled && expired {
|
||||
@@ -372,6 +387,74 @@ func (a *Account) GetPeerNetworkMap(
|
||||
return nm
|
||||
}
|
||||
|
||||
func (a *Account) GetProxyConnectionResources(exposedServices map[string][]*reverseproxy.ReverseProxy) ([]*nbpeer.Peer, []*FirewallRule) {
|
||||
var aclPeers []*nbpeer.Peer
|
||||
var firewallRules []*FirewallRule
|
||||
|
||||
for _, peerServices := range exposedServices {
|
||||
for _, service := range peerServices {
|
||||
if !service.Enabled {
|
||||
continue
|
||||
}
|
||||
for _, target := range service.Targets {
|
||||
if !target.Enabled {
|
||||
continue
|
||||
}
|
||||
switch target.TargetType {
|
||||
case reverseproxy.TargetTypePeer:
|
||||
tpeer := a.GetPeer(target.TargetId)
|
||||
if tpeer == nil {
|
||||
continue
|
||||
}
|
||||
aclPeers = append(aclPeers, tpeer)
|
||||
firewallRules = append(firewallRules, &FirewallRule{
|
||||
PolicyID: "proxy-" + service.ID,
|
||||
PeerIP: tpeer.IP.String(),
|
||||
Direction: FirewallRuleDirectionOUT,
|
||||
Action: "allow",
|
||||
Protocol: string(PolicyRuleProtocolTCP),
|
||||
PortRange: RulePortRange{Start: uint16(target.Port), End: uint16(target.Port)},
|
||||
})
|
||||
case reverseproxy.TargetTypeResource:
|
||||
// TODO: handle resource type targets
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aclPeers, firewallRules
|
||||
}
|
||||
|
||||
func (a *Account) GetPeerProxyResources(services []*reverseproxy.ReverseProxy, proxyPeers []*nbpeer.Peer) ([]*nbpeer.Peer, []*FirewallRule) {
|
||||
var aclPeers []*nbpeer.Peer
|
||||
var firewallRules []*FirewallRule
|
||||
|
||||
for _, service := range services {
|
||||
if !service.Enabled {
|
||||
continue
|
||||
}
|
||||
for _, target := range service.Targets {
|
||||
if !target.Enabled {
|
||||
continue
|
||||
}
|
||||
aclPeers = proxyPeers
|
||||
for _, peer := range aclPeers {
|
||||
firewallRules = append(firewallRules, &FirewallRule{
|
||||
PolicyID: "proxy-" + service.ID,
|
||||
PeerIP: peer.IP.String(),
|
||||
Direction: FirewallRuleDirectionIN,
|
||||
Action: "allow",
|
||||
Protocol: string(PolicyRuleProtocolTCP),
|
||||
PortRange: RulePortRange{Start: uint16(target.Port), End: uint16(target.Port)},
|
||||
})
|
||||
}
|
||||
// TODO: handle routes
|
||||
}
|
||||
}
|
||||
|
||||
return aclPeers, firewallRules
|
||||
}
|
||||
|
||||
func (a *Account) addNetworksRoutingPeers(
|
||||
networkResourcesRoutes []*route.Route,
|
||||
peer *nbpeer.Peer,
|
||||
@@ -1215,7 +1298,7 @@ func (a *Account) getAllPeersFromGroups(ctx context.Context, groups []string, pe
|
||||
filteredPeers := make([]*nbpeer.Peer, 0, len(uniquePeerIDs))
|
||||
for _, p := range uniquePeerIDs {
|
||||
peer, ok := a.Peers[p]
|
||||
if !ok || peer == nil {
|
||||
if !ok || peer == nil || peer.ProxyEmbedded {
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1242,7 +1325,7 @@ func (a *Account) getAllPeersFromGroups(ctx context.Context, groups []string, pe
|
||||
|
||||
func (a *Account) getPeerFromResource(resource Resource, peerID string) ([]*nbpeer.Peer, bool) {
|
||||
peer := a.GetPeer(resource.ID)
|
||||
if peer == nil {
|
||||
if peer == nil || peer.ProxyEmbedded {
|
||||
return []*nbpeer.Peer{}, false
|
||||
}
|
||||
|
||||
@@ -1778,6 +1861,40 @@ func (a *Account) GetActiveGroupUsers() map[string][]string {
|
||||
return groups
|
||||
}
|
||||
|
||||
func (a *Account) GetProxyPeers() []*nbpeer.Peer {
|
||||
var proxyPeers []*nbpeer.Peer
|
||||
for _, peer := range a.Peers {
|
||||
if peer.ProxyEmbedded {
|
||||
proxyPeers = append(proxyPeers, peer)
|
||||
}
|
||||
}
|
||||
return proxyPeers
|
||||
}
|
||||
|
||||
func (a *Account) GetExposedServicesMap() map[string][]*reverseproxy.ReverseProxy {
|
||||
services := make(map[string][]*reverseproxy.ReverseProxy)
|
||||
resourcesMap := make(map[string]*resourceTypes.NetworkResource)
|
||||
for _, resource := range a.NetworkResources {
|
||||
resourcesMap[resource.ID] = resource
|
||||
}
|
||||
routersMap := a.GetResourceRoutersMap()
|
||||
for _, proxy := range a.ReverseProxies {
|
||||
for _, target := range proxy.Targets {
|
||||
switch target.TargetType {
|
||||
case reverseproxy.TargetTypePeer:
|
||||
services[target.TargetId] = append(services[target.TargetId], proxy)
|
||||
case reverseproxy.TargetTypeResource:
|
||||
resource := resourcesMap[target.TargetId]
|
||||
routers := routersMap[resource.NetworkID]
|
||||
for peerID := range routers {
|
||||
services[peerID] = append(services[peerID], proxy)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return services
|
||||
}
|
||||
|
||||
// expandPortsAndRanges expands Ports and PortRanges of a rule into individual firewall rules
|
||||
func expandPortsAndRanges(base FirewallRule, rule *PolicyRule, peer *nbpeer.Peer) []*FirewallRule {
|
||||
features := peerSupportedFirewallFeatures(peer.Meta.WtVersion)
|
||||
|
||||
Reference in New Issue
Block a user