2023-09-29 13:40:35 -04:00
// Copyright (c) Tailscale Inc & AUTHORS
// SPDX-License-Identifier: BSD-3-Clause
package syspolicy
uti/syspolicy: user policy support, auto-refresh and initial preparation for policy structs
This updates the syspolicy package to support multiple policy sources in the
three policy scopes: user, profile, and device, and provides a merged resultant
policy. A policy source is a syspolicy/source.Store that has a name and provides
access to policy settings for a given scope. It can be registered with
syspolicy/rsop.RegisterStore. Policy sources and policy stores can be either
platform-specific or platform-agnostic. On Windows, we have the Registry-based,
platform-specific policy store implemented as
syspolicy/source.PlatformPolicyStore. This store provides access to the Group
Policy and MDM policy settings stored in the Registry. On other platforms, we
currently provide a wrapper that converts a syspolicy.Handler into a
syspolicy/source.Store. However, we should update them in follow-up PRs. An
example of a platform-agnostic policy store would be a policy deployed from the
control, a local policy config file, or even environment variables.
We maintain the current, most recent version of the resultant policy for each
scope in an rsop.Policy. This is done by reading and merging the policy settings
from the registered stores the first time the resultant policy is requested,
then re-reading and re-merging them if a store implements the source.Changeable
interface and reports a policy change. Policy change notifications are debounced
to avoid re-reading policy settings multiple times if there are several changes
within a short period. The rsop.Policy can notify clients if the resultant
policy has changed. However, we do not currently expose this via the syspolicy
package and plan to do so differently along with a struct-based policy hierarchy
in the next PR.
To facilitate this, all policy settings should be registered with the
setting.Register function. The syspolicy package does this automatically for all
policy settings defined in policy_keys.go.
The new functionality is available through the existing syspolicy.Read* set of
functions. However, we plan to expose it via a struct-based policy hierarchy,
along with policy change notifications that other subsystems can use, in the
next PR. We also plan to send the resultant policy back from tailscaled to the
clients via the LocalAPI.
This is primarily a foundational PR to facilitate future changes, but the
immediate observable changes on Windows include:
- The service will use the current policy setting values instead of those read
at OS boot time.
- The GUI has access to policy settings configured on a per-user basis.
On Android:
- We now report policy setting usage via clientmetrics.
Updates #12687
Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-08-02 19:18:42 -05:00
import (
"tailscale.com/types/lazy"
"tailscale.com/util/syspolicy/internal/lazyinit"
"tailscale.com/util/syspolicy/setting"
"tailscale.com/util/testenv"
)
type Key = setting . Key
// The const block below lists known policy keys.
// When adding a key to this list, remember to add a corresponding
// [setting.Definition] to [implicitDefinitions] below.
// Otherwise, the [TestKnownKeysRegistered] test will fail as a reminder.
// Preferably, use a strongly typed policy hierarchy, such as [Policy],
// instead of adding each key to the list below.
2023-09-29 13:40:35 -04:00
const (
// Keys with a string value
2023-10-24 17:07:25 -07:00
ControlURL Key = "LoginURL" // default ""; if blank, ipn uses ipn.DefaultControlURL.
LogTarget Key = "LogTarget" // default ""; if blank logging uses logtail.DefaultHost.
Tailnet Key = "Tailnet" // default ""; if blank, no tailnet name is sent to the server.
2023-11-29 16:48:25 -05:00
// ExitNodeID is the exit node's node id. default ""; if blank, no exit node is forced.
// Exit node ID takes precedence over exit node IP.
// To find the node ID, go to /api.md#device.
ExitNodeID Key = "ExitNodeID"
ExitNodeIP Key = "ExitNodeIP" // default ""; if blank, no exit node is forced. Value is exit node IP.
2023-09-29 13:40:35 -04:00
// Keys with a string value that specifies an option: "always", "never", "user-decides".
2023-11-01 17:20:25 -04:00
// The default is "user-decides" unless otherwise stated. Enforcement of
2023-12-07 12:01:31 -05:00
// these policies is typically performed in ipnlocal.applySysPolicy(). GUIs
// typically hide menu items related to policies that are enforced.
2023-09-29 13:40:35 -04:00
EnableIncomingConnections Key = "AllowIncomingConnections"
EnableServerMode Key = "UnattendedMode"
2023-11-04 15:51:20 -04:00
ExitNodeAllowLANAccess Key = "ExitNodeAllowLANAccess"
2023-12-05 13:30:32 -05:00
EnableTailscaleDNS Key = "UseTailscaleDNSSettings"
EnableTailscaleSubnets Key = "UseTailscaleSubnets"
2023-12-07 12:01:31 -05:00
// CheckUpdates is the key to signal if the updater should periodically
// check for updates.
CheckUpdates Key = "CheckUpdates"
// ApplyUpdates is the key to signal if updates should be automatically
// installed. Its value is "InstallUpdates" because of an awkwardly-named
// visibility option "ApplyUpdates" on MacOS.
ApplyUpdates Key = "InstallUpdates"
2023-12-07 12:01:31 -05:00
// EnableRunExitNode controls if the device acts as an exit node. Even when
// running as an exit node, the device must be approved by a tailnet
// administrator. Its name is slightly awkward because RunExitNodeVisibility
// predates this option but is preserved for backwards compatibility.
EnableRunExitNode Key = "AdvertiseExitNode"
2023-09-29 13:40:35 -04:00
// Keys with a string value that controls visibility: "show", "hide".
2023-11-01 17:20:25 -04:00
// The default is "show" unless otherwise stated. Enforcement of these
// policies is typically performed by the UI code for the relevant operating
// system.
2024-02-22 10:10:31 -08:00
AdminConsoleVisibility Key = "AdminConsole"
NetworkDevicesVisibility Key = "NetworkDevices"
TestMenuVisibility Key = "TestMenu"
UpdateMenuVisibility Key = "UpdateMenu"
ResetToDefaultsVisibility Key = "ResetToDefaults"
2023-12-07 12:01:31 -05:00
// RunExitNodeVisibility controls if the "run as exit node" menu item is
// visible, without controlling the setting itself. This is preserved for
// backwards compatibility but prefer EnableRunExitNode in new deployments.
2023-09-29 13:40:35 -04:00
RunExitNodeVisibility Key = "RunExitNode"
PreferencesMenuVisibility Key = "PreferencesMenu"
2023-11-06 21:42:28 -05:00
ExitNodeMenuVisibility Key = "ExitNodesPicker"
2023-12-07 12:01:31 -05:00
// AutoUpdateVisibility is the key to signal if the menu item for automatic
// installation of updates should be visible. It is only used by macsys
// installations and uses the Sparkle naming convention, even though it does
// not actually control updates, merely the UI for that setting.
AutoUpdateVisibility Key = "ApplyUpdates"
2024-05-06 12:14:10 -04:00
// SuggestedExitNodeVisibility controls the visibility of suggested exit nodes in the client GUI.
// When this system policy is set to 'hide', an exit node suggestion won't be presented to the user as part of the exit nodes picker.
SuggestedExitNodeVisibility Key = "SuggestedExitNode"
2023-09-29 13:40:35 -04:00
// Keys with a string value formatted for use with time.ParseDuration().
KeyExpirationNoticeTime Key = "KeyExpirationNotice" // default 24 hours
// Boolean Keys that are only applicable on Windows. Booleans are stored in the registry as
// DWORD or QWORD (either is acceptable). 0 means false, and anything else means true.
// The default is 0 unless otherwise stated.
LogSCMInteractions Key = "LogSCMInteractions"
FlushDNSOnSessionUnlock Key = "FlushDNSOnSessionUnlock"
2023-10-16 09:39:20 +02:00
// PostureChecking indicates if posture checking is enabled and the client shall gather
2023-10-06 15:00:04 +02:00
// posture data.
2023-10-16 09:39:20 +02:00
// Key is a string value that specifies an option: "always", "never", "user-decides".
// The default is "user-decides" unless otherwise stated.
2023-10-06 15:00:04 +02:00
PostureChecking Key = "PostureChecking"
2024-06-14 10:59:40 -07:00
// DeviceSerialNumber is the serial number of the device that is running Tailscale.
// This is used on iOS/tvOS to allow IT administrators to manually give us a serial number via MDM.
// We are unable to programmatically get the serial number from IOKit due to sandboxing restrictions.
DeviceSerialNumber Key = "DeviceSerialNumber"
2024-02-20 15:08:06 -08:00
// ManagedByOrganizationName indicates the name of the organization managing the Tailscale
// install. It is displayed inside the client UI in a prominent location.
ManagedByOrganizationName Key = "ManagedByOrganizationName"
// ManagedByCaption is an info message displayed inside the client UI as a caption when
// ManagedByOrganizationName is set. It can be used to provide a pointer to support resources
// for Tailscale within the organization.
ManagedByCaption Key = "ManagedByCaption"
2024-02-22 10:10:31 -08:00
// ManagedByURL is a valid URL pointing to a support help desk for Tailscale within the
2024-02-20 15:08:06 -08:00
// organization. A button in the client UI provides easy access to this URL.
ManagedByURL Key = "ManagedByURL"
2024-05-06 12:14:10 -04:00
// Keys with a string array value.
// AllowedSuggestedExitNodes's string array value is a list of exit node IDs that restricts which exit nodes are considered when generating suggestions for exit nodes.
AllowedSuggestedExitNodes Key = "AllowedSuggestedExitNodes"
2023-09-29 13:40:35 -04:00
)
uti/syspolicy: user policy support, auto-refresh and initial preparation for policy structs
This updates the syspolicy package to support multiple policy sources in the
three policy scopes: user, profile, and device, and provides a merged resultant
policy. A policy source is a syspolicy/source.Store that has a name and provides
access to policy settings for a given scope. It can be registered with
syspolicy/rsop.RegisterStore. Policy sources and policy stores can be either
platform-specific or platform-agnostic. On Windows, we have the Registry-based,
platform-specific policy store implemented as
syspolicy/source.PlatformPolicyStore. This store provides access to the Group
Policy and MDM policy settings stored in the Registry. On other platforms, we
currently provide a wrapper that converts a syspolicy.Handler into a
syspolicy/source.Store. However, we should update them in follow-up PRs. An
example of a platform-agnostic policy store would be a policy deployed from the
control, a local policy config file, or even environment variables.
We maintain the current, most recent version of the resultant policy for each
scope in an rsop.Policy. This is done by reading and merging the policy settings
from the registered stores the first time the resultant policy is requested,
then re-reading and re-merging them if a store implements the source.Changeable
interface and reports a policy change. Policy change notifications are debounced
to avoid re-reading policy settings multiple times if there are several changes
within a short period. The rsop.Policy can notify clients if the resultant
policy has changed. However, we do not currently expose this via the syspolicy
package and plan to do so differently along with a struct-based policy hierarchy
in the next PR.
To facilitate this, all policy settings should be registered with the
setting.Register function. The syspolicy package does this automatically for all
policy settings defined in policy_keys.go.
The new functionality is available through the existing syspolicy.Read* set of
functions. However, we plan to expose it via a struct-based policy hierarchy,
along with policy change notifications that other subsystems can use, in the
next PR. We also plan to send the resultant policy back from tailscaled to the
clients via the LocalAPI.
This is primarily a foundational PR to facilitate future changes, but the
immediate observable changes on Windows include:
- The service will use the current policy setting values instead of those read
at OS boot time.
- The GUI has access to policy settings configured on a per-user basis.
On Android:
- We now report policy setting usage via clientmetrics.
Updates #12687
Signed-off-by: Nick Khyl <nickk@tailscale.com>
2024-08-02 19:18:42 -05:00
// implicitDefinitions is a list of [setting.Definition] that will be registered
// automatically by [settingDefinitions] as soon as the package needs to ready a policy.
var implicitDefinitions = [ ] * setting . Definition {
// Device policy settings
setting . NewDefinition ( AllowedSuggestedExitNodes , setting . DeviceSetting , setting . StringListValue ) ,
setting . NewDefinition ( ApplyUpdates , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( CheckUpdates , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( ControlURL , setting . DeviceSetting , setting . StringValue ) ,
setting . NewDefinition ( DeviceSerialNumber , setting . DeviceSetting , setting . StringValue ) ,
setting . NewDefinition ( EnableIncomingConnections , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( EnableRunExitNode , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( EnableServerMode , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( EnableTailscaleDNS , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( EnableTailscaleSubnets , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( ExitNodeAllowLANAccess , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( ExitNodeID , setting . DeviceSetting , setting . StringValue ) ,
setting . NewDefinition ( ExitNodeIP , setting . DeviceSetting , setting . StringValue ) ,
setting . NewDefinition ( FlushDNSOnSessionUnlock , setting . DeviceSetting , setting . BooleanValue ) ,
setting . NewDefinition ( LogSCMInteractions , setting . DeviceSetting , setting . BooleanValue ) ,
setting . NewDefinition ( LogTarget , setting . DeviceSetting , setting . StringValue ) ,
setting . NewDefinition ( PostureChecking , setting . DeviceSetting , setting . PreferenceOptionValue ) ,
setting . NewDefinition ( Tailnet , setting . DeviceSetting , setting . StringValue ) ,
// User policy settings
setting . NewDefinition ( AdminConsoleVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( AutoUpdateVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( ExitNodeMenuVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( KeyExpirationNoticeTime , setting . UserSetting , setting . DurationValue ) ,
setting . NewDefinition ( ManagedByCaption , setting . UserSetting , setting . StringValue ) ,
setting . NewDefinition ( ManagedByOrganizationName , setting . UserSetting , setting . StringValue ) ,
setting . NewDefinition ( ManagedByURL , setting . UserSetting , setting . StringValue ) ,
setting . NewDefinition ( NetworkDevicesVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( PreferencesMenuVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( ResetToDefaultsVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( RunExitNodeVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( SuggestedExitNodeVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( TestMenuVisibility , setting . UserSetting , setting . VisibilityValue ) ,
setting . NewDefinition ( UpdateMenuVisibility , setting . UserSetting , setting . VisibilityValue ) ,
}
func init ( ) {
lazyinit . Defer ( func ( ) error {
// Avoid implicit [SettingDefinition] registration during tests.
// Each test should control which policy settings to register.
// Use [setting.SetDefinitionsForTest] to specify necessary definitions,
// or [setWellKnownSettingsForTest] to set implicit definitions for the test duration.
if testenv . InTest ( ) {
return nil
}
for _ , d := range implicitDefinitions {
setting . RegisterDefinition ( d )
}
return nil
} )
}
var implicitDefinitionMap lazy . SyncValue [ setting . DefinitionMap ]
// WellKnownSettingDefinition returns a well-known, implicit setting definition by its key,
// or an [ErrNoSuchKey] if a policy setting with the specified key does not exist
// among implicit policy definitions.
func WellKnownSettingDefinition ( k Key ) ( * setting . Definition , error ) {
m , err := implicitDefinitionMap . GetErr ( func ( ) ( setting . DefinitionMap , error ) {
return setting . DefinitionMapOf ( implicitDefinitions )
} )
if err != nil {
return nil , err
}
if d , ok := m [ k ] ; ok {
return d , nil
}
return nil , ErrNoSuchKey
}
// setWellKnownSettingsForTest registers all implicit setting definitions
// for the duration of the test.
func setWellKnownSettingsForTest ( tb lazy . TB ) error {
return setting . SetDefinitionsForTest ( tb , implicitDefinitions ... )
}