mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-25 02:04:38 +00:00
369 lines
12 KiB
Go
369 lines
12 KiB
Go
![]() |
// Copyright (c) Tailscale Inc & AUTHORS
|
||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
||
|
package rsop
|
||
|
|
||
|
import (
|
||
|
"slices"
|
||
|
"sort"
|
||
|
"testing"
|
||
|
|
||
|
"tailscale.com/util/syspolicy/setting"
|
||
|
|
||
|
"tailscale.com/util/syspolicy/source"
|
||
|
)
|
||
|
|
||
|
func TestRegisterSourceAndGetResultantPolicy(t *testing.T) {
|
||
|
type sourceConfig struct {
|
||
|
name string
|
||
|
scope setting.PolicyScope
|
||
|
settingKey setting.Key
|
||
|
settingValue string
|
||
|
wantEffective bool
|
||
|
}
|
||
|
tests := []struct {
|
||
|
name string
|
||
|
scope setting.PolicyScope
|
||
|
initialSources []sourceConfig
|
||
|
additionalSources []sourceConfig
|
||
|
wantSnapshot *setting.Snapshot
|
||
|
}{
|
||
|
{
|
||
|
name: "DevicePolicy/NoSources",
|
||
|
scope: setting.DeviceScope,
|
||
|
wantSnapshot: setting.NewSnapshot(nil, setting.DeviceScope),
|
||
|
},
|
||
|
{
|
||
|
name: "UserScope/NoSources",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
wantSnapshot: setting.NewSnapshot(nil, setting.CurrentUserScope),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/OneInitialSource",
|
||
|
scope: setting.DeviceScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceA",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueA",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||
|
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/OneAdditionalSource",
|
||
|
scope: setting.DeviceScope,
|
||
|
additionalSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceA",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueA",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||
|
}, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/ManyInitialSources/NoConflicts",
|
||
|
scope: setting.DeviceScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceA",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueA",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceB",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "TestValueB",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceC",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyC",
|
||
|
settingValue: "TestValueC",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("TestValueA", nil, setting.NewNamedOrigin("TestSourceA", setting.DeviceScope)),
|
||
|
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||
|
"TestKeyC": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
|
||
|
}, setting.DeviceScope),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/ManyInitialSources/Conflicts",
|
||
|
scope: setting.DeviceScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceA",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueA",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceB",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "TestValueB",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceC",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueC",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("TestValueC", nil, setting.NewNamedOrigin("TestSourceC", setting.DeviceScope)),
|
||
|
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||
|
}, setting.DeviceScope),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/MixedSources/Conflicts",
|
||
|
scope: setting.DeviceScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceA",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueA",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceB",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "TestValueB",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceC",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueC",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
additionalSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceD",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueD",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceE",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyC",
|
||
|
settingValue: "TestValueE",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceF",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "TestValueF",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("TestValueF", nil, setting.NewNamedOrigin("TestSourceF", setting.DeviceScope)),
|
||
|
"TestKeyB": setting.RawItemWith("TestValueB", nil, setting.NewNamedOrigin("TestSourceB", setting.DeviceScope)),
|
||
|
"TestKeyC": setting.RawItemWith("TestValueE", nil, setting.NewNamedOrigin("TestSourceE", setting.DeviceScope)),
|
||
|
}, setting.DeviceScope),
|
||
|
},
|
||
|
{
|
||
|
name: "UserScope/Init-DeviceSource",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceDevice",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "DeviceValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
}, setting.CurrentUserScope, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
},
|
||
|
{
|
||
|
name: "UserScope/Init-DeviceSource/Add-UserSource",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceDevice",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "DeviceValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
additionalSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceUser",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "UserValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
"TestKeyB": setting.RawItemWith("UserValue", nil, setting.NewNamedOrigin("TestSourceUser", setting.CurrentUserScope)),
|
||
|
}, setting.CurrentUserScope),
|
||
|
},
|
||
|
{
|
||
|
name: "UserScope/Init-DeviceSource/Add-UserSource-and-ProfileSource",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceDevice",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "DeviceValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
additionalSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceProfile",
|
||
|
scope: setting.CurrentProfileScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "ProfileValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
{
|
||
|
name: "TestSourceUser",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
settingKey: "TestKeyB",
|
||
|
settingValue: "UserValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
"TestKeyB": setting.RawItemWith("ProfileValue", nil, setting.NewNamedOrigin("TestSourceProfile", setting.CurrentProfileScope)),
|
||
|
}, setting.CurrentUserScope),
|
||
|
},
|
||
|
{
|
||
|
name: "DevicePolicy/User-Source-does-not-apply",
|
||
|
scope: setting.DeviceScope,
|
||
|
initialSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceDevice",
|
||
|
scope: setting.DeviceScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "DeviceValue",
|
||
|
wantEffective: true,
|
||
|
},
|
||
|
},
|
||
|
additionalSources: []sourceConfig{
|
||
|
{
|
||
|
name: "TestSourceUser",
|
||
|
scope: setting.CurrentUserScope,
|
||
|
settingKey: "TestKeyA",
|
||
|
settingValue: "UserValue",
|
||
|
wantEffective: false, // Registering a user source should have no impact on the device policy.
|
||
|
},
|
||
|
},
|
||
|
wantSnapshot: setting.NewSnapshot(map[setting.Key]setting.RawItem{
|
||
|
"TestKeyA": setting.RawItemWith("DeviceValue", nil, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
}, setting.NewNamedOrigin("TestSourceDevice", setting.DeviceScope)),
|
||
|
},
|
||
|
}
|
||
|
for _, tt := range tests {
|
||
|
t.Run(tt.name, func(t *testing.T) {
|
||
|
// Register all settings that we use in this test.
|
||
|
var definitions []*setting.Definition
|
||
|
for _, source := range slices.Concat(tt.initialSources, tt.additionalSources) {
|
||
|
definitions = append(definitions, setting.NewDefinition(source.settingKey, tt.scope.Kind(), setting.StringValue))
|
||
|
}
|
||
|
if err := setting.SetDefinitionsForTest(t, definitions...); err != nil {
|
||
|
t.Fatalf("SetDefinitionsForTest failed: %v", err)
|
||
|
}
|
||
|
|
||
|
// Add the initial policy sources.
|
||
|
var wantSources []*source.Source
|
||
|
for _, s := range tt.initialSources {
|
||
|
store := source.NewTestStoreOf(t, source.TestSettingOf(s.settingKey, s.settingValue))
|
||
|
source := source.NewSource(s.name, s.scope, store)
|
||
|
if err := registerSource(source); err != nil {
|
||
|
t.Fatalf("failed to register policy source: %v", source)
|
||
|
}
|
||
|
if s.wantEffective {
|
||
|
wantSources = append(wantSources, source)
|
||
|
}
|
||
|
t.Cleanup(func() { unregisterSource(source) })
|
||
|
}
|
||
|
|
||
|
// Retrieve the resultant policy.
|
||
|
policy, err := resultantPolicyForTest(t, tt.scope)
|
||
|
if err != nil {
|
||
|
t.Fatalf("failed to get resultant policy for %v", tt.scope)
|
||
|
}
|
||
|
|
||
|
// Add additional setting sources one by one, and check the policy settings at each step.
|
||
|
for _, s := range tt.additionalSources {
|
||
|
store := source.NewTestStoreOf(t, source.TestSettingOf(s.settingKey, s.settingValue))
|
||
|
source := source.NewSource(s.name, s.scope, store)
|
||
|
if err := registerSource(source); err != nil {
|
||
|
t.Fatalf("failed to register additional policy source: %v", source)
|
||
|
}
|
||
|
if s.wantEffective {
|
||
|
wantSources = append(wantSources, source)
|
||
|
}
|
||
|
t.Cleanup(func() { unregisterSource(source) })
|
||
|
}
|
||
|
|
||
|
sort.SliceStable(wantSources, func(i, j int) bool {
|
||
|
return wantSources[i].Compare(wantSources[j]) < 0
|
||
|
})
|
||
|
gotSources := make([]*source.Source, len(policy.sources))
|
||
|
for i, s := range policy.sources {
|
||
|
gotSources[i] = s.Source
|
||
|
}
|
||
|
if !slices.Equal(gotSources, wantSources) {
|
||
|
t.Errorf("Sources: got %v; want %v", gotSources, wantSources)
|
||
|
}
|
||
|
|
||
|
// Verify the final resultant settings snapshots.
|
||
|
if got := policy.Get(); !got.Equal(tt.wantSnapshot) {
|
||
|
t.Errorf("Snapshot: got %v; want %v", got, tt.wantSnapshot)
|
||
|
}
|
||
|
})
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// resultantPolicyForTest is like [resultantPolicyFor], but it deletes the policy
|
||
|
// when tb and all its subtests complete.
|
||
|
func resultantPolicyForTest(tb testing.TB, target setting.PolicyScope) (*Policy, error) {
|
||
|
policy, err := PolicyFor(target)
|
||
|
if err != nil {
|
||
|
return nil, err
|
||
|
}
|
||
|
tb.Cleanup(func() {
|
||
|
policy.Close()
|
||
|
<-policy.Done()
|
||
|
deletePolicy(policy)
|
||
|
})
|
||
|
return policy, nil
|
||
|
}
|