|
|
@@ -0,0 +1,375 @@
|
|
|
+// Copyright (c) Tailscale Inc & AUTHORS
|
|
|
+// SPDX-License-Identifier: BSD-3-Clause
|
|
|
+
|
|
|
+package syspolicy
|
|
|
+
|
|
|
+import (
|
|
|
+ "errors"
|
|
|
+ "testing"
|
|
|
+ "time"
|
|
|
+)
|
|
|
+
|
|
|
+// testHandler encompasses all data types returned when testing any of the syspolicy
|
|
|
+// methods that involve getting a policy value.
|
|
|
+// For keys and the corresponding values, check policy_keys.go.
|
|
|
+type testHandler struct {
|
|
|
+ t *testing.T
|
|
|
+ key Key
|
|
|
+ s string
|
|
|
+ u64 uint64
|
|
|
+ err error
|
|
|
+}
|
|
|
+
|
|
|
+var someOtherError = errors.New("error other than not found")
|
|
|
+
|
|
|
+func setHandlerForTest(tb testing.TB, h Handler) {
|
|
|
+ tb.Helper()
|
|
|
+ oldHandler := handler
|
|
|
+ handler = h
|
|
|
+ tb.Cleanup(func() { handler = oldHandler })
|
|
|
+}
|
|
|
+
|
|
|
+func (th *testHandler) ReadString(key string) (string, error) {
|
|
|
+ if key != string(th.key) {
|
|
|
+ th.t.Errorf("ReadString(%q) want %q", key, th.key)
|
|
|
+ }
|
|
|
+ return th.s, th.err
|
|
|
+}
|
|
|
+
|
|
|
+func (th *testHandler) ReadUInt64(key string) (uint64, error) {
|
|
|
+ if key != string(th.key) {
|
|
|
+ th.t.Errorf("ReadUint64(%q) want %q", key, th.key)
|
|
|
+ }
|
|
|
+ return th.u64, th.err
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetString(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ key Key
|
|
|
+ handlerValue string
|
|
|
+ handlerError error
|
|
|
+ defaultValue string
|
|
|
+ wantValue string
|
|
|
+ wantError error
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "read existing value",
|
|
|
+ key: AdminConsoleVisibility,
|
|
|
+ handlerValue: "hide",
|
|
|
+ wantValue: "hide",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value",
|
|
|
+ key: EnableServerMode,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantError: nil,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value, non-blank default",
|
|
|
+ key: EnableServerMode,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ defaultValue: "test",
|
|
|
+ wantValue: "test",
|
|
|
+ wantError: nil,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "reading value returns other error",
|
|
|
+ key: NetworkDevicesVisibility,
|
|
|
+ handlerError: someOtherError,
|
|
|
+ wantError: someOtherError,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ setHandlerForTest(t, &testHandler{
|
|
|
+ t: t,
|
|
|
+ key: tt.key,
|
|
|
+ s: tt.handlerValue,
|
|
|
+ err: tt.handlerError,
|
|
|
+ })
|
|
|
+ value, err := GetString(tt.key, tt.defaultValue)
|
|
|
+ if err != tt.wantError {
|
|
|
+ t.Errorf("err=%q, want %q", err, tt.wantError)
|
|
|
+ }
|
|
|
+ if value != tt.wantValue {
|
|
|
+ t.Errorf("value=%v, want %v", value, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetUint64(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ key Key
|
|
|
+ handlerValue uint64
|
|
|
+ handlerError error
|
|
|
+ defaultValue uint64
|
|
|
+ wantValue uint64
|
|
|
+ wantError error
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "read existing value",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerValue: 1,
|
|
|
+ wantValue: 1,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value",
|
|
|
+ key: LogSCMInteractions,
|
|
|
+ handlerValue: 0,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: 0,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value, non-zero default",
|
|
|
+ key: LogSCMInteractions,
|
|
|
+ defaultValue: 2,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: 2,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "reading value returns other error",
|
|
|
+ key: FlushDNSOnSessionUnlock,
|
|
|
+ handlerError: someOtherError,
|
|
|
+ wantError: someOtherError,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ setHandlerForTest(t, &testHandler{
|
|
|
+ t: t,
|
|
|
+ key: tt.key,
|
|
|
+ u64: tt.handlerValue,
|
|
|
+ err: tt.handlerError,
|
|
|
+ })
|
|
|
+ value, err := GetUint64(tt.key, tt.defaultValue)
|
|
|
+ if err != tt.wantError {
|
|
|
+ t.Errorf("err=%q, want %q", err, tt.wantError)
|
|
|
+ }
|
|
|
+ if value != tt.wantValue {
|
|
|
+ t.Errorf("value=%v, want %v", value, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetPreferenceOption(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ key Key
|
|
|
+ handlerValue string
|
|
|
+ handlerError error
|
|
|
+ wantValue PreferenceOption
|
|
|
+ wantError error
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "always by policy",
|
|
|
+ key: EnableIncomingConnections,
|
|
|
+ handlerValue: "always",
|
|
|
+ wantValue: alwaysByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "never by policy",
|
|
|
+ key: EnableIncomingConnections,
|
|
|
+ handlerValue: "never",
|
|
|
+ wantValue: neverByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "use default",
|
|
|
+ key: EnableIncomingConnections,
|
|
|
+ handlerValue: "",
|
|
|
+ wantValue: showChoiceByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value",
|
|
|
+ key: EnableIncomingConnections,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: showChoiceByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "other error is returned",
|
|
|
+ key: EnableIncomingConnections,
|
|
|
+ handlerError: someOtherError,
|
|
|
+ wantValue: showChoiceByPolicy,
|
|
|
+ wantError: someOtherError,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ setHandlerForTest(t, &testHandler{
|
|
|
+ t: t,
|
|
|
+ key: tt.key,
|
|
|
+ s: tt.handlerValue,
|
|
|
+ err: tt.handlerError,
|
|
|
+ })
|
|
|
+ option, err := GetPreferenceOption(tt.key)
|
|
|
+ if err != tt.wantError {
|
|
|
+ t.Errorf("err=%q, want %q", err, tt.wantError)
|
|
|
+ }
|
|
|
+ if option != tt.wantValue {
|
|
|
+ t.Errorf("option=%v, want %v", option, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetVisibility(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ key Key
|
|
|
+ handlerValue string
|
|
|
+ handlerError error
|
|
|
+ wantValue Visibility
|
|
|
+ wantError error
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "hidden by policy",
|
|
|
+ key: AdminConsoleVisibility,
|
|
|
+ handlerValue: "hide",
|
|
|
+ wantValue: hiddenByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "visibility default",
|
|
|
+ key: AdminConsoleVisibility,
|
|
|
+ handlerValue: "show",
|
|
|
+ wantValue: visibleByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value",
|
|
|
+ key: AdminConsoleVisibility,
|
|
|
+ handlerValue: "show",
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: visibleByPolicy,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "other error is returned",
|
|
|
+ key: AdminConsoleVisibility,
|
|
|
+ handlerValue: "show",
|
|
|
+ handlerError: someOtherError,
|
|
|
+ wantValue: visibleByPolicy,
|
|
|
+ wantError: someOtherError,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ setHandlerForTest(t, &testHandler{
|
|
|
+ t: t,
|
|
|
+ key: tt.key,
|
|
|
+ s: tt.handlerValue,
|
|
|
+ err: tt.handlerError,
|
|
|
+ })
|
|
|
+ visibility, err := GetVisibility(tt.key)
|
|
|
+ if err != tt.wantError {
|
|
|
+ t.Errorf("err=%q, want %q", err, tt.wantError)
|
|
|
+ }
|
|
|
+ if visibility != tt.wantValue {
|
|
|
+ t.Errorf("visibility=%v, want %v", visibility, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestGetDuration(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ name string
|
|
|
+ key Key
|
|
|
+ handlerValue string
|
|
|
+ handlerError error
|
|
|
+ defaultValue time.Duration
|
|
|
+ wantValue time.Duration
|
|
|
+ wantError error
|
|
|
+ }{
|
|
|
+ {
|
|
|
+ name: "read existing value",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerValue: "2h",
|
|
|
+ wantValue: 2 * time.Hour,
|
|
|
+ defaultValue: 24 * time.Hour,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "invalid duration value",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerValue: "-20",
|
|
|
+ wantValue: 24 * time.Hour,
|
|
|
+ defaultValue: 24 * time.Hour,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: 24 * time.Hour,
|
|
|
+ defaultValue: 24 * time.Hour,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "read non-existing value different default",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerError: ErrNoSuchKey,
|
|
|
+ wantValue: 0 * time.Second,
|
|
|
+ defaultValue: 0 * time.Second,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ name: "other error is returned",
|
|
|
+ key: KeyExpirationNoticeTime,
|
|
|
+ handlerError: someOtherError,
|
|
|
+ wantValue: 24 * time.Hour,
|
|
|
+ wantError: someOtherError,
|
|
|
+ defaultValue: 24 * time.Hour,
|
|
|
+ },
|
|
|
+ }
|
|
|
+
|
|
|
+ for _, tt := range tests {
|
|
|
+ t.Run(tt.name, func(t *testing.T) {
|
|
|
+ setHandlerForTest(t, &testHandler{
|
|
|
+ t: t,
|
|
|
+ key: tt.key,
|
|
|
+ s: tt.handlerValue,
|
|
|
+ err: tt.handlerError,
|
|
|
+ })
|
|
|
+ duration, err := GetDuration(tt.key, tt.defaultValue)
|
|
|
+ if err != tt.wantError {
|
|
|
+ t.Errorf("err=%q, want %q", err, tt.wantError)
|
|
|
+ }
|
|
|
+ if duration != tt.wantValue {
|
|
|
+ t.Errorf("duration=%v, want %v", duration, tt.wantValue)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func TestSelectControlURL(t *testing.T) {
|
|
|
+ tests := []struct {
|
|
|
+ reg, disk, want string
|
|
|
+ }{
|
|
|
+ // Modern default case.
|
|
|
+ {"", "", "https://controlplane.tailscale.com"},
|
|
|
+
|
|
|
+ // For a user who installed prior to Dec 2020, with
|
|
|
+ // stuff in their registry.
|
|
|
+ {"https://login.tailscale.com", "", "https://login.tailscale.com"},
|
|
|
+
|
|
|
+ // Ignore pre-Dec'20 LoginURL from installer if prefs
|
|
|
+ // prefs overridden manually to an on-prem control
|
|
|
+ // server.
|
|
|
+ {"https://login.tailscale.com", "http://on-prem", "http://on-prem"},
|
|
|
+
|
|
|
+ // Something unknown explicitly set in the registry always wins.
|
|
|
+ {"http://explicit-reg", "", "http://explicit-reg"},
|
|
|
+ {"http://explicit-reg", "http://on-prem", "http://explicit-reg"},
|
|
|
+ {"http://explicit-reg", "https://login.tailscale.com", "http://explicit-reg"},
|
|
|
+ {"http://explicit-reg", "https://controlplane.tailscale.com", "http://explicit-reg"},
|
|
|
+
|
|
|
+ // If nothing in the registry, disk wins.
|
|
|
+ {"", "http://on-prem", "http://on-prem"},
|
|
|
+ }
|
|
|
+ for _, tt := range tests {
|
|
|
+ if got := SelectControlURL(tt.reg, tt.disk); got != tt.want {
|
|
|
+ t.Errorf("(reg %q, disk %q) = %q; want %q", tt.reg, tt.disk, got, tt.want)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|