mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-22 08:21:30 +02:00
Compare commits
11 Commits
v0.45.0
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0aff276f27 | ||
|
|
670446d42e | ||
|
|
5bed6777d5 | ||
|
|
a0482ebc7b | ||
|
|
2a89d6e47a | ||
|
|
24f932b2ce | ||
|
|
c03435061c | ||
|
|
8e948739f1 | ||
|
|
9b53cad752 | ||
|
|
802a18167c | ||
|
|
e9108ffe6c |
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).
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"archive/zip"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -534,6 +535,33 @@ func (g *BundleGenerator) addLogfile() error {
|
||||
return fmt.Errorf("add client log file to zip: %w", err)
|
||||
}
|
||||
|
||||
// add latest rotated log file
|
||||
pattern := filepath.Join(logDir, "client-*.log.gz")
|
||||
files, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
log.Warnf("failed to glob rotated logs: %v", err)
|
||||
} else if len(files) > 0 {
|
||||
// pick the file with the latest ModTime
|
||||
sort.Slice(files, func(i, j int) bool {
|
||||
fi, err := os.Stat(files[i])
|
||||
if err != nil {
|
||||
log.Warnf("failed to stat rotated log %s: %v", files[i], err)
|
||||
return false
|
||||
}
|
||||
fj, err := os.Stat(files[j])
|
||||
if err != nil {
|
||||
log.Warnf("failed to stat rotated log %s: %v", files[j], err)
|
||||
return false
|
||||
}
|
||||
return fi.ModTime().Before(fj.ModTime())
|
||||
})
|
||||
latest := files[len(files)-1]
|
||||
name := filepath.Base(latest)
|
||||
if err := g.addSingleLogFileGz(latest, name); err != nil {
|
||||
log.Warnf("failed to add rotated log %s: %v", name, err)
|
||||
}
|
||||
}
|
||||
|
||||
stdErrLogPath := filepath.Join(logDir, errorLogFile)
|
||||
stdoutLogPath := filepath.Join(logDir, stdoutLogFile)
|
||||
if runtime.GOOS == "darwin" {
|
||||
@@ -564,16 +592,13 @@ func (g *BundleGenerator) addSingleLogfile(logPath, targetName string) error {
|
||||
}
|
||||
}()
|
||||
|
||||
var logReader io.Reader
|
||||
var logReader io.Reader = logFile
|
||||
if g.anonymize {
|
||||
var writer *io.PipeWriter
|
||||
logReader, writer = io.Pipe()
|
||||
|
||||
go anonymizeLog(logFile, writer, g.anonymizer)
|
||||
} else {
|
||||
logReader = logFile
|
||||
}
|
||||
|
||||
if err := g.addFileToZip(logReader, targetName); err != nil {
|
||||
return fmt.Errorf("add %s to zip: %w", targetName, err)
|
||||
}
|
||||
@@ -581,6 +606,44 @@ func (g *BundleGenerator) addSingleLogfile(logPath, targetName string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// addSingleLogFileGz adds a single gzipped log file to the archive
|
||||
func (g *BundleGenerator) addSingleLogFileGz(logPath, targetName string) error {
|
||||
f, err := os.Open(logPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open gz log file %s: %w", targetName, err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
gzr, err := gzip.NewReader(f)
|
||||
if err != nil {
|
||||
return fmt.Errorf("create gzip reader: %w", err)
|
||||
}
|
||||
defer gzr.Close()
|
||||
|
||||
var logReader io.Reader = gzr
|
||||
if g.anonymize {
|
||||
var pw *io.PipeWriter
|
||||
logReader, pw = io.Pipe()
|
||||
go anonymizeLog(gzr, pw, g.anonymizer)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
gw := gzip.NewWriter(&buf)
|
||||
if _, err := io.Copy(gw, logReader); err != nil {
|
||||
return fmt.Errorf("re-gzip: %w", err)
|
||||
}
|
||||
|
||||
if err := gw.Close(); err != nil {
|
||||
return fmt.Errorf("close gzip writer: %w", err)
|
||||
}
|
||||
|
||||
if err := g.addFileToZip(&buf, targetName); err != nil {
|
||||
return fmt.Errorf("add anonymized gz: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *BundleGenerator) addFileToZip(reader io.Reader, filename string) error {
|
||||
header := &zip.FileHeader{
|
||||
Name: filename,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -123,8 +123,14 @@ func (m *Manager) disableFlow() error {
|
||||
|
||||
m.logger.Close()
|
||||
|
||||
if m.receiverClient != nil {
|
||||
return m.receiverClient.Close()
|
||||
if m.receiverClient == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
err := m.receiverClient.Close()
|
||||
m.receiverClient = nil
|
||||
if err != nil {
|
||||
return fmt.Errorf("close: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
@@ -260,8 +260,6 @@ func (c *GrpcClient) receiveEvents(stream proto.ManagementService_SyncClient, se
|
||||
|
||||
if err := msgHandler(decryptedResp); err != nil {
|
||||
log.Errorf("failed handling an update message received from Management Service: %v", err.Error())
|
||||
// hide any grpc error code that is not relevant for management
|
||||
return fmt.Errorf("msg handler error: %v", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -12,9 +13,10 @@ import (
|
||||
|
||||
// Client Management service HTTP REST API Client
|
||||
type Client struct {
|
||||
managementURL string
|
||||
authHeader string
|
||||
httpClient HttpClient
|
||||
managementURL string
|
||||
authHeader string
|
||||
impersonatedAccount string
|
||||
httpClient HTTPClient
|
||||
|
||||
// Accounts NetBird account APIs
|
||||
// see more: https://docs.netbird.io/api/resources/accounts
|
||||
@@ -85,7 +87,8 @@ func NewWithBearerToken(managementURL, token string) *Client {
|
||||
)
|
||||
}
|
||||
|
||||
func NewWithOptions(opts ...option) *Client {
|
||||
// NewWithOptions initialize new Client instance with options
|
||||
func NewWithOptions(opts ...Option) *Client {
|
||||
client := &Client{
|
||||
httpClient: http.DefaultClient,
|
||||
}
|
||||
@@ -114,6 +117,7 @@ func (c *Client) initialize() {
|
||||
c.Events = &EventsAPI{c}
|
||||
}
|
||||
|
||||
// NewRequest creates and executes new management API request
|
||||
func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Reader) (*http.Response, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, method, c.managementURL+path, body)
|
||||
if err != nil {
|
||||
@@ -126,6 +130,12 @@ func (c *Client) NewRequest(ctx context.Context, method, path string, body io.Re
|
||||
req.Header.Add("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if c.impersonatedAccount != "" {
|
||||
query := req.URL.Query()
|
||||
query.Add("account", c.impersonatedAccount)
|
||||
req.URL.RawQuery = query.Encode()
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -134,7 +144,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 +156,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
|
||||
}
|
||||
|
||||
@@ -2,34 +2,50 @@ package rest
|
||||
|
||||
import "net/http"
|
||||
|
||||
type option func(*Client)
|
||||
// Option modifier for creation of Client
|
||||
type Option func(*Client)
|
||||
|
||||
type HttpClient interface {
|
||||
// HTTPClient interface for HTTP client
|
||||
type HTTPClient interface {
|
||||
Do(req *http.Request) (*http.Response, error)
|
||||
}
|
||||
|
||||
func WithHttpClient(client HttpClient) option {
|
||||
// WithHTTPClient overrides HTTPClient used
|
||||
func WithHTTPClient(client HTTPClient) Option {
|
||||
return func(c *Client) {
|
||||
c.httpClient = client
|
||||
}
|
||||
}
|
||||
|
||||
func WithBearerToken(token string) option {
|
||||
// WithBearerToken uses provided bearer token acquired from SSO for authentication
|
||||
func WithBearerToken(token string) Option {
|
||||
return WithAuthHeader("Bearer " + token)
|
||||
}
|
||||
|
||||
func WithPAT(token string) option {
|
||||
// WithPAT uses provided Personal Access Token
|
||||
// (created from NetBird Management Dashboard) for authentication
|
||||
func WithPAT(token string) Option {
|
||||
return WithAuthHeader("Token " + token)
|
||||
}
|
||||
|
||||
func WithManagementURL(url string) option {
|
||||
// WithManagementURL overrides target NetBird Management server
|
||||
func WithManagementURL(url string) Option {
|
||||
return func(c *Client) {
|
||||
c.managementURL = url
|
||||
}
|
||||
}
|
||||
|
||||
func WithAuthHeader(value string) option {
|
||||
// WithAuthHeader overrides auth header completely, this should generally not be used
|
||||
// and WithBearerToken or WithPAT should be used instead
|
||||
func WithAuthHeader(value string) Option {
|
||||
return func(c *Client) {
|
||||
c.authHeader = value
|
||||
}
|
||||
}
|
||||
|
||||
// WithAccount uses impersonated account for Client
|
||||
func WithAccount(value string) Option {
|
||||
return func(c *Client) {
|
||||
c.impersonatedAccount = value
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -4295,6 +4300,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 +4335,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"`
|
||||
|
||||
@@ -1784,6 +1787,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 +1802,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,
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1221,7 +1221,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 +1306,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})
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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, update.AccountID, updatedGroups); err != nil {
|
||||
return false, nil, nil, nil, fmt.Errorf("error saving groups: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user