mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-25 19:15:34 +00:00
67df9abdc6
Package setting contains types for defining and representing policy settings. It facilitates the registration of setting definitions using Register and RegisterDefinition, and the retrieval of registered setting definitions via Definitions and DefinitionOf. This package is intended for use primarily within the syspolicy package hierarchy, and added in a preparation for the next PRs. Updates #12687 Signed-off-by: Nick Khyl <nickk@tailscale.com>
345 lines
11 KiB
Go
345 lines
11 KiB
Go
// Copyright (c) Tailscale Inc & AUTHORS
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package setting
|
|
|
|
import (
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"tailscale.com/types/lazy"
|
|
"tailscale.com/types/ptr"
|
|
"tailscale.com/util/syspolicy/internal"
|
|
)
|
|
|
|
func TestSettingDefinition(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
setting *Definition
|
|
osOverride string
|
|
wantKey Key
|
|
wantScope Scope
|
|
wantType Type
|
|
wantIsSupported bool
|
|
wantSupportedPlatforms PlatformList
|
|
wantString string
|
|
}{
|
|
{
|
|
name: "Nil",
|
|
setting: nil,
|
|
wantKey: "",
|
|
wantScope: 0,
|
|
wantType: InvalidValue,
|
|
wantIsSupported: false,
|
|
wantString: "(nil)",
|
|
},
|
|
{
|
|
name: "Device/Invalid",
|
|
setting: NewDefinition("TestDevicePolicySetting", DeviceSetting, InvalidValue),
|
|
wantKey: "TestDevicePolicySetting",
|
|
wantScope: DeviceSetting,
|
|
wantType: InvalidValue,
|
|
wantIsSupported: true,
|
|
wantString: `Device("TestDevicePolicySetting", Invalid)`,
|
|
},
|
|
{
|
|
name: "Device/Integer",
|
|
setting: NewDefinition("TestDevicePolicySetting", DeviceSetting, IntegerValue),
|
|
wantKey: "TestDevicePolicySetting",
|
|
wantScope: DeviceSetting,
|
|
wantType: IntegerValue,
|
|
wantIsSupported: true,
|
|
wantString: `Device("TestDevicePolicySetting", Integer)`,
|
|
},
|
|
{
|
|
name: "Profile/String",
|
|
setting: NewDefinition("TestProfilePolicySetting", ProfileSetting, StringValue),
|
|
wantKey: "TestProfilePolicySetting",
|
|
wantScope: ProfileSetting,
|
|
wantType: StringValue,
|
|
wantIsSupported: true,
|
|
wantString: `Profile("TestProfilePolicySetting", String)`,
|
|
},
|
|
{
|
|
name: "Device/StringList",
|
|
setting: NewDefinition("AllowedSuggestedExitNodes", DeviceSetting, StringListValue),
|
|
wantKey: "AllowedSuggestedExitNodes",
|
|
wantScope: DeviceSetting,
|
|
wantType: StringListValue,
|
|
wantIsSupported: true,
|
|
wantString: `Device("AllowedSuggestedExitNodes", StringList)`,
|
|
},
|
|
{
|
|
name: "Device/PreferenceOption",
|
|
setting: NewDefinition("AdvertiseExitNode", DeviceSetting, PreferenceOptionValue),
|
|
wantKey: "AdvertiseExitNode",
|
|
wantScope: DeviceSetting,
|
|
wantType: PreferenceOptionValue,
|
|
wantIsSupported: true,
|
|
wantString: `Device("AdvertiseExitNode", PreferenceOption)`,
|
|
},
|
|
{
|
|
name: "User/Boolean",
|
|
setting: NewDefinition("TestUserPolicySetting", UserSetting, BooleanValue),
|
|
wantKey: "TestUserPolicySetting",
|
|
wantScope: UserSetting,
|
|
wantType: BooleanValue,
|
|
wantIsSupported: true,
|
|
wantString: `User("TestUserPolicySetting", Boolean)`,
|
|
},
|
|
{
|
|
name: "User/Visibility",
|
|
setting: NewDefinition("AdminConsole", UserSetting, VisibilityValue),
|
|
wantKey: "AdminConsole",
|
|
wantScope: UserSetting,
|
|
wantType: VisibilityValue,
|
|
wantIsSupported: true,
|
|
wantString: `User("AdminConsole", Visibility)`,
|
|
},
|
|
{
|
|
name: "User/Duration",
|
|
setting: NewDefinition("KeyExpirationNotice", UserSetting, DurationValue),
|
|
wantKey: "KeyExpirationNotice",
|
|
wantScope: UserSetting,
|
|
wantType: DurationValue,
|
|
wantIsSupported: true,
|
|
wantString: `User("KeyExpirationNotice", Duration)`,
|
|
},
|
|
{
|
|
name: "SupportedSetting",
|
|
setting: NewDefinition("DesktopPolicySetting", DeviceSetting, StringValue, "macos", "windows"),
|
|
osOverride: "windows",
|
|
wantKey: "DesktopPolicySetting",
|
|
wantScope: DeviceSetting,
|
|
wantType: StringValue,
|
|
wantIsSupported: true,
|
|
wantSupportedPlatforms: PlatformList{"macos", "windows"},
|
|
wantString: `Device("DesktopPolicySetting", String)`,
|
|
},
|
|
{
|
|
name: "UnsupportedSetting",
|
|
setting: NewDefinition("AndroidPolicySetting", DeviceSetting, StringValue, "android"),
|
|
osOverride: "macos",
|
|
wantKey: "AndroidPolicySetting",
|
|
wantScope: DeviceSetting,
|
|
wantType: StringValue,
|
|
wantIsSupported: false,
|
|
wantSupportedPlatforms: PlatformList{"android"},
|
|
wantString: `Device("AndroidPolicySetting", String)`,
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
if tt.osOverride != "" {
|
|
internal.OSForTesting.SetForTest(t, tt.osOverride, nil)
|
|
}
|
|
if !tt.setting.Equal(tt.setting) {
|
|
t.Errorf("the setting should be equal to itself")
|
|
}
|
|
if tt.setting != nil && !tt.setting.Equal(ptr.To(*tt.setting)) {
|
|
t.Errorf("the setting should be equal to its shallow copy")
|
|
}
|
|
if gotKey := tt.setting.Key(); gotKey != tt.wantKey {
|
|
t.Errorf("Key: got %q, want %q", gotKey, tt.wantKey)
|
|
}
|
|
if gotScope := tt.setting.Scope(); gotScope != tt.wantScope {
|
|
t.Errorf("Scope: got %v, want %v", gotScope, tt.wantScope)
|
|
}
|
|
if gotType := tt.setting.Type(); gotType != tt.wantType {
|
|
t.Errorf("Type: got %v, want %v", gotType, tt.wantType)
|
|
}
|
|
if gotIsSupported := tt.setting.IsSupported(); gotIsSupported != tt.wantIsSupported {
|
|
t.Errorf("IsSupported: got %v, want %v", gotIsSupported, tt.wantIsSupported)
|
|
}
|
|
if gotSupportedPlatforms := tt.setting.SupportedPlatforms(); !slices.Equal(gotSupportedPlatforms, tt.wantSupportedPlatforms) {
|
|
t.Errorf("SupportedPlatforms: got %v, want %v", gotSupportedPlatforms, tt.wantSupportedPlatforms)
|
|
}
|
|
if gotString := tt.setting.String(); gotString != tt.wantString {
|
|
t.Errorf("String: got %v, want %v", gotString, tt.wantString)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegisterSettingDefinition(t *testing.T) {
|
|
const testPolicySettingKey Key = "TestPolicySetting"
|
|
tests := []struct {
|
|
name string
|
|
key Key
|
|
wantEq *Definition
|
|
wantErr error
|
|
}{
|
|
{
|
|
name: "GetRegistered",
|
|
key: "TestPolicySetting",
|
|
wantEq: NewDefinition(testPolicySettingKey, DeviceSetting, StringValue),
|
|
},
|
|
{
|
|
name: "GetNonRegistered",
|
|
key: "OtherPolicySetting",
|
|
wantEq: nil,
|
|
wantErr: ErrNoSuchKey,
|
|
},
|
|
}
|
|
|
|
resetSettingDefinitions(t)
|
|
Register(testPolicySettingKey, DeviceSetting, StringValue)
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, gotErr := DefinitionOf(tt.key)
|
|
if gotErr != tt.wantErr {
|
|
t.Errorf("gotErr %v, wantErr %v", gotErr, tt.wantErr)
|
|
}
|
|
if !got.Equal(tt.wantEq) {
|
|
t.Errorf("got %v, want %v", got, tt.wantEq)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRegisterAfterUsePanics(t *testing.T) {
|
|
resetSettingDefinitions(t)
|
|
|
|
Register("TestPolicySetting", DeviceSetting, StringValue)
|
|
DefinitionOf("TestPolicySetting")
|
|
|
|
func() {
|
|
defer func() {
|
|
if gotPanic, wantPanic := recover(), "policy definitions are already in use"; gotPanic != wantPanic {
|
|
t.Errorf("gotPanic: %q, wantPanic: %q", gotPanic, wantPanic)
|
|
}
|
|
}()
|
|
|
|
Register("TestPolicySetting", DeviceSetting, StringValue)
|
|
}()
|
|
}
|
|
|
|
func TestRegisterDuplicateSettings(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
name string
|
|
settings []*Definition
|
|
wantEq *Definition
|
|
wantErrStr string
|
|
}{
|
|
{
|
|
name: "NoConflict/Exact",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
|
|
},
|
|
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
|
|
},
|
|
{
|
|
name: "NoConflict/MergeOS-First",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "android", "macos"),
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
|
|
},
|
|
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
|
|
},
|
|
{
|
|
name: "NoConflict/MergeOS-Second",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "android", "macos"),
|
|
},
|
|
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue), // all platforms
|
|
},
|
|
{
|
|
name: "NoConflict/MergeOS-Both",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "macos"),
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "windows"),
|
|
},
|
|
wantEq: NewDefinition("TestPolicySetting", DeviceSetting, StringValue, "macos", "windows"),
|
|
},
|
|
{
|
|
name: "Conflict/Scope",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", DeviceSetting, StringValue),
|
|
NewDefinition("TestPolicySetting", UserSetting, StringValue),
|
|
},
|
|
wantEq: nil,
|
|
wantErrStr: `duplicate policy definition: "TestPolicySetting"`,
|
|
},
|
|
{
|
|
name: "Conflict/Type",
|
|
settings: []*Definition{
|
|
NewDefinition("TestPolicySetting", UserSetting, StringValue),
|
|
NewDefinition("TestPolicySetting", UserSetting, IntegerValue),
|
|
},
|
|
wantEq: nil,
|
|
wantErrStr: `duplicate policy definition: "TestPolicySetting"`,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
resetSettingDefinitions(t)
|
|
for _, s := range tt.settings {
|
|
Register(s.Key(), s.Scope(), s.Type(), s.SupportedPlatforms()...)
|
|
}
|
|
got, err := DefinitionOf("TestPolicySetting")
|
|
var gotErrStr string
|
|
if err != nil {
|
|
gotErrStr = err.Error()
|
|
}
|
|
if gotErrStr != tt.wantErrStr {
|
|
t.Fatalf("ErrStr: got %q, want %q", gotErrStr, tt.wantErrStr)
|
|
}
|
|
if !got.Equal(tt.wantEq) {
|
|
t.Errorf("Definition got %v, want %v", got, tt.wantEq)
|
|
}
|
|
if !slices.Equal(got.SupportedPlatforms(), tt.wantEq.SupportedPlatforms()) {
|
|
t.Errorf("SupportedPlatforms got %v, want %v", got.SupportedPlatforms(), tt.wantEq.SupportedPlatforms())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestListSettingDefinitions(t *testing.T) {
|
|
definitions := []*Definition{
|
|
NewDefinition("TestDevicePolicySetting", DeviceSetting, IntegerValue),
|
|
NewDefinition("TestProfilePolicySetting", ProfileSetting, StringValue),
|
|
NewDefinition("TestUserPolicySetting", UserSetting, BooleanValue),
|
|
NewDefinition("TestStringListPolicySetting", DeviceSetting, StringListValue),
|
|
}
|
|
if err := SetDefinitionsForTest(t, definitions...); err != nil {
|
|
t.Fatalf("SetDefinitionsForTest failed: %v", err)
|
|
}
|
|
|
|
cmp := func(l, r *Definition) int {
|
|
return strings.Compare(string(l.Key()), string(r.Key()))
|
|
}
|
|
want := append([]*Definition{}, definitions...)
|
|
slices.SortFunc(want, cmp)
|
|
|
|
got, err := Definitions()
|
|
if err != nil {
|
|
t.Fatalf("Definitions failed: %v", err)
|
|
}
|
|
slices.SortFunc(got, cmp)
|
|
|
|
if !slices.Equal(got, want) {
|
|
t.Errorf("got %v, want %v", got, want)
|
|
}
|
|
}
|
|
|
|
func resetSettingDefinitions(t *testing.T) {
|
|
t.Cleanup(func() {
|
|
definitionsMu.Lock()
|
|
definitionsList = nil
|
|
definitions = lazy.SyncValue[DefinitionMap]{}
|
|
definitionsUsed = false
|
|
definitionsMu.Unlock()
|
|
})
|
|
|
|
definitionsMu.Lock()
|
|
definitionsList = nil
|
|
definitions = lazy.SyncValue[DefinitionMap]{}
|
|
definitionsUsed = false
|
|
definitionsMu.Unlock()
|
|
}
|