mirror of
https://github.com/netbirdio/netbird.git
synced 2026-07-19 15:01:29 +02:00
Compare commits
1 Commits
dmitri-fil
...
profile-bi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59cc28702b |
294
client/ios/NetBirdSDK/profile_manager.go
Normal file
294
client/ios/NetBirdSDK/profile_manager.go
Normal file
@@ -0,0 +1,294 @@
|
||||
//go:build ios
|
||||
|
||||
package NetBirdSDK
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/netbirdio/netbird/client/internal/profilemanager"
|
||||
)
|
||||
|
||||
// iOS profile storage layout (mirrors the Android layout so the shared
|
||||
// profilemanager.ServiceManager behaves identically on both platforms):
|
||||
//
|
||||
// <container>/ ← configDir parameter (App Group root)
|
||||
// ├── netbird.cfg ← default profile config
|
||||
// ├── state.json ← default profile state
|
||||
// ├── active_profile.json ← active profile tracker {"name": <id>, "username": "ios"}
|
||||
// └── profiles/ ← non-default profiles
|
||||
// ├── <id>.json ← profile config (holds the display "Name")
|
||||
// └── <id>.state.json ← profile state
|
||||
//
|
||||
// The ProfileLayoutMigration in NetbirdKit moves the legacy directory-per-name
|
||||
// layout into this shape before NewProfileManager ever runs.
|
||||
|
||||
const (
|
||||
// iosDefaultConfigFilename is the default profile config name. Must match
|
||||
// GlobalConstants.configFileName on the Swift side ("netbird.cfg").
|
||||
iosDefaultConfigFilename = "netbird.cfg"
|
||||
// iosDefaultStateFilename is the default profile state name. Must match
|
||||
// GlobalConstants.stateFileName on the Swift side ("state.json").
|
||||
iosDefaultStateFilename = "state.json"
|
||||
// iosProfilesSubdir holds non-default profile files.
|
||||
iosProfilesSubdir = "profiles"
|
||||
// iosUsername is the single user context the app runs under. The value is
|
||||
// written into active_profile.json's "username" field and is required to be
|
||||
// non-empty for non-default profiles by ServiceManager.SetActiveProfileState.
|
||||
// Must match the value the migration writes ("ios").
|
||||
iosUsername = "ios"
|
||||
)
|
||||
|
||||
// Profile represents a profile for gomobile. gomobile exposes the exported
|
||||
// fields as id_/name/isActive on the Swift side.
|
||||
type Profile struct {
|
||||
ID string
|
||||
Name string
|
||||
IsActive bool
|
||||
}
|
||||
|
||||
// ProfileArray wraps a profile slice for gomobile (which cannot bind Go slices
|
||||
// directly; callers iterate with Length()/Get()).
|
||||
type ProfileArray struct {
|
||||
items []*Profile
|
||||
}
|
||||
|
||||
// Length returns the number of profiles.
|
||||
func (p *ProfileArray) Length() int {
|
||||
return len(p.items)
|
||||
}
|
||||
|
||||
// Get returns the profile at index i, or nil if i is out of range.
|
||||
func (p *ProfileArray) Get(i int) *Profile {
|
||||
if i < 0 || i >= len(p.items) {
|
||||
return nil
|
||||
}
|
||||
return p.items[i]
|
||||
}
|
||||
|
||||
// ProfileManager manages profiles for iOS. It wraps the internal
|
||||
// profilemanager.ServiceManager, which owns all profile identity (the on-disk
|
||||
// filename is the ID, the display name lives inside the config JSON).
|
||||
type ProfileManager struct {
|
||||
configDir string
|
||||
serviceMgr *profilemanager.ServiceManager
|
||||
}
|
||||
|
||||
// NewProfileManager creates a profile manager rooted at configDir (the App
|
||||
// Group shared container). gomobile maps this to a nullable Swift initializer.
|
||||
func NewProfileManager(configDir string) *ProfileManager {
|
||||
defaultConfigPath := filepath.Join(configDir, iosDefaultConfigFilename)
|
||||
|
||||
// Point the package-level paths at the iOS container. The default profile
|
||||
// lives in the root configDir (not under profiles/).
|
||||
profilemanager.DefaultConfigPathDir = configDir
|
||||
profilemanager.DefaultConfigPath = defaultConfigPath
|
||||
profilemanager.ActiveProfileStatePath = filepath.Join(configDir, "active_profile.json")
|
||||
|
||||
// A fixed profiles directory avoids mutating the global ConfigDirOverride;
|
||||
// the ServiceManager then ignores the username when resolving the directory.
|
||||
profilesDir := filepath.Join(configDir, iosProfilesSubdir)
|
||||
serviceMgr := profilemanager.NewServiceManagerWithProfilesDir(defaultConfigPath, profilesDir)
|
||||
|
||||
return &ProfileManager{
|
||||
configDir: configDir,
|
||||
serviceMgr: serviceMgr,
|
||||
}
|
||||
}
|
||||
|
||||
// ListProfiles returns all available profiles, including the default, with
|
||||
// their active status and resolved display names.
|
||||
func (pm *ProfileManager) ListProfiles() (*ProfileArray, error) {
|
||||
internalProfiles, err := pm.serviceMgr.ListProfiles(iosUsername)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list profiles: %w", err)
|
||||
}
|
||||
|
||||
var profiles []*Profile
|
||||
for _, p := range internalProfiles {
|
||||
profiles = append(profiles, &Profile{
|
||||
ID: p.ID.String(),
|
||||
Name: p.Name,
|
||||
IsActive: p.IsActive,
|
||||
})
|
||||
}
|
||||
|
||||
return &ProfileArray{items: profiles}, nil
|
||||
}
|
||||
|
||||
// GetActiveProfile returns the currently active profile with its display name
|
||||
// resolved. ActiveProfileState only records the ID, so the ID is resolved to a
|
||||
// full profile to recover the Name.
|
||||
func (pm *ProfileManager) GetActiveProfile() (*Profile, error) {
|
||||
activeState, err := pm.serviceMgr.GetActiveProfileState()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get active profile: %w", err)
|
||||
}
|
||||
|
||||
prof, err := pm.serviceMgr.ResolveProfile(activeState.ID.String(), iosUsername)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve active profile %q: %w", activeState.ID, err)
|
||||
}
|
||||
|
||||
return &Profile{ID: prof.ID.String(), Name: prof.Name, IsActive: true}, nil
|
||||
}
|
||||
|
||||
// AddProfile creates a new profile with displayName and returns it. The
|
||||
// returned profile carries the freshly generated ID, which callers must use
|
||||
// for all follow-up operations (the ID is NOT the display name).
|
||||
func (pm *ProfileManager) AddProfile(displayName string) (*Profile, error) {
|
||||
prof, err := pm.serviceMgr.AddProfile(displayName, iosUsername)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add profile: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("created new profile: %s", prof.ID)
|
||||
return &Profile{ID: prof.ID.String(), Name: prof.Name, IsActive: false}, nil
|
||||
}
|
||||
|
||||
// SwitchProfile records the given profile ID as the active profile. Callers
|
||||
// must stop the VPN before switching.
|
||||
func (pm *ProfileManager) SwitchProfile(id string) error {
|
||||
if err := pm.serviceMgr.SetActiveProfileState(&profilemanager.ActiveProfileState{
|
||||
ID: profilemanager.ID(id),
|
||||
Username: iosUsername,
|
||||
}); err != nil {
|
||||
return fmt.Errorf("failed to switch profile: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("switched to profile: %s", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RenameProfile changes a profile's display name. The on-disk ID (filename) is
|
||||
// unchanged. There is no ServiceManager rename, so this edits the Name field of
|
||||
// the config JSON in place.
|
||||
func (pm *ProfileManager) RenameProfile(id, newName string) error {
|
||||
if id == profilemanager.DefaultProfileName {
|
||||
return fmt.Errorf("cannot rename the default profile")
|
||||
}
|
||||
if !profilemanager.IsValidProfileFilenameStem(profilemanager.ID(id)) {
|
||||
return fmt.Errorf("invalid profile ID: %q", id)
|
||||
}
|
||||
|
||||
newName = strings.TrimSpace(newName)
|
||||
if newName == "" {
|
||||
return fmt.Errorf("profile name must not be empty")
|
||||
}
|
||||
if newName == profilemanager.DefaultProfileName {
|
||||
return fmt.Errorf("cannot use reserved profile name: %s", profilemanager.DefaultProfileName)
|
||||
}
|
||||
|
||||
configPath, err := pm.getProfileConfigPath(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("profile %q does not exist", id)
|
||||
}
|
||||
|
||||
config, err := profilemanager.ReadConfig(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read profile config: %w", err)
|
||||
}
|
||||
|
||||
config.Name = newName
|
||||
|
||||
if err := profilemanager.WriteOutConfig(configPath, config); err != nil {
|
||||
return fmt.Errorf("failed to write profile config: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("renamed profile %q to %q", id, newName)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveProfile deletes a profile. The default and the active profile cannot be
|
||||
// removed.
|
||||
func (pm *ProfileManager) RemoveProfile(id string) error {
|
||||
if err := pm.serviceMgr.RemoveProfile(profilemanager.ID(id), iosUsername); err != nil {
|
||||
return fmt.Errorf("failed to remove profile: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("removed profile: %s", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LogoutProfile clears a profile's authentication (private key and SSH key),
|
||||
// forcing re-login. The management URL is preserved in the config.
|
||||
func (pm *ProfileManager) LogoutProfile(id string) error {
|
||||
if !profilemanager.IsValidProfileFilenameStem(profilemanager.ID(id)) {
|
||||
return fmt.Errorf("invalid profile ID: %q", id)
|
||||
}
|
||||
|
||||
configPath, err := pm.getProfileConfigPath(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := os.Stat(configPath); os.IsNotExist(err) {
|
||||
return fmt.Errorf("profile %q does not exist", id)
|
||||
}
|
||||
|
||||
config, err := profilemanager.ReadConfig(configPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read profile config: %w", err)
|
||||
}
|
||||
|
||||
config.PrivateKey = ""
|
||||
config.SSHKey = ""
|
||||
|
||||
if err := profilemanager.WriteOutConfig(configPath, config); err != nil {
|
||||
return fmt.Errorf("failed to save config: %w", err)
|
||||
}
|
||||
|
||||
log.Infof("logged out from profile: %s", id)
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfigPath returns the config file path for a given profile ID.
|
||||
func (pm *ProfileManager) GetConfigPath(id string) (string, error) {
|
||||
return pm.getProfileConfigPath(id)
|
||||
}
|
||||
|
||||
// GetStateFilePath returns the state file path for a given profile ID.
|
||||
func (pm *ProfileManager) GetStateFilePath(id string) (string, error) {
|
||||
if id == "" || id == profilemanager.DefaultProfileName {
|
||||
return filepath.Join(pm.configDir, iosDefaultStateFilename), nil
|
||||
}
|
||||
|
||||
profilesDir := filepath.Join(pm.configDir, iosProfilesSubdir)
|
||||
return filepath.Join(profilesDir, id+".state.json"), nil
|
||||
}
|
||||
|
||||
// GetActiveConfigPath returns the config file path for the active profile.
|
||||
func (pm *ProfileManager) GetActiveConfigPath() (string, error) {
|
||||
activeProfile, err := pm.GetActiveProfile()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get active profile: %w", err)
|
||||
}
|
||||
return pm.GetConfigPath(activeProfile.ID)
|
||||
}
|
||||
|
||||
// GetActiveStateFilePath returns the state file path for the active profile.
|
||||
func (pm *ProfileManager) GetActiveStateFilePath() (string, error) {
|
||||
activeProfile, err := pm.GetActiveProfile()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get active profile: %w", err)
|
||||
}
|
||||
return pm.GetStateFilePath(activeProfile.ID)
|
||||
}
|
||||
|
||||
// getProfileConfigPath returns the config file path for a profile ID. The
|
||||
// default profile lives in the root configDir as netbird.cfg; everything else
|
||||
// lives under profiles/ as <id>.json.
|
||||
func (pm *ProfileManager) getProfileConfigPath(id string) (string, error) {
|
||||
if id == "" || id == profilemanager.DefaultProfileName {
|
||||
return filepath.Join(pm.configDir, iosDefaultConfigFilename), nil
|
||||
}
|
||||
|
||||
profilesDir := filepath.Join(pm.configDir, iosProfilesSubdir)
|
||||
return filepath.Join(profilesDir, id+".json"), nil
|
||||
}
|
||||
@@ -1916,117 +1916,6 @@ func TestDefaultAccountManager_MarkPeerConnected_PeerLoginExpiration(t *testing.
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_MarkPeerDisconnected_SchedulesInactivityExpiration(t *testing.T) {
|
||||
manager, _, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
accountID, err := manager.GetAccountIDByUserID(context.Background(), auth.UserAuth{UserId: userID})
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
require.NoError(t, err, "unable to generate WireGuard key")
|
||||
peerPubKey := key.PublicKey().String()
|
||||
|
||||
_, _, _, _, err = manager.AddPeer(context.Background(), "", "", userID, &nbpeer.Peer{
|
||||
Key: peerPubKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||
InactivityExpirationEnabled: true,
|
||||
}, false)
|
||||
require.NoError(t, err, "unable to add peer")
|
||||
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &types.Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
PeerInactivityExpiration: time.Hour,
|
||||
PeerInactivityExpirationEnabled: true,
|
||||
Extra: &types.ExtraSettings{},
|
||||
})
|
||||
require.NoError(t, err, "expecting to update account settings successfully but got error")
|
||||
|
||||
// Establish a session so the matching-token disconnect is actually applied.
|
||||
streamStartTime := time.Now().UTC()
|
||||
err = manager.MarkPeerConnected(context.Background(), peerPubKey, accountID, streamStartTime.UnixNano(), nil)
|
||||
require.NoError(t, err, "unable to mark peer connected")
|
||||
|
||||
// Install the mock only now, so the assertion observes the disconnect, not
|
||||
// the earlier connect.
|
||||
scheduled := make(chan struct{}, 1)
|
||||
manager.peerInactivityExpiry = &MockScheduler{
|
||||
CancelFunc: func(ctx context.Context, IDs []string) {},
|
||||
ScheduleFunc: func(ctx context.Context, in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
select {
|
||||
case scheduled <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
err = manager.MarkPeerDisconnected(context.Background(), peerPubKey, accountID, streamStartTime.UnixNano())
|
||||
require.NoError(t, err, "unable to mark peer disconnected")
|
||||
|
||||
select {
|
||||
case <-scheduled:
|
||||
// expected: disconnect re-armed the inactivity expiry timer
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("expected inactivity expiration to be rescheduled when an eligible peer disconnects")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_MarkPeerDisconnected_SkipsInactivityExpirationWhenDisabled(t *testing.T) {
|
||||
manager, _, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
accountID, err := manager.GetAccountIDByUserID(context.Background(), auth.UserAuth{UserId: userID})
|
||||
require.NoError(t, err, "unable to create an account")
|
||||
|
||||
key, err := wgtypes.GenerateKey()
|
||||
require.NoError(t, err, "unable to generate WireGuard key")
|
||||
peerPubKey := key.PublicKey().String()
|
||||
|
||||
_, _, _, _, err = manager.AddPeer(context.Background(), "", "", userID, &nbpeer.Peer{
|
||||
Key: peerPubKey,
|
||||
Meta: nbpeer.PeerSystemMeta{Hostname: "test-peer"},
|
||||
InactivityExpirationEnabled: true,
|
||||
}, false)
|
||||
require.NoError(t, err, "unable to add peer")
|
||||
|
||||
// Peer is eligible (SSO + inactivity enabled) but the account-level setting
|
||||
// stays disabled, so disconnect must not schedule anything.
|
||||
_, err = manager.UpdateAccountSettings(context.Background(), accountID, userID, &types.Settings{
|
||||
PeerLoginExpiration: time.Hour,
|
||||
PeerLoginExpirationEnabled: true,
|
||||
PeerInactivityExpiration: time.Hour,
|
||||
PeerInactivityExpirationEnabled: false,
|
||||
Extra: &types.ExtraSettings{},
|
||||
})
|
||||
require.NoError(t, err, "expecting to update account settings successfully but got error")
|
||||
|
||||
streamStartTime := time.Now().UTC()
|
||||
err = manager.MarkPeerConnected(context.Background(), peerPubKey, accountID, streamStartTime.UnixNano(), nil)
|
||||
require.NoError(t, err, "unable to mark peer connected")
|
||||
|
||||
scheduled := make(chan struct{}, 1)
|
||||
manager.peerInactivityExpiry = &MockScheduler{
|
||||
CancelFunc: func(ctx context.Context, IDs []string) {},
|
||||
ScheduleFunc: func(ctx context.Context, in time.Duration, ID string, job func() (nextRunIn time.Duration, reschedule bool)) {
|
||||
select {
|
||||
case scheduled <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
err = manager.MarkPeerDisconnected(context.Background(), peerPubKey, accountID, streamStartTime.UnixNano())
|
||||
require.NoError(t, err, "unable to mark peer disconnected")
|
||||
|
||||
select {
|
||||
case <-scheduled:
|
||||
t.Fatal("inactivity expiration must not be scheduled while the account-level setting is disabled")
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
// expected: nothing scheduled
|
||||
}
|
||||
}
|
||||
|
||||
func TestDefaultAccountManager_OnPeerDisconnected_LastSeenCheck(t *testing.T) {
|
||||
manager, _, err := createManager(t)
|
||||
require.NoError(t, err, "unable to create account manager")
|
||||
|
||||
@@ -41,7 +41,7 @@ func TestAffectedPeers_DependencyCoverageMatrix(t *testing.T) {
|
||||
_, err := s.manager.SavePolicy(ctx, s.accountID, userID, peerToResourcePolicyByGroup(s.sourceGroupID, s.resourceGroupID), true)
|
||||
require.NoError(t, err)
|
||||
return affectedpeers.Change{ChangedPeerIDs: []string{s.routerPeerID}},
|
||||
[]string{s.sourcePeerID, s.routerPeerID}, []string{s.unrelatedPeerID}
|
||||
[]string{s.sourcePeerID}, []string{s.unrelatedPeerID}
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -106,8 +106,12 @@ func TestAffectedPeers_DependencyCoverageMatrix(t *testing.T) {
|
||||
change, mustContain, mustExclude := r.build(t, s, ctx)
|
||||
affected := resolveAffected(t, s.manager.Store, s.accountID, change)
|
||||
|
||||
assert.ElementsMatch(t, affected, mustContain, "expected peer to be affected")
|
||||
assert.NotContains(t, affected, mustExclude, "peer must not be affected")
|
||||
for _, id := range mustContain {
|
||||
assert.Contains(t, affected, id, "expected peer to be affected")
|
||||
}
|
||||
for _, id := range mustExclude {
|
||||
assert.NotContains(t, affected, id, "peer must not be affected")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,54 +96,33 @@ func affectedGroupID(i int) string { return fmt.Sprintf("affected-grp-%d", i)
|
||||
func affectedGroupName(i int) string { return fmt.Sprintf("AffectedGroup%d", i) }
|
||||
|
||||
func TestCollectGroupChange_PolicyLinked(t *testing.T) {
|
||||
manager, s, accountID, peerIDs, groupIDs := setupAffectedPeersTest(t)
|
||||
manager, s, accountID, _, groupIDs := setupAffectedPeersTest(t)
|
||||
ctx := context.Background()
|
||||
|
||||
_, err := manager.SavePolicy(ctx, accountID, userID, &types.Policy{
|
||||
Enabled: true,
|
||||
Rules: []*types.PolicyRule{
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
SourceResource: types.Resource{ID: peerIDs[0], Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: peerIDs[1], Type: types.ResourceTypePeer},
|
||||
Bidirectional: true,
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
SourceResource: types.Resource{ID: peerIDs[2], Type: types.ResourceTypeHost},
|
||||
DestinationResource: types.Resource{ID: peerIDs[3], Type: types.ResourceTypeHost},
|
||||
Bidirectional: true,
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
SourceResource: types.Resource{ID: "", Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: "", Type: types.ResourceTypePeer},
|
||||
Bidirectional: true,
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
Bidirectional: true,
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
groups, directPeers := collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[0]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.ElementsMatch(t, directPeers, []string{peerIDs[1]})
|
||||
groups, _ := collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[0]})
|
||||
assert.Contains(t, groups, groupIDs[0])
|
||||
assert.Contains(t, groups, groupIDs[1])
|
||||
|
||||
groups, directPeers = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[1]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.ElementsMatch(t, directPeers, []string{peerIDs[0]})
|
||||
groups, _ = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[1]})
|
||||
assert.Contains(t, groups, groupIDs[0])
|
||||
assert.Contains(t, groups, groupIDs[1])
|
||||
|
||||
groups, directPeers = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[2]})
|
||||
groups, _ = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[2]})
|
||||
assert.Empty(t, groups)
|
||||
assert.Empty(t, directPeers)
|
||||
}
|
||||
|
||||
func TestCollectGroupChange_PolicyWithDirectPeerResource(t *testing.T) {
|
||||
@@ -154,44 +133,20 @@ func TestCollectGroupChange_PolicyWithDirectPeerResource(t *testing.T) {
|
||||
Enabled: true,
|
||||
Rules: []*types.PolicyRule{
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
SourceResource: types.Resource{ID: peerIDs[3], Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: peerIDs[4], Type: types.ResourceTypePeer},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
SourceResource: types.Resource{ID: peerIDs[1], Type: types.ResourceTypeHost},
|
||||
DestinationResource: types.Resource{ID: peerIDs[2], Type: types.ResourceTypeHost},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
{
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
SourceResource: types.Resource{ID: "", Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: "", Type: types.ResourceTypePeer},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
Enabled: true,
|
||||
Sources: []string{groupIDs[0]},
|
||||
SourceResource: types.Resource{ID: peerIDs[3], Type: types.ResourceTypePeer},
|
||||
Destinations: []string{groupIDs[1]},
|
||||
Action: types.PolicyTrafficActionAccept,
|
||||
},
|
||||
},
|
||||
}, true)
|
||||
require.NoError(t, err)
|
||||
|
||||
groups, directPeers := collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[0]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.ElementsMatch(t, directPeers, []string{peerIDs[4]})
|
||||
|
||||
groups, directPeers = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[1]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.ElementsMatch(t, directPeers, []string{peerIDs[3]})
|
||||
|
||||
groups, directPeers = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[2]})
|
||||
assert.Empty(t, groups)
|
||||
assert.Empty(t, directPeers)
|
||||
assert.Contains(t, groups, groupIDs[0])
|
||||
assert.Contains(t, groups, groupIDs[1])
|
||||
assert.Contains(t, directPeers, peerIDs[3])
|
||||
}
|
||||
|
||||
func TestCollectGroupChange_PolicyWithNonPeerResource_NoDirectPeers(t *testing.T) {
|
||||
@@ -213,7 +168,8 @@ func TestCollectGroupChange_PolicyWithNonPeerResource_NoDirectPeers(t *testing.T
|
||||
require.NoError(t, err)
|
||||
|
||||
groups, directPeers := collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[0]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.Contains(t, groups, groupIDs[0])
|
||||
assert.Contains(t, groups, groupIDs[1])
|
||||
assert.Empty(t, directPeers, "non-peer resources should not produce direct peer IDs")
|
||||
}
|
||||
|
||||
@@ -417,11 +373,17 @@ func TestCollectGroupChange_MultipleEntities(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
groups, directPeers := collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[0]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[0], groupIDs[1]})
|
||||
assert.Contains(t, groups, groupIDs[0])
|
||||
assert.Contains(t, groups, groupIDs[1])
|
||||
assert.NotContains(t, groups, groupIDs[2])
|
||||
assert.NotContains(t, groups, groupIDs[3])
|
||||
assert.Empty(t, directPeers)
|
||||
|
||||
groups, directPeers = collectGroupChangeAffectedGroups(ctx, s, accountID, []string{groupIDs[3]})
|
||||
assert.ElementsMatch(t, groups, []string{groupIDs[2], groupIDs[3]})
|
||||
assert.Contains(t, groups, groupIDs[2])
|
||||
assert.Contains(t, groups, groupIDs[3])
|
||||
assert.NotContains(t, groups, groupIDs[0])
|
||||
assert.NotContains(t, groups, groupIDs[1])
|
||||
assert.Empty(t, directPeers)
|
||||
}
|
||||
|
||||
@@ -512,7 +474,7 @@ func TestResolveAffectedPeers_PolicyThreeGroups(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
result := manager.resolveAffectedPeersForPeerChanges(ctx, s, accountID, []string{peerIDs[0]})
|
||||
assert.ElementsMatch(t, []string{peerIDs[0], peerIDs[2]}, result)
|
||||
assert.ElementsMatch(t, []string{peerIDs[0], peerIDs[1], peerIDs[2]}, result)
|
||||
}
|
||||
|
||||
func TestResolveAffectedPeers_RoutePeerGroups(t *testing.T) {
|
||||
@@ -735,7 +697,7 @@ func TestResolveAffectedPeers_MultipleChangedPeers(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
result := manager.resolveAffectedPeersForPeerChanges(ctx, s, accountID, []string{peerIDs[0], peerIDs[2]})
|
||||
assert.ElementsMatch(t, []string{peerIDs[0], peerIDs[2], peerIDs[1], peerIDs[3]}, result)
|
||||
assert.ElementsMatch(t, []string{peerIDs[0], peerIDs[1], peerIDs[2], peerIDs[3]}, result)
|
||||
}
|
||||
|
||||
func TestResolveAffectedPeers_SharedGroupAcrossPolicyAndRoute(t *testing.T) {
|
||||
|
||||
@@ -11,8 +11,6 @@ package affectedpeers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
log "github.com/sirupsen/logrus"
|
||||
|
||||
@@ -449,24 +447,15 @@ func (r *resolver) collectFromPostureChecks(postureCheckIDs []string) {
|
||||
|
||||
func (r *resolver) collectFromPolicies() {
|
||||
for _, policy := range r.policies() {
|
||||
// changed peer IDs have been mapped to changedGroupSet on resolver creation (see seedChangedGroupsFromPeers)
|
||||
// there's no change to the groupSet if the same policies have been changed directly
|
||||
peerIdsViaGroups, groupIdsViaGroups := getGroupsAndPeersFromPolicyViaGroups(policy, r.changedGroupSet)
|
||||
addAll(r.groupSet, groupIdsViaGroups)
|
||||
addAll(r.peerSet, peerIdsViaGroups)
|
||||
|
||||
peerIdsViaPeers, groupIdsViaPeers := getGroupsAndPeersFromPolicyViaPeers(policy, r.changedPeerSet)
|
||||
addAll(r.groupSet, groupIdsViaPeers)
|
||||
addAll(r.peerSet, peerIdsViaPeers)
|
||||
|
||||
hasGroupChanges := len(groupIdsViaPeers) > 0 || len(groupIdsViaGroups) > 0
|
||||
hasPeerChanges := len(peerIdsViaPeers) > 0 || len(peerIdsViaGroups) > 0
|
||||
if !hasGroupChanges && !hasPeerChanges {
|
||||
matchedByGroup := policyReferencesGroups(policy, r.changedGroupSet)
|
||||
matchedByPeer := len(r.changedPeerSet) > 0 && policyReferencesDirectPeers(policy, r.changedPeerSet)
|
||||
if !matchedByGroup && !matchedByPeer {
|
||||
continue
|
||||
}
|
||||
|
||||
log.WithContext(r.ctx).Tracef("collectFromPolicies: policy %s (%s) matched (byGroup=%t byPeer=%t) -> folding rule groups %v + direct peers",
|
||||
policy.ID, policy.Name, hasGroupChanges, hasPeerChanges, policy.RuleGroups())
|
||||
policy.ID, policy.Name, matchedByGroup, matchedByPeer, policy.RuleGroups())
|
||||
addAll(r.groupSet, policy.RuleGroups())
|
||||
collectPolicyDirectPeers(policy, r.peerSet)
|
||||
r.matchedPolicies = append(r.matchedPolicies, policy)
|
||||
}
|
||||
}
|
||||
@@ -745,60 +734,22 @@ func collectPolicySources(policy *types.Policy, groupSet, peerSet map[string]str
|
||||
}
|
||||
}
|
||||
|
||||
// returns group and peer IDs on the opposite side of the policy:
|
||||
// i.e. if a group is present in the policy rule sources, return destination group IDs and the destinationResource from the rule
|
||||
// and vice-versa
|
||||
func getGroupsAndPeersFromPolicyViaGroups(policy *types.Policy, groupSet map[string]struct{}) ([]string, []string) {
|
||||
var groupIds, peerIds []string
|
||||
if len(groupSet) == 0 {
|
||||
return peerIds, groupIds
|
||||
}
|
||||
func policyReferencesGroups(policy *types.Policy, groupSet map[string]struct{}) bool {
|
||||
for _, rule := range policy.Rules {
|
||||
if matchedIds, ok := allInSet(rule.Sources, groupSet); ok {
|
||||
groupIds = append(groupIds, matchedIds...)
|
||||
groupIds = append(groupIds, rule.Destinations...)
|
||||
if rule.DestinationResource.Type == types.ResourceTypePeer && rule.DestinationResource.ID != "" {
|
||||
peerIds = append(peerIds, rule.DestinationResource.ID)
|
||||
}
|
||||
}
|
||||
if matchedIds, ok := allInSet(rule.Destinations, groupSet); ok {
|
||||
groupIds = append(groupIds, matchedIds...)
|
||||
groupIds = append(groupIds, rule.Sources...)
|
||||
if rule.SourceResource.Type == types.ResourceTypePeer && rule.SourceResource.ID != "" {
|
||||
peerIds = append(peerIds, rule.SourceResource.ID)
|
||||
}
|
||||
if anyInSet(rule.Sources, groupSet) || anyInSet(rule.Destinations, groupSet) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return peerIds, groupIds
|
||||
return false
|
||||
}
|
||||
|
||||
// returns group and peer IDs on the opposite side of the policy:
|
||||
// i.e. if a peer is present in the policy rule sourceResources, return destination group IDs and the destinationResource from the rule
|
||||
// and vice-versa
|
||||
func getGroupsAndPeersFromPolicyViaPeers(policy *types.Policy, changedSet map[string]struct{}) ([]string, []string) {
|
||||
peerIds := make(map[string]struct{})
|
||||
var groupIds []string
|
||||
if len(changedSet) == 0 {
|
||||
return []string{}, groupIds
|
||||
}
|
||||
func policyReferencesDirectPeers(policy *types.Policy, changedSet map[string]struct{}) bool {
|
||||
for _, rule := range policy.Rules {
|
||||
if isDirectPeerInSet(rule.SourceResource, changedSet) {
|
||||
groupIds = append(groupIds, rule.Destinations...)
|
||||
peerIds[rule.SourceResource.ID] = struct{}{}
|
||||
if rule.DestinationResource.Type == types.ResourceTypePeer && rule.DestinationResource.ID != "" {
|
||||
peerIds[rule.DestinationResource.ID] = struct{}{}
|
||||
}
|
||||
}
|
||||
// it's possible that the changeSet contains peer ids of both source and destination resources
|
||||
if isDirectPeerInSet(rule.DestinationResource, changedSet) {
|
||||
groupIds = append(groupIds, rule.Sources...)
|
||||
peerIds[rule.DestinationResource.ID] = struct{}{}
|
||||
if rule.SourceResource.Type == types.ResourceTypePeer && rule.SourceResource.ID != "" {
|
||||
peerIds[rule.SourceResource.ID] = struct{}{}
|
||||
}
|
||||
if isDirectPeerInSet(rule.SourceResource, changedSet) || isDirectPeerInSet(rule.DestinationResource, changedSet) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return slices.Collect(maps.Keys(peerIds)), groupIds
|
||||
return false
|
||||
}
|
||||
|
||||
func policyReferencesPostureChecks(policy *types.Policy, ids map[string]struct{}) bool {
|
||||
@@ -844,16 +795,6 @@ func anyInSet(ids []string, set map[string]struct{}) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func allInSet(ids []string, set map[string]struct{}) ([]string, bool) {
|
||||
var matchedIds []string
|
||||
for _, id := range ids {
|
||||
if _, ok := set[id]; ok {
|
||||
matchedIds = append(matchedIds, id)
|
||||
}
|
||||
}
|
||||
return matchedIds, len(matchedIds) > 0
|
||||
}
|
||||
|
||||
func isInSet(id string, set map[string]struct{}) bool {
|
||||
_, ok := set[id]
|
||||
return ok
|
||||
|
||||
@@ -80,189 +80,24 @@ func TestChangeIsEmpty(t *testing.T) {
|
||||
assert.False(t, Change{PostureCheckIDs: []string{"pc"}}.isEmpty())
|
||||
}
|
||||
|
||||
func TestGroupsFromPolicyDirectionally(t *testing.T) {
|
||||
policy := &types.Policy{Rules: []*types.PolicyRule{
|
||||
{Sources: []string{"g1", "g2"}, Destinations: []string{"g3"}},
|
||||
{Sources: []string{"g4"}, Destinations: []string{"g5", "g6"}},
|
||||
{Sources: []string{"g7"}, Destinations: []string{"g8"},
|
||||
SourceResource: types.Resource{ID: "r7", Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: "r8", Type: types.ResourceTypePeer}},
|
||||
{Sources: []string{"g9"}, Destinations: []string{"g10"},
|
||||
SourceResource: types.Resource{ID: "", Type: types.ResourceTypePeer},
|
||||
DestinationResource: types.Resource{ID: "", Type: types.ResourceTypePeer}},
|
||||
{Sources: []string{"g11"}, Destinations: []string{"g12"},
|
||||
SourceResource: types.Resource{ID: "r11", Type: types.ResourceTypeHost},
|
||||
DestinationResource: types.Resource{ID: "r12", Type: types.ResourceTypeHost}},
|
||||
}}
|
||||
func TestPolicyReferencesGroups(t *testing.T) {
|
||||
policy := &types.Policy{Rules: []*types.PolicyRule{{Sources: []string{"g1", "g2"}, Destinations: []string{"g3"}}}}
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
inGroups map[string]struct{}
|
||||
expectedPeerIds []string
|
||||
expectedGroupIds []string
|
||||
}{
|
||||
{
|
||||
name: "match sources",
|
||||
inGroups: map[string]struct{}{"g1": {}, "g4": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g1", "g4", "g3", "g5", "g6"},
|
||||
},
|
||||
{
|
||||
name: "match destinations",
|
||||
inGroups: map[string]struct{}{"g3": {}, "g6": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g1", "g2", "g4", "g3", "g6"},
|
||||
},
|
||||
{
|
||||
name: "should return destinations and destination resource",
|
||||
inGroups: map[string]struct{}{"g7": {}},
|
||||
expectedPeerIds: []string{"r8"},
|
||||
expectedGroupIds: []string{"g7", "g8"},
|
||||
},
|
||||
{
|
||||
name: "should return sources and source resource",
|
||||
inGroups: map[string]struct{}{"g8": {}},
|
||||
expectedPeerIds: []string{"r7"},
|
||||
expectedGroupIds: []string{"g7", "g8"},
|
||||
},
|
||||
{
|
||||
name: "should not return source resource (empty id)",
|
||||
inGroups: map[string]struct{}{"g10": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g9", "g10"},
|
||||
},
|
||||
{
|
||||
name: "should not return destination resource (empty id)",
|
||||
inGroups: map[string]struct{}{"g9": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g9", "g10"},
|
||||
},
|
||||
{
|
||||
name: "should not return source resource (non-peer type)",
|
||||
inGroups: map[string]struct{}{"g12": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g11", "g12"},
|
||||
},
|
||||
{
|
||||
name: "should not return destination resource (non-peer type)",
|
||||
inGroups: map[string]struct{}{"g12": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{"g11", "g12"},
|
||||
},
|
||||
{
|
||||
name: "non-existing group",
|
||||
inGroups: map[string]struct{}{"g33": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{},
|
||||
},
|
||||
{
|
||||
name: "empty groupset",
|
||||
inGroups: map[string]struct{}{},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
peerIds, groupIds := getGroupsAndPeersFromPolicyViaGroups(policy, tt.inGroups)
|
||||
assert.ElementsMatch(t, peerIds, tt.expectedPeerIds)
|
||||
assert.ElementsMatch(t, groupIds, tt.expectedGroupIds)
|
||||
})
|
||||
}
|
||||
assert.True(t, policyReferencesGroups(policy, map[string]struct{}{"g1": {}}))
|
||||
assert.True(t, policyReferencesGroups(policy, map[string]struct{}{"g3": {}}))
|
||||
assert.False(t, policyReferencesGroups(policy, map[string]struct{}{"g4": {}}))
|
||||
assert.False(t, policyReferencesGroups(policy, map[string]struct{}{}))
|
||||
}
|
||||
|
||||
func TestPolicyReferencesDirectPeers(t *testing.T) {
|
||||
policy := &types.Policy{Rules: []*types.PolicyRule{
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p1"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypePeer, ID: "r1"},
|
||||
Sources: []string{"sg1"},
|
||||
Destinations: []string{"dg1"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p2"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypePeer, ID: "r2"},
|
||||
Sources: []string{"sg2"},
|
||||
Destinations: []string{"dg2"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p3"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypeHost, ID: "r3"},
|
||||
Sources: []string{"sg3"},
|
||||
Destinations: []string{"dg3"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypeHost, ID: "p4"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypePeer, ID: "r4"},
|
||||
Sources: []string{"sg4"},
|
||||
Destinations: []string{"dg4"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypeHost, ID: "p5"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypePeer, ID: "r5"},
|
||||
Sources: []string{"sg5"},
|
||||
Destinations: []string{"dg5"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p6"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypeHost, ID: "r6"},
|
||||
Sources: []string{"sg6"},
|
||||
Destinations: []string{"dg6"},
|
||||
},
|
||||
{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p7"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypePeer, ID: "r7"},
|
||||
Sources: []string{"sg7"},
|
||||
Destinations: []string{"dg7"},
|
||||
},
|
||||
}}
|
||||
policy := &types.Policy{Rules: []*types.PolicyRule{{
|
||||
SourceResource: types.Resource{Type: types.ResourceTypePeer, ID: "p1"},
|
||||
DestinationResource: types.Resource{Type: types.ResourceTypeHost, ID: "r1"},
|
||||
}}}
|
||||
|
||||
var tests = []struct {
|
||||
name string
|
||||
changedPeerIds map[string]struct{}
|
||||
expectedPeerIds []string
|
||||
expectedGroupIds []string
|
||||
}{
|
||||
{
|
||||
name: "match sources",
|
||||
changedPeerIds: map[string]struct{}{"p1": {}, "p2": {}},
|
||||
expectedPeerIds: []string{"p1", "p2", "r1", "r2"},
|
||||
expectedGroupIds: []string{"dg1", "dg2"},
|
||||
},
|
||||
{
|
||||
name: "match destinations",
|
||||
changedPeerIds: map[string]struct{}{"r1": {}, "r2": {}},
|
||||
expectedPeerIds: []string{"r1", "r2", "p1", "p2"},
|
||||
expectedGroupIds: []string{"sg1", "sg2"},
|
||||
},
|
||||
{
|
||||
name: "wrong opposing peer types, only changed peer ids and groups on the opposing end of the rule",
|
||||
changedPeerIds: map[string]struct{}{"p3": {}, "r4": {}},
|
||||
expectedPeerIds: []string{"p3", "r4"},
|
||||
expectedGroupIds: []string{"dg3", "sg4"},
|
||||
},
|
||||
{
|
||||
name: "wrong peer type, no matching peer ids",
|
||||
changedPeerIds: map[string]struct{}{"p5": {}, "r6": {}},
|
||||
expectedPeerIds: []string{},
|
||||
expectedGroupIds: []string{},
|
||||
},
|
||||
{
|
||||
name: "changed peers on both sides of the policy",
|
||||
changedPeerIds: map[string]struct{}{"p7": {}, "r7": {}},
|
||||
expectedPeerIds: []string{"p7", "r7"},
|
||||
expectedGroupIds: []string{"sg7", "dg7"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
peerIds, groupIds := getGroupsAndPeersFromPolicyViaPeers(policy, tt.changedPeerIds)
|
||||
assert.ElementsMatch(t, peerIds, tt.expectedPeerIds)
|
||||
assert.ElementsMatch(t, groupIds, tt.expectedGroupIds)
|
||||
})
|
||||
}
|
||||
assert.True(t, policyReferencesDirectPeers(policy, map[string]struct{}{"p1": {}}))
|
||||
assert.False(t, policyReferencesDirectPeers(policy, map[string]struct{}{"r1": {}}))
|
||||
assert.False(t, policyReferencesDirectPeers(policy, map[string]struct{}{"p2": {}}))
|
||||
}
|
||||
|
||||
func TestPolicyReferencesPostureChecks(t *testing.T) {
|
||||
|
||||
@@ -188,15 +188,6 @@ func (am *DefaultAccountManager) MarkPeerDisconnected(ctx context.Context, peerP
|
||||
}
|
||||
}
|
||||
|
||||
if peer.AddedWithSSOLogin() && peer.InactivityExpirationEnabled {
|
||||
settings, err := am.Store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
|
||||
if err != nil {
|
||||
log.WithContext(ctx).Warnf("failed getting account settings to schedule inactivity expiration for peer %s: %v", peer.ID, err)
|
||||
} else if settings.PeerInactivityExpirationEnabled {
|
||||
am.checkAndSchedulePeerInactivityExpiration(ctx, accountID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user