Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa07b3b87b | ||
|
|
2bef214cc0 | ||
|
|
cfb2d82352 | ||
|
|
684501fd35 | ||
|
|
0492c1724a | ||
|
|
6f436e57b5 | ||
|
|
a0d28f9851 | ||
|
|
cdd27a9fe5 | ||
|
|
5523040acd | ||
|
|
670446d42e | ||
|
|
5bed6777d5 | ||
|
|
a0482ebc7b | ||
|
|
2a89d6e47a | ||
|
|
24f932b2ce | ||
|
|
c03435061c | ||
|
|
8e948739f1 | ||
|
|
9b53cad752 |
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@@ -13,3 +13,5 @@
|
||||
- [ ] It is a refactor
|
||||
- [ ] Created tests that fail without the change (if possible)
|
||||
- [ ] Extended the README / documentation, if necessary
|
||||
|
||||
> By submitting this pull request, you confirm that you have read and agree to the terms of the [Contributor License Agreement](https://github.com/netbirdio/netbird/blob/main/CONTRIBUTOR_LICENSE_AGREEMENT.md).
|
||||
|
||||
8
.github/workflows/golang-test-linux.yml
vendored
8
.github/workflows/golang-test-linux.yml
vendored
@@ -223,6 +223,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
||||
|
||||
- name: Get Go environment
|
||||
run: |
|
||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||
@@ -269,6 +273,10 @@ jobs:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: sudo apt update && sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
|
||||
|
||||
- name: Get Go environment
|
||||
run: |
|
||||
echo "cache=$(go env GOCACHE)" >> $GITHUB_ENV
|
||||
|
||||
@@ -98,14 +98,14 @@ func (e *ConnMgr) UpdatedRemoteFeatureFlag(ctx context.Context, enabled bool) er
|
||||
}
|
||||
|
||||
// SetExcludeList sets the list of peer IDs that should always have permanent connections.
|
||||
func (e *ConnMgr) SetExcludeList(peerIDs []string) {
|
||||
func (e *ConnMgr) SetExcludeList(peerIDs map[string]bool) {
|
||||
if e.lazyConnMgr == nil {
|
||||
return
|
||||
}
|
||||
|
||||
excludedPeers := make([]lazyconn.PeerConfig, 0, len(peerIDs))
|
||||
|
||||
for _, peerID := range peerIDs {
|
||||
for peerID := range peerIDs {
|
||||
var peerConn *peer.Conn
|
||||
var exists bool
|
||||
if peerConn, exists = e.peerStore.PeerConn(peerID); !exists {
|
||||
|
||||
@@ -241,6 +241,8 @@ func NewEngine(
|
||||
checks: checks,
|
||||
connSemaphore: semaphoregroup.NewSemaphoreGroup(connInitLimit),
|
||||
}
|
||||
|
||||
path := statemanager.GetDefaultStatePath()
|
||||
if runtime.GOOS == "ios" {
|
||||
if !fileExists(mobileDep.StateFilePath) {
|
||||
err := createFile(mobileDep.StateFilePath)
|
||||
@@ -250,11 +252,9 @@ func NewEngine(
|
||||
}
|
||||
}
|
||||
|
||||
engine.stateManager = statemanager.New(mobileDep.StateFilePath)
|
||||
}
|
||||
if path := statemanager.GetDefaultStatePath(); path != "" {
|
||||
engine.stateManager = statemanager.New(path)
|
||||
path = mobileDep.StateFilePath
|
||||
}
|
||||
engine.stateManager = statemanager.New(path)
|
||||
|
||||
return engine
|
||||
}
|
||||
@@ -1927,14 +1927,16 @@ func (e *Engine) updateForwardRules(rules []*mgmProto.ForwardingRule) ([]firewal
|
||||
return forwardingRules, nberrors.FormatErrorOrNil(merr)
|
||||
}
|
||||
|
||||
func (e *Engine) toExcludedLazyPeers(routes []*route.Route, rules []firewallManager.ForwardRule, peers []*mgmProto.RemotePeerConfig) []string {
|
||||
excludedPeers := make([]string, 0)
|
||||
func (e *Engine) toExcludedLazyPeers(routes []*route.Route, rules []firewallManager.ForwardRule, peers []*mgmProto.RemotePeerConfig) map[string]bool {
|
||||
excludedPeers := make(map[string]bool)
|
||||
for _, r := range routes {
|
||||
if r.Peer == "" {
|
||||
continue
|
||||
}
|
||||
log.Infof("exclude router peer from lazy connection: %s", r.Peer)
|
||||
excludedPeers = append(excludedPeers, r.Peer)
|
||||
if !excludedPeers[r.Peer] {
|
||||
log.Infof("exclude router peer from lazy connection: %s", r.Peer)
|
||||
excludedPeers[r.Peer] = true
|
||||
}
|
||||
}
|
||||
|
||||
for _, r := range rules {
|
||||
@@ -1945,7 +1947,7 @@ func (e *Engine) toExcludedLazyPeers(routes []*route.Route, rules []firewallMana
|
||||
continue
|
||||
}
|
||||
log.Infof("exclude forwarder peer from lazy connection: %s", p.GetWgPubKey())
|
||||
excludedPeers = append(excludedPeers, p.GetWgPubKey())
|
||||
excludedPeers[p.GetWgPubKey()] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -691,8 +691,7 @@ func (conn *Conn) evalStatus() ConnStatus {
|
||||
}
|
||||
|
||||
func (conn *Conn) isConnectedOnAllWay() (connected bool) {
|
||||
conn.mu.Lock()
|
||||
defer conn.mu.Unlock()
|
||||
// would be better to protect this with a mutex, but it could cause deadlock with Close function
|
||||
|
||||
defer func() {
|
||||
if !connected {
|
||||
|
||||
@@ -18,6 +18,8 @@ type notifier struct {
|
||||
currentClientState bool
|
||||
lastNotification int
|
||||
lastNumberOfPeers int
|
||||
lastFqdnAddress string
|
||||
lastIPAddress string
|
||||
}
|
||||
|
||||
func newNotifier() *notifier {
|
||||
@@ -25,15 +27,22 @@ func newNotifier() *notifier {
|
||||
}
|
||||
|
||||
func (n *notifier) setListener(listener Listener) {
|
||||
n.serverStateLock.Lock()
|
||||
lastNotification := n.lastNotification
|
||||
numOfPeers := n.lastNumberOfPeers
|
||||
fqdnAddress := n.lastFqdnAddress
|
||||
address := n.lastIPAddress
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
|
||||
n.serverStateLock.Lock()
|
||||
n.notifyListener(listener, n.lastNotification)
|
||||
listener.OnPeersListChanged(n.lastNumberOfPeers)
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.listener = listener
|
||||
|
||||
listener.OnAddressChanged(fqdnAddress, address)
|
||||
notifyListener(listener, lastNotification)
|
||||
// run on go routine to avoid on Java layer to call go functions on same thread
|
||||
go listener.OnPeersListChanged(numOfPeers)
|
||||
}
|
||||
|
||||
func (n *notifier) removeListener() {
|
||||
@@ -44,41 +53,44 @@ func (n *notifier) removeListener() {
|
||||
|
||||
func (n *notifier) updateServerStates(mgmState bool, signalState bool) {
|
||||
n.serverStateLock.Lock()
|
||||
defer n.serverStateLock.Unlock()
|
||||
|
||||
calculatedState := n.calculateState(mgmState, signalState)
|
||||
|
||||
if !n.isServerStateChanged(calculatedState) {
|
||||
n.serverStateLock.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
n.lastNotification = calculatedState
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.notify(n.lastNotification)
|
||||
n.notify(calculatedState)
|
||||
}
|
||||
|
||||
func (n *notifier) clientStart() {
|
||||
n.serverStateLock.Lock()
|
||||
defer n.serverStateLock.Unlock()
|
||||
n.currentClientState = true
|
||||
n.lastNotification = stateConnecting
|
||||
n.notify(n.lastNotification)
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.notify(stateConnecting)
|
||||
}
|
||||
|
||||
func (n *notifier) clientStop() {
|
||||
n.serverStateLock.Lock()
|
||||
defer n.serverStateLock.Unlock()
|
||||
n.currentClientState = false
|
||||
n.lastNotification = stateDisconnected
|
||||
n.notify(n.lastNotification)
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.notify(stateDisconnected)
|
||||
}
|
||||
|
||||
func (n *notifier) clientTearDown() {
|
||||
n.serverStateLock.Lock()
|
||||
defer n.serverStateLock.Unlock()
|
||||
n.currentClientState = false
|
||||
n.lastNotification = stateDisconnecting
|
||||
n.notify(n.lastNotification)
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.notify(stateDisconnecting)
|
||||
}
|
||||
|
||||
func (n *notifier) isServerStateChanged(newState int) bool {
|
||||
@@ -87,26 +99,14 @@ func (n *notifier) isServerStateChanged(newState int) bool {
|
||||
|
||||
func (n *notifier) notify(state int) {
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
if n.listener == nil {
|
||||
listener := n.listener
|
||||
n.listenersLock.Unlock()
|
||||
|
||||
if listener == nil {
|
||||
return
|
||||
}
|
||||
n.notifyListener(n.listener, state)
|
||||
}
|
||||
|
||||
func (n *notifier) notifyListener(l Listener, state int) {
|
||||
go func() {
|
||||
switch state {
|
||||
case stateDisconnected:
|
||||
l.OnDisconnected()
|
||||
case stateConnected:
|
||||
l.OnConnected()
|
||||
case stateConnecting:
|
||||
l.OnConnecting()
|
||||
case stateDisconnecting:
|
||||
l.OnDisconnecting()
|
||||
}
|
||||
}()
|
||||
notifyListener(listener, state)
|
||||
}
|
||||
|
||||
func (n *notifier) calculateState(managementConn, signalConn bool) int {
|
||||
@@ -126,20 +126,48 @@ func (n *notifier) calculateState(managementConn, signalConn bool) int {
|
||||
}
|
||||
|
||||
func (n *notifier) peerListChanged(numOfPeers int) {
|
||||
n.serverStateLock.Lock()
|
||||
n.lastNumberOfPeers = numOfPeers
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
if n.listener == nil {
|
||||
listener := n.listener
|
||||
n.listenersLock.Unlock()
|
||||
|
||||
if listener == nil {
|
||||
return
|
||||
}
|
||||
n.listener.OnPeersListChanged(numOfPeers)
|
||||
|
||||
// run on go routine to avoid on Java layer to call go functions on same thread
|
||||
go listener.OnPeersListChanged(numOfPeers)
|
||||
}
|
||||
|
||||
func (n *notifier) localAddressChanged(fqdn, address string) {
|
||||
n.serverStateLock.Lock()
|
||||
n.lastFqdnAddress = fqdn
|
||||
n.lastIPAddress = address
|
||||
n.serverStateLock.Unlock()
|
||||
|
||||
n.listenersLock.Lock()
|
||||
defer n.listenersLock.Unlock()
|
||||
if n.listener == nil {
|
||||
listener := n.listener
|
||||
n.listenersLock.Unlock()
|
||||
|
||||
if listener == nil {
|
||||
return
|
||||
}
|
||||
n.listener.OnAddressChanged(fqdn, address)
|
||||
|
||||
listener.OnAddressChanged(fqdn, address)
|
||||
}
|
||||
|
||||
func notifyListener(l Listener, state int) {
|
||||
switch state {
|
||||
case stateDisconnected:
|
||||
l.OnDisconnected()
|
||||
case stateConnected:
|
||||
l.OnConnected()
|
||||
case stateConnecting:
|
||||
l.OnConnecting()
|
||||
case stateDisconnecting:
|
||||
l.OnDisconnecting()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
//go:build !android
|
||||
|
||||
package routemanager
|
||||
|
||||
import (
|
||||
@@ -1,27 +0,0 @@
|
||||
//go:build android
|
||||
|
||||
package routemanager
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
firewall "github.com/netbirdio/netbird/client/firewall/manager"
|
||||
"github.com/netbirdio/netbird/client/internal/peer"
|
||||
"github.com/netbirdio/netbird/client/internal/routemanager/iface"
|
||||
"github.com/netbirdio/netbird/route"
|
||||
)
|
||||
|
||||
type serverRouter struct {
|
||||
}
|
||||
|
||||
func (r serverRouter) cleanUp() {
|
||||
}
|
||||
|
||||
func (r serverRouter) updateRoutes(map[route.ID]*route.Route, bool) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newServerRouter(context.Context, iface.WGIface, firewall.Manager, *peer.Status) (*serverRouter, error) {
|
||||
return nil, fmt.Errorf("server route not supported on this os")
|
||||
}
|
||||
@@ -66,6 +66,15 @@ func TestAccounts_List_Err(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccounts_List_ConnErr(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
ret, err := c.Accounts.List(context.Background())
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "404")
|
||||
assert.Empty(t, ret)
|
||||
})
|
||||
}
|
||||
|
||||
func TestAccounts_Update_200(t *testing.T) {
|
||||
withMockClient(func(c *rest.Client, mux *http.ServeMux) {
|
||||
mux.HandleFunc("/api/accounts/Test", func(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
@@ -134,7 +135,8 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
||||
if resp.StatusCode > 299 {
|
||||
parsedErr, pErr := parseResponse[util.ErrorResponse](resp)
|
||||
if pErr != nil {
|
||||
return nil, err
|
||||
|
||||
return nil, pErr
|
||||
}
|
||||
return nil, errors.New(parsedErr.Message)
|
||||
}
|
||||
@@ -145,13 +147,16 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
||||
func parseResponse[T any](resp *http.Response) (T, error) {
|
||||
var ret T
|
||||
if resp.Body == nil {
|
||||
return ret, errors.New("No body")
|
||||
return ret, fmt.Errorf("Body missing, HTTP Error code %d", resp.StatusCode)
|
||||
}
|
||||
bs, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
err = json.Unmarshal(bs, &ret)
|
||||
if err != nil {
|
||||
return ret, fmt.Errorf("Error code %d, error unmarshalling body: %w", resp.StatusCode, err)
|
||||
}
|
||||
|
||||
return ret, err
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
@@ -339,13 +339,20 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountRoutingPeerDNSResolutionDisabled, nil)
|
||||
}
|
||||
updateAccountPeers = true
|
||||
account.Network.Serial++
|
||||
}
|
||||
|
||||
if oldSettings.LazyConnectionEnabled != newSettings.LazyConnectionEnabled {
|
||||
if newSettings.LazyConnectionEnabled {
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountLazyConnectionEnabled, nil)
|
||||
} else {
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountLazyConnectionDisabled, nil)
|
||||
}
|
||||
updateAccountPeers = true
|
||||
}
|
||||
|
||||
if oldSettings.DNSDomain != newSettings.DNSDomain {
|
||||
am.StoreEvent(ctx, userID, accountID, accountID, activity.AccountDNSDomainUpdated, nil)
|
||||
updateAccountPeers = true
|
||||
account.Network.Serial++
|
||||
}
|
||||
|
||||
err = am.handleInactivityExpirationSettings(ctx, oldSettings, newSettings, userID, accountID)
|
||||
@@ -358,7 +365,11 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
return nil, fmt.Errorf("groups propagation failed: %w", err)
|
||||
}
|
||||
|
||||
updatedAccount := account.UpdateSettings(newSettings)
|
||||
account.UpdateSettings(newSettings)
|
||||
|
||||
if updateAccountPeers {
|
||||
account.Network.Serial++
|
||||
}
|
||||
|
||||
err = am.Store.SaveAccount(ctx, account)
|
||||
if err != nil {
|
||||
@@ -374,7 +385,7 @@ func (am *DefaultAccountManager) UpdateAccountSettings(ctx context.Context, acco
|
||||
go am.UpdateAccountPeers(ctx, accountID)
|
||||
}
|
||||
|
||||
return updatedAccount, nil
|
||||
return account, nil
|
||||
}
|
||||
|
||||
func (am *DefaultAccountManager) handleGroupsPropagationSettings(ctx context.Context, oldSettings, newSettings *types.Settings, userID, accountID string) error {
|
||||
@@ -1237,7 +1248,7 @@ func (am *DefaultAccountManager) SyncUserJWTGroups(ctx context.Context, userAuth
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, newGroupsToCreate); err != nil {
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, userAuth.AccountId, newGroupsToCreate); err != nil {
|
||||
return fmt.Errorf("error saving groups: %w", err)
|
||||
}
|
||||
|
||||
@@ -1271,7 +1282,7 @@ func (am *DefaultAccountManager) SyncUserJWTGroups(ctx context.Context, userAuth
|
||||
return fmt.Errorf("error modifying user peers in groups: %w", err)
|
||||
}
|
||||
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, updatedGroups); err != nil {
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, userAuth.AccountId, updatedGroups); err != nil {
|
||||
return fmt.Errorf("error saving groups: %w", err)
|
||||
}
|
||||
|
||||
|
||||
@@ -171,6 +171,9 @@ const (
|
||||
ResourceRemovedFromGroup Activity = 83
|
||||
|
||||
AccountDNSDomainUpdated Activity = 84
|
||||
|
||||
AccountLazyConnectionEnabled Activity = 85
|
||||
AccountLazyConnectionDisabled Activity = 86
|
||||
)
|
||||
|
||||
var activityMap = map[Activity]Code{
|
||||
@@ -268,6 +271,9 @@ var activityMap = map[Activity]Code{
|
||||
ResourceRemovedFromGroup: {"Resource removed from group", "resource.group.delete"},
|
||||
|
||||
AccountDNSDomainUpdated: {"Account DNS domain updated", "account.dns.domain.update"},
|
||||
|
||||
AccountLazyConnectionEnabled: {"Account lazy connection enabled", "account.setting.lazy.connection.enable"},
|
||||
AccountLazyConnectionDisabled: {"Account lazy connection disabled", "account.setting.lazy.connection.disable"},
|
||||
}
|
||||
|
||||
// StringCode returns a string code of the activity
|
||||
|
||||
@@ -116,7 +116,7 @@ func (am *DefaultAccountManager) SaveGroups(ctx context.Context, accountID, user
|
||||
return err
|
||||
}
|
||||
|
||||
return transaction.SaveGroups(ctx, store.LockingStrengthUpdate, groupsToSave)
|
||||
return transaction.SaveGroups(ctx, store.LockingStrengthUpdate, accountID, groupsToSave)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -517,7 +517,7 @@ func (s *GRPCServer) prepareLoginResponse(ctx context.Context, peer *nbpeer.Peer
|
||||
// if peer has reached this point then it has logged in
|
||||
loginResp := &proto.LoginResponse{
|
||||
NetbirdConfig: toNetbirdConfig(s.config, nil, relayToken, nil),
|
||||
PeerConfig: toPeerConfig(peer, netMap.Network, s.accountManager.GetDNSDomain(settings), false),
|
||||
PeerConfig: toPeerConfig(peer, netMap.Network, s.accountManager.GetDNSDomain(settings), settings),
|
||||
Checks: toProtocolChecks(ctx, postureChecks),
|
||||
}
|
||||
|
||||
@@ -632,20 +632,21 @@ func toNetbirdConfig(config *types.Config, turnCredentials *Token, relayToken *T
|
||||
return nbConfig
|
||||
}
|
||||
|
||||
func toPeerConfig(peer *nbpeer.Peer, network *types.Network, dnsName string, dnsResolutionOnRoutingPeerEnabled bool) *proto.PeerConfig {
|
||||
func toPeerConfig(peer *nbpeer.Peer, network *types.Network, dnsName string, settings *types.Settings) *proto.PeerConfig {
|
||||
netmask, _ := network.Net.Mask.Size()
|
||||
fqdn := peer.FQDN(dnsName)
|
||||
return &proto.PeerConfig{
|
||||
Address: fmt.Sprintf("%s/%d", peer.IP.String(), netmask), // take it from the network
|
||||
SshConfig: &proto.SSHConfig{SshEnabled: peer.SSHEnabled},
|
||||
Fqdn: fqdn,
|
||||
RoutingPeerDnsResolutionEnabled: dnsResolutionOnRoutingPeerEnabled,
|
||||
RoutingPeerDnsResolutionEnabled: settings.RoutingPeerDNSResolutionEnabled,
|
||||
LazyConnectionEnabled: settings.LazyConnectionEnabled,
|
||||
}
|
||||
}
|
||||
|
||||
func toSyncResponse(ctx context.Context, config *types.Config, peer *nbpeer.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*posture.Checks, dnsCache *DNSConfigCache, dnsResolutionOnRoutingPeerEnabled bool, extraSettings *types.ExtraSettings) *proto.SyncResponse {
|
||||
func toSyncResponse(ctx context.Context, config *types.Config, peer *nbpeer.Peer, turnCredentials *Token, relayCredentials *Token, networkMap *types.NetworkMap, dnsName string, checks []*posture.Checks, dnsCache *DNSConfigCache, settings *types.Settings, extraSettings *types.ExtraSettings) *proto.SyncResponse {
|
||||
response := &proto.SyncResponse{
|
||||
PeerConfig: toPeerConfig(peer, networkMap.Network, dnsName, dnsResolutionOnRoutingPeerEnabled),
|
||||
PeerConfig: toPeerConfig(peer, networkMap.Network, dnsName, settings),
|
||||
NetworkMap: &proto.NetworkMap{
|
||||
Serial: networkMap.Network.CurrentSerial(),
|
||||
Routes: toProtocolRoutes(networkMap.Routes),
|
||||
@@ -731,7 +732,7 @@ func (s *GRPCServer) sendInitialSync(ctx context.Context, peerKey wgtypes.Key, p
|
||||
return status.Errorf(codes.Internal, "error handling request")
|
||||
}
|
||||
|
||||
plainResp := toSyncResponse(ctx, s.config, peer, turnToken, relayToken, networkMap, s.accountManager.GetDNSDomain(settings), postureChecks, nil, settings.RoutingPeerDNSResolutionEnabled, settings.Extra)
|
||||
plainResp := toSyncResponse(ctx, s.config, peer, turnToken, relayToken, networkMap, s.accountManager.GetDNSDomain(settings), postureChecks, nil, settings, settings.Extra)
|
||||
|
||||
encryptedResp, err := encryption.EncryptMessage(peerKey, s.wgKey, plainResp)
|
||||
if err != nil {
|
||||
|
||||
@@ -118,6 +118,11 @@ components:
|
||||
example: my-organization.org
|
||||
extra:
|
||||
$ref: '#/components/schemas/AccountExtraSettings'
|
||||
lazy_connection_enabled:
|
||||
x-experimental: true
|
||||
description: Enables or disables experimental lazy connection
|
||||
type: boolean
|
||||
example: true
|
||||
required:
|
||||
- peer_login_expiration_enabled
|
||||
- peer_login_expiration
|
||||
@@ -1920,13 +1925,71 @@ components:
|
||||
- os
|
||||
- address
|
||||
- dns_label
|
||||
NetworkTrafficEvent:
|
||||
NetworkTrafficUser:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: "ID of the event. Unique."
|
||||
example: "18e204d6-f7c6-405d-8025-70becb216add"
|
||||
description: "UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated)."
|
||||
example: "google-oauth2|123456789012345678901"
|
||||
email:
|
||||
type: string
|
||||
description: "Email of the user who initiated the event (if any)."
|
||||
example: "alice@netbird.io"
|
||||
name:
|
||||
type: string
|
||||
description: "Name of the user who initiated the event (if any)."
|
||||
example: "Alice Smith"
|
||||
required:
|
||||
- id
|
||||
- email
|
||||
- name
|
||||
NetworkTrafficPolicy:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
description: "ID of the policy that allowed this event."
|
||||
example: "ch8i4ug6lnn4g9hqv7m0"
|
||||
name:
|
||||
type: string
|
||||
description: "Name of the policy that allowed this event."
|
||||
example: "All to All"
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
NetworkTrafficICMP:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: integer
|
||||
description: "ICMP type (if applicable)."
|
||||
example: 8
|
||||
code:
|
||||
type: integer
|
||||
description: "ICMP code (if applicable)."
|
||||
example: 0
|
||||
required:
|
||||
- type
|
||||
- code
|
||||
NetworkTrafficSubEvent:
|
||||
type: object
|
||||
properties:
|
||||
type:
|
||||
type: string
|
||||
description: Type of the event (e.g., TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP).
|
||||
example: TYPE_START
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: Timestamp of the event as sent by the peer.
|
||||
example: 2025-03-20T16:23:58.125397Z
|
||||
required:
|
||||
- type
|
||||
- timestamp
|
||||
NetworkTrafficEvent:
|
||||
type: object
|
||||
properties:
|
||||
flow_id:
|
||||
type: string
|
||||
description: "FlowID is the ID of the connection flow. Not unique because it can be the same for multiple events (e.g., start and end of the connection)."
|
||||
@@ -1935,43 +1998,20 @@ components:
|
||||
type: string
|
||||
description: "ID of the reporter of the event (e.g., the peer that reported the event)."
|
||||
example: "ch8i4ug6lnn4g9hqv7m0"
|
||||
timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: "Timestamp of the event. Send by the peer."
|
||||
example: "2025-03-20T16:23:58.125397Z"
|
||||
receive_timestamp:
|
||||
type: string
|
||||
format: date-time
|
||||
description: "Timestamp when the event was received by our API."
|
||||
example: "2025-03-20T16:23:58.125397Z"
|
||||
source:
|
||||
$ref: '#/components/schemas/NetworkTrafficEndpoint'
|
||||
user_id:
|
||||
type: string
|
||||
nullable: true
|
||||
description: "UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated)."
|
||||
example: "google-oauth2|123456789012345678901"
|
||||
user_email:
|
||||
type: string
|
||||
nullable: true
|
||||
description: "Email of the user who initiated the event (if any)."
|
||||
example: "alice@netbird.io"
|
||||
user_name:
|
||||
type: string
|
||||
nullable: true
|
||||
description: "Name of the user who initiated the event (if any)."
|
||||
example: "Alice Smith"
|
||||
destination:
|
||||
$ref: '#/components/schemas/NetworkTrafficEndpoint'
|
||||
user:
|
||||
$ref: '#/components/schemas/NetworkTrafficUser'
|
||||
policy:
|
||||
$ref: '#/components/schemas/NetworkTrafficPolicy'
|
||||
icmp:
|
||||
$ref: '#/components/schemas/NetworkTrafficICMP'
|
||||
protocol:
|
||||
type: integer
|
||||
description: "Protocol is the protocol of the traffic (e.g. 1 = ICMP, 6 = TCP, 17 = UDP, etc.)."
|
||||
example: 6
|
||||
type:
|
||||
type: string
|
||||
description: "Type of the event (e.g. TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP)."
|
||||
example: "TYPE_START"
|
||||
direction:
|
||||
type: string
|
||||
description: "Direction of the traffic (e.g. DIRECTION_UNKNOWN, INGRESS, EGRESS)."
|
||||
@@ -1992,43 +2032,28 @@ components:
|
||||
type: integer
|
||||
description: "Number of packets transmitted."
|
||||
example: 5
|
||||
policy_id:
|
||||
type: string
|
||||
description: "ID of the policy that allowed this event."
|
||||
example: "ch8i4ug6lnn4g9hqv7m0"
|
||||
policy_name:
|
||||
type: string
|
||||
description: "Name of the policy that allowed this event."
|
||||
example: "All to All"
|
||||
icmp_type:
|
||||
type: integer
|
||||
description: "ICMP type (if applicable)."
|
||||
example: 8
|
||||
icmp_code:
|
||||
type: integer
|
||||
description: "ICMP code (if applicable)."
|
||||
example: 0
|
||||
events:
|
||||
type: array
|
||||
description: "List of events that are correlated to this flow (e.g., start, end)."
|
||||
items:
|
||||
$ref: '#/components/schemas/NetworkTrafficSubEvent'
|
||||
required:
|
||||
- id
|
||||
- flow_id
|
||||
- reporter_id
|
||||
- timestamp
|
||||
- receive_timestamp
|
||||
- source
|
||||
- user_id
|
||||
- user_email
|
||||
- destination
|
||||
- user
|
||||
- policy
|
||||
- icmp
|
||||
- protocol
|
||||
- type
|
||||
- direction
|
||||
- rx_bytes
|
||||
- rx_packets
|
||||
- tx_bytes
|
||||
- tx_packets
|
||||
- policy_id
|
||||
- policy_name
|
||||
- icmp_type
|
||||
- icmp_code
|
||||
- events
|
||||
NetworkTrafficEventsResponse:
|
||||
type: object
|
||||
properties:
|
||||
@@ -4043,6 +4068,31 @@ paths:
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/networks/routers:
|
||||
get:
|
||||
summary: List all Network Routers
|
||||
description: Returns a list of all routers in a network
|
||||
tags: [ Networks ]
|
||||
security:
|
||||
- BearerAuth: [ ]
|
||||
- TokenAuth: [ ]
|
||||
responses:
|
||||
'200':
|
||||
description: A JSON Array of Routers
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/NetworkRouter'
|
||||
'400':
|
||||
"$ref": "#/components/responses/bad_request"
|
||||
'401':
|
||||
"$ref": "#/components/responses/requires_authentication"
|
||||
'403':
|
||||
"$ref": "#/components/responses/forbidden"
|
||||
'500':
|
||||
"$ref": "#/components/responses/internal_error"
|
||||
/api/dns/nameservers:
|
||||
get:
|
||||
summary: List all Nameserver Groups
|
||||
@@ -4295,6 +4345,12 @@ paths:
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: reporter_id
|
||||
in: query
|
||||
description: Filter by reporter ID
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- name: protocol
|
||||
in: query
|
||||
description: Filter by protocol
|
||||
@@ -4324,7 +4380,7 @@ paths:
|
||||
enum: [INGRESS, EGRESS, DIRECTION_UNKNOWN]
|
||||
- name: search
|
||||
in: query
|
||||
description: Filters events with a partial match on user email, source and destination names and source and destination addresses
|
||||
description: Case-insensitive partial match on user email, source/destination names, and source/destination addresses
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
|
||||
@@ -289,6 +289,9 @@ type AccountSettings struct {
|
||||
// JwtGroupsEnabled Allows extract groups from JWT claim and add it to account groups.
|
||||
JwtGroupsEnabled *bool `json:"jwt_groups_enabled,omitempty"`
|
||||
|
||||
// LazyConnectionEnabled Enables or disables experimental lazy connection
|
||||
LazyConnectionEnabled *bool `json:"lazy_connection_enabled,omitempty"`
|
||||
|
||||
// PeerInactivityExpiration Period of time of inactivity after which peer session expires (seconds).
|
||||
PeerInactivityExpiration int `json:"peer_inactivity_expiration"`
|
||||
|
||||
@@ -880,30 +883,17 @@ type NetworkTrafficEvent struct {
|
||||
// Direction Direction of the traffic (e.g. DIRECTION_UNKNOWN, INGRESS, EGRESS).
|
||||
Direction string `json:"direction"`
|
||||
|
||||
// Events List of events that are correlated to this flow (e.g., start, end).
|
||||
Events []NetworkTrafficSubEvent `json:"events"`
|
||||
|
||||
// FlowId FlowID is the ID of the connection flow. Not unique because it can be the same for multiple events (e.g., start and end of the connection).
|
||||
FlowId string `json:"flow_id"`
|
||||
|
||||
// IcmpCode ICMP code (if applicable).
|
||||
IcmpCode int `json:"icmp_code"`
|
||||
|
||||
// IcmpType ICMP type (if applicable).
|
||||
IcmpType int `json:"icmp_type"`
|
||||
|
||||
// Id ID of the event. Unique.
|
||||
Id string `json:"id"`
|
||||
|
||||
// PolicyId ID of the policy that allowed this event.
|
||||
PolicyId string `json:"policy_id"`
|
||||
|
||||
// PolicyName Name of the policy that allowed this event.
|
||||
PolicyName string `json:"policy_name"`
|
||||
FlowId string `json:"flow_id"`
|
||||
Icmp NetworkTrafficICMP `json:"icmp"`
|
||||
Policy NetworkTrafficPolicy `json:"policy"`
|
||||
|
||||
// Protocol Protocol is the protocol of the traffic (e.g. 1 = ICMP, 6 = TCP, 17 = UDP, etc.).
|
||||
Protocol int `json:"protocol"`
|
||||
|
||||
// ReceiveTimestamp Timestamp when the event was received by our API.
|
||||
ReceiveTimestamp time.Time `json:"receive_timestamp"`
|
||||
|
||||
// ReporterId ID of the reporter of the event (e.g., the peer that reported the event).
|
||||
ReporterId string `json:"reporter_id"`
|
||||
|
||||
@@ -914,26 +904,12 @@ type NetworkTrafficEvent struct {
|
||||
RxPackets int `json:"rx_packets"`
|
||||
Source NetworkTrafficEndpoint `json:"source"`
|
||||
|
||||
// Timestamp Timestamp of the event. Send by the peer.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// TxBytes Number of bytes transmitted.
|
||||
TxBytes int `json:"tx_bytes"`
|
||||
|
||||
// TxPackets Number of packets transmitted.
|
||||
TxPackets int `json:"tx_packets"`
|
||||
|
||||
// Type Type of the event (e.g. TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP).
|
||||
Type string `json:"type"`
|
||||
|
||||
// UserEmail Email of the user who initiated the event (if any).
|
||||
UserEmail *string `json:"user_email"`
|
||||
|
||||
// UserId UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated).
|
||||
UserId *string `json:"user_id"`
|
||||
|
||||
// UserName Name of the user who initiated the event (if any).
|
||||
UserName *string `json:"user_name"`
|
||||
TxPackets int `json:"tx_packets"`
|
||||
User NetworkTrafficUser `json:"user"`
|
||||
}
|
||||
|
||||
// NetworkTrafficEventsResponse defines model for NetworkTrafficEventsResponse.
|
||||
@@ -954,6 +930,15 @@ type NetworkTrafficEventsResponse struct {
|
||||
TotalRecords int `json:"total_records"`
|
||||
}
|
||||
|
||||
// NetworkTrafficICMP defines model for NetworkTrafficICMP.
|
||||
type NetworkTrafficICMP struct {
|
||||
// Code ICMP code (if applicable).
|
||||
Code int `json:"code"`
|
||||
|
||||
// Type ICMP type (if applicable).
|
||||
Type int `json:"type"`
|
||||
}
|
||||
|
||||
// NetworkTrafficLocation defines model for NetworkTrafficLocation.
|
||||
type NetworkTrafficLocation struct {
|
||||
// CityName Name of the city (if known).
|
||||
@@ -963,6 +948,36 @@ type NetworkTrafficLocation struct {
|
||||
CountryCode string `json:"country_code"`
|
||||
}
|
||||
|
||||
// NetworkTrafficPolicy defines model for NetworkTrafficPolicy.
|
||||
type NetworkTrafficPolicy struct {
|
||||
// Id ID of the policy that allowed this event.
|
||||
Id string `json:"id"`
|
||||
|
||||
// Name Name of the policy that allowed this event.
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// NetworkTrafficSubEvent defines model for NetworkTrafficSubEvent.
|
||||
type NetworkTrafficSubEvent struct {
|
||||
// Timestamp Timestamp of the event as sent by the peer.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// Type Type of the event (e.g., TYPE_UNKNOWN, TYPE_START, TYPE_END, TYPE_DROP).
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// NetworkTrafficUser defines model for NetworkTrafficUser.
|
||||
type NetworkTrafficUser struct {
|
||||
// Email Email of the user who initiated the event (if any).
|
||||
Email string `json:"email"`
|
||||
|
||||
// Id UserID is the ID of the user that initiated the event (can be empty as not every event is user-initiated).
|
||||
Id string `json:"id"`
|
||||
|
||||
// Name Name of the user who initiated the event (if any).
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// OSVersionCheck Posture check for the version of operating system
|
||||
type OSVersionCheck struct {
|
||||
// Android Posture check for the version of operating system
|
||||
@@ -1784,6 +1799,9 @@ type GetApiEventsNetworkTrafficParams struct {
|
||||
// UserId Filter by user ID
|
||||
UserId *string `form:"user_id,omitempty" json:"user_id,omitempty"`
|
||||
|
||||
// ReporterId Filter by reporter ID
|
||||
ReporterId *string `form:"reporter_id,omitempty" json:"reporter_id,omitempty"`
|
||||
|
||||
// Protocol Filter by protocol
|
||||
Protocol *int `form:"protocol,omitempty" json:"protocol,omitempty"`
|
||||
|
||||
@@ -1796,7 +1814,7 @@ type GetApiEventsNetworkTrafficParams struct {
|
||||
// Direction Filter by direction
|
||||
Direction *GetApiEventsNetworkTrafficParamsDirection `form:"direction,omitempty" json:"direction,omitempty"`
|
||||
|
||||
// Search Filters events with a partial match on user email, source and destination names and source and destination addresses
|
||||
// Search Case-insensitive partial match on user email, source/destination names, and source/destination addresses
|
||||
Search *string `form:"search,omitempty" json:"search,omitempty"`
|
||||
|
||||
// StartDate Start date for filtering events (ISO 8601 format, e.g., 2024-01-01T00:00:00Z).
|
||||
|
||||
@@ -122,6 +122,9 @@ func (h *handler) updateAccount(w http.ResponseWriter, r *http.Request) {
|
||||
if req.Settings.DnsDomain != nil {
|
||||
settings.DNSDomain = *req.Settings.DnsDomain
|
||||
}
|
||||
if req.Settings.LazyConnectionEnabled != nil {
|
||||
settings.LazyConnectionEnabled = *req.Settings.LazyConnectionEnabled
|
||||
}
|
||||
|
||||
updatedAccount, err := h.accountManager.UpdateAccountSettings(r.Context(), accountID, userID, settings)
|
||||
if err != nil {
|
||||
@@ -181,6 +184,7 @@ func toAccountResponse(accountID string, settings *types.Settings, meta *types.A
|
||||
JwtAllowGroups: &jwtAllowGroups,
|
||||
RegularUsersViewBlocked: settings.RegularUsersViewBlocked,
|
||||
RoutingPeerDnsResolutionEnabled: &settings.RoutingPeerDNSResolutionEnabled,
|
||||
LazyConnectionEnabled: &settings.LazyConnectionEnabled,
|
||||
DnsDomain: &settings.DNSDomain,
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
JwtAllowGroups: &[]string{},
|
||||
RegularUsersViewBlocked: true,
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
},
|
||||
expectedArray: true,
|
||||
@@ -129,6 +130,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
JwtAllowGroups: &[]string{},
|
||||
RegularUsersViewBlocked: false,
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
},
|
||||
expectedArray: false,
|
||||
@@ -150,6 +152,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
JwtAllowGroups: &[]string{"test"},
|
||||
RegularUsersViewBlocked: true,
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
},
|
||||
expectedArray: false,
|
||||
@@ -171,6 +174,7 @@ func TestAccounts_AccountsHandler(t *testing.T) {
|
||||
JwtAllowGroups: &[]string{},
|
||||
RegularUsersViewBlocked: true,
|
||||
RoutingPeerDnsResolutionEnabled: br(false),
|
||||
LazyConnectionEnabled: br(false),
|
||||
DnsDomain: sr(""),
|
||||
},
|
||||
expectedArray: false,
|
||||
|
||||
@@ -19,7 +19,8 @@ type routersHandler struct {
|
||||
|
||||
func addRouterEndpoints(routersManager routers.Manager, router *mux.Router) {
|
||||
routersHandler := newRoutersHandler(routersManager)
|
||||
router.HandleFunc("/networks/{networkId}/routers", routersHandler.getAllRouters).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/networks/routers", routersHandler.getAllRouters).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/networks/{networkId}/routers", routersHandler.getNetworkRouters).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/networks/{networkId}/routers", routersHandler.createRouter).Methods("POST", "OPTIONS")
|
||||
router.HandleFunc("/networks/{networkId}/routers/{routerId}", routersHandler.getRouter).Methods("GET", "OPTIONS")
|
||||
router.HandleFunc("/networks/{networkId}/routers/{routerId}", routersHandler.updateRouter).Methods("PUT", "OPTIONS")
|
||||
@@ -41,6 +42,31 @@ func (h *routersHandler) getAllRouters(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
accountID, userID := userAuth.AccountId, userAuth.UserId
|
||||
|
||||
routersMap, err := h.routersManager.GetAllRoutersInAccount(r.Context(), accountID, userID)
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
routersResponse := make([]*api.NetworkRouter, 0)
|
||||
for _, routers := range routersMap {
|
||||
for _, router := range routers {
|
||||
routersResponse = append(routersResponse, router.ToAPIResponse())
|
||||
}
|
||||
}
|
||||
|
||||
util.WriteJSONObject(r.Context(), w, routersResponse)
|
||||
}
|
||||
|
||||
func (h *routersHandler) getNetworkRouters(w http.ResponseWriter, r *http.Request) {
|
||||
userAuth, err := nbcontext.GetUserAuthFromContext(r.Context())
|
||||
if err != nil {
|
||||
util.WriteError(r.Context(), err, w)
|
||||
return
|
||||
}
|
||||
|
||||
accountID, userID := userAuth.AccountId, userAuth.UserId
|
||||
|
||||
networkID := mux.Vars(r)["networkId"]
|
||||
routers, err := h.routersManager.GetAllRoutersInNetwork(r.Context(), accountID, userID, networkID)
|
||||
if err != nil {
|
||||
|
||||
@@ -18,7 +18,9 @@ import (
|
||||
"github.com/netbirdio/netbird/management/server/types"
|
||||
)
|
||||
|
||||
const domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,}$`
|
||||
const domainPattern = `^(?i)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*[*.a-z]{1,}$`
|
||||
|
||||
var invalidDomainName = errors.New("invalid domain name")
|
||||
|
||||
// GetNameServerGroup gets a nameserver group object from account and nameserver group IDs
|
||||
func (am *DefaultAccountManager) GetNameServerGroup(ctx context.Context, accountID, userID, nsGroupID string) (*nbdns.NameServerGroup, error) {
|
||||
@@ -319,13 +321,9 @@ func validateDomain(domain string) error {
|
||||
return errors.New("domain should consists of only letters, numbers, and hyphens with no leading, trailing hyphens, or spaces")
|
||||
}
|
||||
|
||||
labels, valid := dns.IsDomainName(domain)
|
||||
_, valid := dns.IsDomainName(domain)
|
||||
if !valid {
|
||||
return errors.New("invalid domain name")
|
||||
}
|
||||
|
||||
if labels < 2 {
|
||||
return errors.New("domain should consists of a minimum of two labels")
|
||||
return invalidDomainName
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -899,13 +899,33 @@ func TestValidateDomain(t *testing.T) {
|
||||
errFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain name with double hyphen",
|
||||
domain: "test--example.com",
|
||||
name: "Valid domain name with only one label",
|
||||
domain: "example",
|
||||
errFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "Valid domain name with trailing dot",
|
||||
domain: "example.",
|
||||
errFunc: require.NoError,
|
||||
},
|
||||
{
|
||||
name: "Invalid wildcard domain name",
|
||||
domain: "*.example",
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain name with only one label",
|
||||
domain: "com",
|
||||
name: "Invalid domain name with leading dot",
|
||||
domain: ".com",
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain name with dot only",
|
||||
domain: ".",
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
name: "Invalid domain name with double hyphen",
|
||||
domain: "test--example.com",
|
||||
errFunc: require.Error,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -17,6 +17,7 @@ import (
|
||||
|
||||
"github.com/netbirdio/netbird/management/domain"
|
||||
"github.com/netbirdio/netbird/management/server/geolocation"
|
||||
routerTypes "github.com/netbirdio/netbird/management/server/networks/routers/types"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/modules"
|
||||
"github.com/netbirdio/netbird/management/server/permissions/operations"
|
||||
|
||||
@@ -352,7 +353,7 @@ func (am *DefaultAccountManager) DeletePeer(ctx context.Context, accountID, peer
|
||||
return err
|
||||
}
|
||||
|
||||
if err = am.validatePeerDelete(ctx, accountID, peerID); err != nil {
|
||||
if err = am.validatePeerDelete(ctx, transaction, accountID, peerID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1221,7 +1222,7 @@ func (am *DefaultAccountManager) UpdateAccountPeers(ctx context.Context, account
|
||||
return
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled, extraSetting)
|
||||
update := toSyncResponse(ctx, nil, p, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSetting)
|
||||
am.peersUpdateManager.SendUpdate(ctx, p.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}(peer)
|
||||
}
|
||||
@@ -1306,7 +1307,7 @@ func (am *DefaultAccountManager) UpdateAccountPeer(ctx context.Context, accountI
|
||||
return
|
||||
}
|
||||
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings.RoutingPeerDNSResolutionEnabled, extraSettings)
|
||||
update := toSyncResponse(ctx, nil, peer, nil, nil, remotePeerNetworkMap, dnsDomain, postureChecks, dnsCache, account.Settings, extraSettings)
|
||||
am.peersUpdateManager.SendUpdate(ctx, peer.ID, &UpdateMessage{Update: update, NetworkMap: remotePeerNetworkMap})
|
||||
}
|
||||
|
||||
@@ -1543,7 +1544,7 @@ func ConvertSliceToMap(existingLabels []string) map[string]struct{} {
|
||||
}
|
||||
|
||||
// validatePeerDelete checks if the peer can be deleted.
|
||||
func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, accountId, peerId string) error {
|
||||
func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, transaction store.Store, accountId, peerId string) error {
|
||||
linkedInIngressPorts, err := am.proxyController.IsPeerInIngressPorts(ctx, accountId, peerId)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -1553,5 +1554,27 @@ func (am *DefaultAccountManager) validatePeerDelete(ctx context.Context, account
|
||||
return status.Errorf(status.PreconditionFailed, "peer is linked to ingress ports: %s", peerId)
|
||||
}
|
||||
|
||||
linked, router := isPeerLinkedToNetworkRouter(ctx, transaction, accountId, peerId)
|
||||
if linked {
|
||||
return status.Errorf(status.PreconditionFailed, "peer is linked to a network router: %s", router.ID)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isPeerLinkedToNetworkRouter checks if a peer is linked to any network router in the account.
|
||||
func isPeerLinkedToNetworkRouter(ctx context.Context, transaction store.Store, accountID string, peerID string) (bool, *routerTypes.NetworkRouter) {
|
||||
routers, err := transaction.GetNetworkRoutersByAccountID(ctx, store.LockingStrengthShare, accountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Errorf("error retrieving network routers while checking peer linkage: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
for _, router := range routers {
|
||||
if router.Peer == peerID {
|
||||
return true, router
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
@@ -1157,8 +1157,8 @@ func TestToSyncResponse(t *testing.T) {
|
||||
},
|
||||
}
|
||||
dnsCache := &DNSConfigCache{}
|
||||
|
||||
response := toSyncResponse(context.Background(), config, peer, turnRelayToken, turnRelayToken, networkMap, dnsName, checks, dnsCache, true, nil)
|
||||
accountSettings := &types.Settings{RoutingPeerDNSResolutionEnabled: true}
|
||||
response := toSyncResponse(context.Background(), config, peer, turnRelayToken, turnRelayToken, networkMap, dnsName, checks, dnsCache, accountSettings, nil)
|
||||
|
||||
assert.NotNil(t, response)
|
||||
// assert peer config
|
||||
|
||||
@@ -455,7 +455,7 @@ func TestArePostureCheckChangesAffectPeers(t *testing.T) {
|
||||
AccountID: account.Id,
|
||||
Peers: []string{},
|
||||
}
|
||||
err = manager.Store.SaveGroups(context.Background(), store.LockingStrengthUpdate, []*types.Group{groupA, groupB})
|
||||
err = manager.Store.SaveGroups(context.Background(), store.LockingStrengthUpdate, account.Id, []*types.Group{groupA, groupB})
|
||||
require.NoError(t, err, "failed to save groups")
|
||||
|
||||
postureCheckA := &posture.Checks{
|
||||
|
||||
@@ -448,12 +448,20 @@ func (s *SqlStore) SaveUser(ctx context.Context, lockStrength LockingStrength, u
|
||||
}
|
||||
|
||||
// SaveGroups saves the given list of groups to the database.
|
||||
func (s *SqlStore) SaveGroups(ctx context.Context, lockStrength LockingStrength, groups []*types.Group) error {
|
||||
func (s *SqlStore) SaveGroups(ctx context.Context, lockStrength LockingStrength, accountID string, groups []*types.Group) error {
|
||||
if len(groups) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
result := s.db.Clauses(clause.Locking{Strength: string(lockStrength)}, clause.OnConflict{UpdateAll: true}).Create(&groups)
|
||||
result := s.db.
|
||||
Clauses(
|
||||
clause.Locking{Strength: string(lockStrength)},
|
||||
clause.OnConflict{
|
||||
Where: clause.Where{Exprs: []clause.Expression{clause.Eq{Column: "groups.account_id", Value: accountID}}},
|
||||
UpdateAll: true,
|
||||
},
|
||||
).
|
||||
Create(&groups)
|
||||
if result.Error != nil {
|
||||
return status.Errorf(status.Internal, "failed to save groups to store: %v", result.Error)
|
||||
}
|
||||
|
||||
@@ -1324,11 +1324,11 @@ func TestSqlStore_SaveGroups(t *testing.T) {
|
||||
Peers: []string{"peer3", "peer4"},
|
||||
},
|
||||
}
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, groups)
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, accountID, groups)
|
||||
require.NoError(t, err)
|
||||
|
||||
groups[1].Peers = []string{}
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, groups)
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, accountID, groups)
|
||||
require.NoError(t, err)
|
||||
|
||||
group, err := store.GetGroupByID(context.Background(), LockingStrengthShare, accountID, groups[1].ID)
|
||||
@@ -3240,7 +3240,7 @@ func TestSqlStore_SaveGroups_LargeBatch(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, groupsToSave)
|
||||
err = store.SaveGroups(context.Background(), LockingStrengthUpdate, accountID, groupsToSave)
|
||||
require.NoError(t, err)
|
||||
|
||||
accountGroups, err = store.GetAccountGroups(context.Background(), LockingStrengthShare, accountID)
|
||||
|
||||
@@ -98,7 +98,7 @@ type Store interface {
|
||||
GetGroupByID(ctx context.Context, lockStrength LockingStrength, accountID, groupID string) (*types.Group, error)
|
||||
GetGroupByName(ctx context.Context, lockStrength LockingStrength, groupName, accountID string) (*types.Group, error)
|
||||
GetGroupsByIDs(ctx context.Context, lockStrength LockingStrength, accountID string, groupIDs []string) (map[string]*types.Group, error)
|
||||
SaveGroups(ctx context.Context, lockStrength LockingStrength, groups []*types.Group) error
|
||||
SaveGroups(ctx context.Context, lockStrength LockingStrength, accountID string, groups []*types.Group) error
|
||||
SaveGroup(ctx context.Context, lockStrength LockingStrength, group *types.Group) error
|
||||
DeleteGroup(ctx context.Context, lockStrength LockingStrength, accountID, groupID string) error
|
||||
DeleteGroups(ctx context.Context, strength LockingStrength, accountID string, groupIDs []string) error
|
||||
@@ -365,11 +365,14 @@ func NewTestStoreFromSQL(ctx context.Context, filename string, dataDir string) (
|
||||
return nil, nil, fmt.Errorf("failed to add all group to account: %v", err)
|
||||
}
|
||||
|
||||
var sqlStore Store
|
||||
var cleanup func()
|
||||
|
||||
maxRetries := 2
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
sqlStore, cleanUp, err := getSqlStoreEngine(ctx, store, kind)
|
||||
sqlStore, cleanup, err = getSqlStoreEngine(ctx, store, kind)
|
||||
if err == nil {
|
||||
return sqlStore, cleanUp, nil
|
||||
return sqlStore, cleanup, nil
|
||||
}
|
||||
if i < maxRetries-1 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -427,16 +430,16 @@ func getSqlStoreEngine(ctx context.Context, store *SqlStore, kind types.Engine)
|
||||
}
|
||||
|
||||
func newReusedPostgresStore(ctx context.Context, store *SqlStore, kind types.Engine) (*SqlStore, func(), error) {
|
||||
if envDsn, ok := os.LookupEnv(postgresDsnEnv); !ok || envDsn == "" {
|
||||
dsn, ok := os.LookupEnv(postgresDsnEnv)
|
||||
if !ok || dsn == "" {
|
||||
var err error
|
||||
_, err = testutil.CreatePostgresTestContainer()
|
||||
_, dsn, err = testutil.CreatePostgresTestContainer()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dsn, ok := os.LookupEnv(postgresDsnEnv)
|
||||
if !ok {
|
||||
if dsn == "" {
|
||||
return nil, nil, fmt.Errorf("%s is not set", postgresDsnEnv)
|
||||
}
|
||||
|
||||
@@ -447,28 +450,28 @@ func newReusedPostgresStore(ctx context.Context, store *SqlStore, kind types.Eng
|
||||
|
||||
dsn, cleanup, err := createRandomDB(dsn, db, kind)
|
||||
if err != nil {
|
||||
return nil, cleanup, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
store, err = NewPostgresqlStoreFromSqlStore(ctx, store, dsn, nil)
|
||||
if err != nil {
|
||||
return nil, cleanup, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return store, cleanup, nil
|
||||
}
|
||||
|
||||
func newReusedMysqlStore(ctx context.Context, store *SqlStore, kind types.Engine) (*SqlStore, func(), error) {
|
||||
if envDsn, ok := os.LookupEnv(mysqlDsnEnv); !ok || envDsn == "" {
|
||||
dsn, ok := os.LookupEnv(mysqlDsnEnv)
|
||||
if !ok || dsn == "" {
|
||||
var err error
|
||||
_, err = testutil.CreateMysqlTestContainer()
|
||||
_, dsn, err = testutil.CreateMysqlTestContainer()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
dsn, ok := os.LookupEnv(mysqlDsnEnv)
|
||||
if !ok {
|
||||
if dsn == "" {
|
||||
return nil, nil, fmt.Errorf("%s is not set", mysqlDsnEnv)
|
||||
}
|
||||
|
||||
@@ -479,7 +482,7 @@ func newReusedMysqlStore(ctx context.Context, store *SqlStore, kind types.Engine
|
||||
|
||||
dsn, cleanup, err := createRandomDB(dsn, db, kind)
|
||||
if err != nil {
|
||||
return nil, cleanup, err
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
store, err = NewMysqlStoreFromSqlStore(ctx, store, dsn, nil)
|
||||
|
||||
@@ -5,7 +5,6 @@ package testutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
@@ -16,11 +15,25 @@ import (
|
||||
"github.com/testcontainers/testcontainers-go/wait"
|
||||
)
|
||||
|
||||
var (
|
||||
pgContainer *postgres.PostgresContainer
|
||||
mysqlContainer *mysql.MySQLContainer
|
||||
)
|
||||
|
||||
// CreateMysqlTestContainer creates a new MySQL container for testing.
|
||||
func CreateMysqlTestContainer() (func(), error) {
|
||||
func CreateMysqlTestContainer() (func(), string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
myContainer, err := mysql.RunContainer(ctx,
|
||||
if mysqlContainer != nil {
|
||||
connStr, err := mysqlContainer.ConnectionString(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return noOpCleanup, connStr, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
mysqlContainer, err = mysql.RunContainer(ctx,
|
||||
testcontainers.WithImage("mlsmaycon/warmed-mysql:8"),
|
||||
mysql.WithDatabase("testing"),
|
||||
mysql.WithUsername("root"),
|
||||
@@ -31,31 +44,42 @@ func CreateMysqlTestContainer() (func(), error) {
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
os.Unsetenv("NETBIRD_STORE_ENGINE_MYSQL_DSN")
|
||||
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancelFunc()
|
||||
if err = myContainer.Terminate(timeoutCtx); err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to stop mysql container %s: %s", myContainer.GetContainerID(), err)
|
||||
if mysqlContainer != nil {
|
||||
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancelFunc()
|
||||
if err = mysqlContainer.Terminate(timeoutCtx); err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to stop mysql container %s: %s", mysqlContainer.GetContainerID(), err)
|
||||
}
|
||||
mysqlContainer = nil // reset the container to allow recreation
|
||||
}
|
||||
}
|
||||
|
||||
talksConn, err := myContainer.ConnectionString(ctx)
|
||||
talksConn, err := mysqlContainer.ConnectionString(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_MYSQL_DSN", talksConn)
|
||||
return cleanup, talksConn, nil
|
||||
}
|
||||
|
||||
// CreatePostgresTestContainer creates a new PostgreSQL container for testing.
|
||||
func CreatePostgresTestContainer() (func(), error) {
|
||||
func CreatePostgresTestContainer() (func(), string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
pgContainer, err := postgres.RunContainer(ctx,
|
||||
if pgContainer != nil {
|
||||
connStr, err := pgContainer.ConnectionString(ctx)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
return noOpCleanup, connStr, nil
|
||||
}
|
||||
|
||||
var err error
|
||||
pgContainer, err = postgres.RunContainer(ctx,
|
||||
testcontainers.WithImage("postgres:16-alpine"),
|
||||
postgres.WithDatabase("netbird"),
|
||||
postgres.WithUsername("root"),
|
||||
@@ -66,24 +90,31 @@ func CreatePostgresTestContainer() (func(), error) {
|
||||
),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
os.Unsetenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN")
|
||||
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancelFunc()
|
||||
if err = pgContainer.Terminate(timeoutCtx); err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to stop postgres container %s: %s", pgContainer.GetContainerID(), err)
|
||||
if pgContainer != nil {
|
||||
timeoutCtx, cancelFunc := context.WithTimeout(ctx, 1*time.Second)
|
||||
defer cancelFunc()
|
||||
if err = pgContainer.Terminate(timeoutCtx); err != nil {
|
||||
log.WithContext(ctx).Warnf("failed to stop postgres container %s: %s", pgContainer.GetContainerID(), err)
|
||||
}
|
||||
pgContainer = nil // reset the container to allow recreation
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
talksConn, err := pgContainer.ConnectionString(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
return cleanup, os.Setenv("NETBIRD_STORE_ENGINE_POSTGRES_DSN", talksConn)
|
||||
return cleanup, talksConn, nil
|
||||
}
|
||||
|
||||
func noOpCleanup() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// CreateRedisTestContainer creates a new Redis container for testing.
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
|
||||
package testutil
|
||||
|
||||
func CreatePostgresTestContainer() (func(), error) {
|
||||
func CreatePostgresTestContainer() (func(), string, error) {
|
||||
return func() {
|
||||
// Empty function for Postgres
|
||||
}, nil
|
||||
}, "", nil
|
||||
}
|
||||
|
||||
func CreateMysqlTestContainer() (func(), error) {
|
||||
func CreateMysqlTestContainer() (func(), string, error) {
|
||||
return func() {
|
||||
// Empty function for MySQL
|
||||
}, nil
|
||||
}, "", nil
|
||||
}
|
||||
|
||||
func CreateRedisTestContainer() (func(), string, error) {
|
||||
|
||||
@@ -44,6 +44,9 @@ type Settings struct {
|
||||
|
||||
// Extra is a dictionary of Account settings
|
||||
Extra *ExtraSettings `gorm:"embedded;embeddedPrefix:extra_"`
|
||||
|
||||
// LazyConnectionEnabled indicates wether the experimental feature is enabled or disabled
|
||||
LazyConnectionEnabled bool `gorm:"default:false"`
|
||||
}
|
||||
|
||||
// Copy copies the Settings struct
|
||||
@@ -61,6 +64,7 @@ func (s *Settings) Copy() *Settings {
|
||||
PeerInactivityExpiration: s.PeerInactivityExpiration,
|
||||
|
||||
RoutingPeerDNSResolutionEnabled: s.RoutingPeerDNSResolutionEnabled,
|
||||
LazyConnectionEnabled: s.LazyConnectionEnabled,
|
||||
DNSDomain: s.DNSDomain,
|
||||
}
|
||||
if s.Extra != nil {
|
||||
|
||||
@@ -676,7 +676,7 @@ func (am *DefaultAccountManager) processUserUpdate(ctx context.Context, transact
|
||||
return false, nil, nil, nil, fmt.Errorf("error modifying user peers in groups: %w", err)
|
||||
}
|
||||
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, updatedGroups); err != nil {
|
||||
if err = transaction.SaveGroups(ctx, store.LockingStrengthUpdate, accountID, updatedGroups); err != nil {
|
||||
return false, nil, nil, nil, fmt.Errorf("error saving groups: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user