|
|
|
@ -7,7 +7,6 @@ import ( |
|
|
|
"context" |
|
|
|
"context" |
|
|
|
"errors" |
|
|
|
"errors" |
|
|
|
"fmt" |
|
|
|
"fmt" |
|
|
|
"iter" |
|
|
|
|
|
|
|
"maps" |
|
|
|
"maps" |
|
|
|
"reflect" |
|
|
|
"reflect" |
|
|
|
"slices" |
|
|
|
"slices" |
|
|
|
@ -24,8 +23,6 @@ import ( |
|
|
|
"tailscale.com/tsd" |
|
|
|
"tailscale.com/tsd" |
|
|
|
"tailscale.com/types/logger" |
|
|
|
"tailscale.com/types/logger" |
|
|
|
"tailscale.com/util/execqueue" |
|
|
|
"tailscale.com/util/execqueue" |
|
|
|
"tailscale.com/util/set" |
|
|
|
|
|
|
|
"tailscale.com/util/slicesx" |
|
|
|
|
|
|
|
"tailscale.com/util/testenv" |
|
|
|
"tailscale.com/util/testenv" |
|
|
|
) |
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
@ -78,6 +75,7 @@ type ExtensionHost struct { |
|
|
|
// initOnce is used to ensure that the extensions are initialized only once,
|
|
|
|
// initOnce is used to ensure that the extensions are initialized only once,
|
|
|
|
// even if [extensionHost.Init] is called multiple times.
|
|
|
|
// even if [extensionHost.Init] is called multiple times.
|
|
|
|
initOnce sync.Once |
|
|
|
initOnce sync.Once |
|
|
|
|
|
|
|
initDone atomic.Bool |
|
|
|
// shutdownOnce is like initOnce, but for [ExtensionHost.Shutdown].
|
|
|
|
// shutdownOnce is like initOnce, but for [ExtensionHost.Shutdown].
|
|
|
|
shutdownOnce sync.Once |
|
|
|
shutdownOnce sync.Once |
|
|
|
|
|
|
|
|
|
|
|
@ -87,6 +85,24 @@ type ExtensionHost struct { |
|
|
|
// doEnqueueBackendOperation adds an asynchronous [LocalBackend] operation to the workQueue.
|
|
|
|
// doEnqueueBackendOperation adds an asynchronous [LocalBackend] operation to the workQueue.
|
|
|
|
doEnqueueBackendOperation func(func(Backend)) |
|
|
|
doEnqueueBackendOperation func(func(Backend)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// profileStateChangeCbs are callbacks that are invoked when the current login profile
|
|
|
|
|
|
|
|
// or its [ipn.Prefs] change, after those changes have been made. The current login profile
|
|
|
|
|
|
|
|
// may be changed either because of a profile switch, or because the profile information
|
|
|
|
|
|
|
|
// was updated by [LocalBackend.SetControlClientStatus], including when the profile
|
|
|
|
|
|
|
|
// is first populated and persisted.
|
|
|
|
|
|
|
|
profileStateChangeCbs []ipnext.ProfileStateChangeCallback |
|
|
|
|
|
|
|
// backgroundProfileResolvers are registered background profile resolvers.
|
|
|
|
|
|
|
|
// They're used to determine the profile to use when no GUI/CLI client is connected.
|
|
|
|
|
|
|
|
backgroundProfileResolvers []ipnext.ProfileResolver |
|
|
|
|
|
|
|
// auditLoggers are registered [AuditLogProvider]s.
|
|
|
|
|
|
|
|
// Each provider is called to get an [ipnauth.AuditLogFunc] when an auditable action
|
|
|
|
|
|
|
|
// is about to be performed. If an audit logger returns an error, the action is denied.
|
|
|
|
|
|
|
|
auditLoggers []ipnext.AuditLogProvider |
|
|
|
|
|
|
|
// newControlClientCbs are the functions to be called when a new control client is created.
|
|
|
|
|
|
|
|
newControlClientCbs []ipnext.NewControlClientCallback |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
shuttingDown atomic.Bool |
|
|
|
|
|
|
|
|
|
|
|
// mu protects the following fields.
|
|
|
|
// mu protects the following fields.
|
|
|
|
// It must not be held when calling [LocalBackend] methods
|
|
|
|
// It must not be held when calling [LocalBackend] methods
|
|
|
|
// or when invoking callbacks registered by extensions.
|
|
|
|
// or when invoking callbacks registered by extensions.
|
|
|
|
@ -107,22 +123,6 @@ type ExtensionHost struct { |
|
|
|
// currentPrefs is a read-only view of the current profile's [ipn.Prefs]
|
|
|
|
// currentPrefs is a read-only view of the current profile's [ipn.Prefs]
|
|
|
|
// with any private keys stripped. It is always Valid.
|
|
|
|
// with any private keys stripped. It is always Valid.
|
|
|
|
currentPrefs ipn.PrefsView |
|
|
|
currentPrefs ipn.PrefsView |
|
|
|
|
|
|
|
|
|
|
|
// auditLoggers are registered [AuditLogProvider]s.
|
|
|
|
|
|
|
|
// Each provider is called to get an [ipnauth.AuditLogFunc] when an auditable action
|
|
|
|
|
|
|
|
// is about to be performed. If an audit logger returns an error, the action is denied.
|
|
|
|
|
|
|
|
auditLoggers set.HandleSet[ipnext.AuditLogProvider] |
|
|
|
|
|
|
|
// backgroundProfileResolvers are registered background profile resolvers.
|
|
|
|
|
|
|
|
// They're used to determine the profile to use when no GUI/CLI client is connected.
|
|
|
|
|
|
|
|
backgroundProfileResolvers set.HandleSet[ipnext.ProfileResolver] |
|
|
|
|
|
|
|
// newControlClientCbs are the functions to be called when a new control client is created.
|
|
|
|
|
|
|
|
newControlClientCbs set.HandleSet[ipnext.NewControlClientCallback] |
|
|
|
|
|
|
|
// profileStateChangeCbs are callbacks that are invoked when the current login profile
|
|
|
|
|
|
|
|
// or its [ipn.Prefs] change, after those changes have been made. The current login profile
|
|
|
|
|
|
|
|
// may be changed either because of a profile switch, or because the profile information
|
|
|
|
|
|
|
|
// was updated by [LocalBackend.SetControlClientStatus], including when the profile
|
|
|
|
|
|
|
|
// is first populated and persisted.
|
|
|
|
|
|
|
|
profileStateChangeCbs set.HandleSet[ipnext.ProfileStateChangeCallback] |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// Backend is a subset of [LocalBackend] methods that are used by [ExtensionHost].
|
|
|
|
// Backend is a subset of [LocalBackend] methods that are used by [ExtensionHost].
|
|
|
|
@ -160,13 +160,10 @@ func NewExtensionHost(logf logger.Logf, sys *tsd.System, b Backend, overrideExts |
|
|
|
host.workQueue.Add(func() { f(b) }) |
|
|
|
host.workQueue.Add(func() { f(b) }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
var numExts int |
|
|
|
// Use registered extensions.
|
|
|
|
var exts iter.Seq2[int, *ipnext.Definition] |
|
|
|
exts := ipnext.Extensions().All() |
|
|
|
if overrideExts == nil { |
|
|
|
numExts := ipnext.Extensions().Len() |
|
|
|
// Use registered extensions.
|
|
|
|
if overrideExts != nil { |
|
|
|
exts = ipnext.Extensions().All() |
|
|
|
|
|
|
|
numExts = ipnext.Extensions().Len() |
|
|
|
|
|
|
|
} else { |
|
|
|
|
|
|
|
// Use the provided, potentially empty, overrideExts
|
|
|
|
// Use the provided, potentially empty, overrideExts
|
|
|
|
// instead of the registered ones.
|
|
|
|
// instead of the registered ones.
|
|
|
|
exts = slices.All(overrideExts) |
|
|
|
exts = slices.All(overrideExts) |
|
|
|
@ -196,6 +193,8 @@ func (h *ExtensionHost) Init() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (h *ExtensionHost) init() { |
|
|
|
func (h *ExtensionHost) init() { |
|
|
|
|
|
|
|
defer h.initDone.Store(true) |
|
|
|
|
|
|
|
|
|
|
|
// Initialize the extensions in the order they were registered.
|
|
|
|
// Initialize the extensions in the order they were registered.
|
|
|
|
h.mu.Lock() |
|
|
|
h.mu.Lock() |
|
|
|
h.activeExtensions = make([]ipnext.Extension, 0, len(h.allExtensions)) |
|
|
|
h.activeExtensions = make([]ipnext.Extension, 0, len(h.allExtensions)) |
|
|
|
@ -343,21 +342,21 @@ func (h *ExtensionHost) Backend() Backend { |
|
|
|
return h.b |
|
|
|
return h.b |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RegisterProfileStateChangeCallback implements [ipnext.ProfileServices].
|
|
|
|
// addFuncHook appends non-nil fn to hooks.
|
|
|
|
func (h *ExtensionHost) RegisterProfileStateChangeCallback(cb ipnext.ProfileStateChangeCallback) (unregister func()) { |
|
|
|
func addFuncHook[F any](h *ExtensionHost, hooks *[]F, fn F) { |
|
|
|
if h == nil { |
|
|
|
if h.initDone.Load() { |
|
|
|
return func() {} |
|
|
|
panic("invalid callback register after init") |
|
|
|
} |
|
|
|
} |
|
|
|
if cb == nil { |
|
|
|
if reflect.ValueOf(fn).IsZero() { |
|
|
|
panic("nil profile change callback") |
|
|
|
panic("nil function hook") |
|
|
|
} |
|
|
|
} |
|
|
|
h.mu.Lock() |
|
|
|
*hooks = append(*hooks, fn) |
|
|
|
defer h.mu.Unlock() |
|
|
|
} |
|
|
|
handle := h.profileStateChangeCbs.Add(cb) |
|
|
|
|
|
|
|
return func() { |
|
|
|
// RegisterProfileStateChangeCallback implements [ipnext.ProfileServices].
|
|
|
|
h.mu.Lock() |
|
|
|
func (h *ExtensionHost) RegisterProfileStateChangeCallback(cb ipnext.ProfileStateChangeCallback) { |
|
|
|
defer h.mu.Unlock() |
|
|
|
if h != nil { |
|
|
|
delete(h.profileStateChangeCbs, handle) |
|
|
|
addFuncHook(h, &h.profileStateChangeCbs, cb) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -366,7 +365,7 @@ func (h *ExtensionHost) RegisterProfileStateChangeCallback(cb ipnext.ProfileStat |
|
|
|
// It strips private keys from the [ipn.Prefs] before preserving
|
|
|
|
// It strips private keys from the [ipn.Prefs] before preserving
|
|
|
|
// or passing them to the callbacks.
|
|
|
|
// or passing them to the callbacks.
|
|
|
|
func (h *ExtensionHost) NotifyProfileChange(profile ipn.LoginProfileView, prefs ipn.PrefsView, sameNode bool) { |
|
|
|
func (h *ExtensionHost) NotifyProfileChange(profile ipn.LoginProfileView, prefs ipn.PrefsView, sameNode bool) { |
|
|
|
if h == nil { |
|
|
|
if !h.active() { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
h.mu.Lock() |
|
|
|
h.mu.Lock() |
|
|
|
@ -378,10 +377,9 @@ func (h *ExtensionHost) NotifyProfileChange(profile ipn.LoginProfileView, prefs |
|
|
|
// so we can provide them to the extensions later if they ask.
|
|
|
|
// so we can provide them to the extensions later if they ask.
|
|
|
|
h.currentPrefs = prefs |
|
|
|
h.currentPrefs = prefs |
|
|
|
h.currentProfile = profile |
|
|
|
h.currentProfile = profile |
|
|
|
// Get the callbacks to be invoked.
|
|
|
|
|
|
|
|
cbs := slicesx.MapValues(h.profileStateChangeCbs) |
|
|
|
|
|
|
|
h.mu.Unlock() |
|
|
|
h.mu.Unlock() |
|
|
|
for _, cb := range cbs { |
|
|
|
|
|
|
|
|
|
|
|
for _, cb := range h.profileStateChangeCbs { |
|
|
|
cb(profile, prefs, sameNode) |
|
|
|
cb(profile, prefs, sameNode) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
@ -390,7 +388,7 @@ func (h *ExtensionHost) NotifyProfileChange(profile ipn.LoginProfileView, prefs |
|
|
|
// and updates the current profile and prefs in the host.
|
|
|
|
// and updates the current profile and prefs in the host.
|
|
|
|
// It strips private keys from the [ipn.Prefs] before preserving or using them.
|
|
|
|
// It strips private keys from the [ipn.Prefs] before preserving or using them.
|
|
|
|
func (h *ExtensionHost) NotifyProfilePrefsChanged(profile ipn.LoginProfileView, oldPrefs, newPrefs ipn.PrefsView) { |
|
|
|
func (h *ExtensionHost) NotifyProfilePrefsChanged(profile ipn.LoginProfileView, oldPrefs, newPrefs ipn.PrefsView) { |
|
|
|
if h == nil { |
|
|
|
if !h.active() { |
|
|
|
return |
|
|
|
return |
|
|
|
} |
|
|
|
} |
|
|
|
h.mu.Lock() |
|
|
|
h.mu.Lock() |
|
|
|
@ -403,28 +401,24 @@ func (h *ExtensionHost) NotifyProfilePrefsChanged(profile ipn.LoginProfileView, |
|
|
|
h.currentPrefs = newPrefs |
|
|
|
h.currentPrefs = newPrefs |
|
|
|
h.currentProfile = profile |
|
|
|
h.currentProfile = profile |
|
|
|
// Get the callbacks to be invoked.
|
|
|
|
// Get the callbacks to be invoked.
|
|
|
|
stateCbs := slicesx.MapValues(h.profileStateChangeCbs) |
|
|
|
|
|
|
|
h.mu.Unlock() |
|
|
|
h.mu.Unlock() |
|
|
|
for _, cb := range stateCbs { |
|
|
|
|
|
|
|
|
|
|
|
for _, cb := range h.profileStateChangeCbs { |
|
|
|
cb(profile, newPrefs, true) |
|
|
|
cb(profile, newPrefs, true) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RegisterBackgroundProfileResolver implements [ipnext.ProfileServices].
|
|
|
|
// RegisterBackgroundProfileResolver implements [ipnext.ProfileServices].
|
|
|
|
func (h *ExtensionHost) RegisterBackgroundProfileResolver(resolver ipnext.ProfileResolver) (unregister func()) { |
|
|
|
func (h *ExtensionHost) RegisterBackgroundProfileResolver(resolver ipnext.ProfileResolver) { |
|
|
|
if h == nil { |
|
|
|
if h != nil { |
|
|
|
return func() {} |
|
|
|
addFuncHook(h, &h.backgroundProfileResolvers, resolver) |
|
|
|
} |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
handle := h.backgroundProfileResolvers.Add(resolver) |
|
|
|
|
|
|
|
return func() { |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
delete(h.backgroundProfileResolvers, handle) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (h *ExtensionHost) active() bool { |
|
|
|
|
|
|
|
return h != nil && !h.shuttingDown.Load() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// DetermineBackgroundProfile returns a read-only view of the profile
|
|
|
|
// DetermineBackgroundProfile returns a read-only view of the profile
|
|
|
|
// used when no GUI/CLI client is connected, using background profile
|
|
|
|
// used when no GUI/CLI client is connected, using background profile
|
|
|
|
// resolvers registered by extensions.
|
|
|
|
// resolvers registered by extensions.
|
|
|
|
@ -434,7 +428,7 @@ func (h *ExtensionHost) RegisterBackgroundProfileResolver(resolver ipnext.Profil |
|
|
|
//
|
|
|
|
//
|
|
|
|
// As of 2025-02-07, this is only used on Windows.
|
|
|
|
// As of 2025-02-07, this is only used on Windows.
|
|
|
|
func (h *ExtensionHost) DetermineBackgroundProfile(profiles ipnext.ProfileStore) ipn.LoginProfileView { |
|
|
|
func (h *ExtensionHost) DetermineBackgroundProfile(profiles ipnext.ProfileStore) ipn.LoginProfileView { |
|
|
|
if h == nil { |
|
|
|
if !h.active() { |
|
|
|
return ipn.LoginProfileView{} |
|
|
|
return ipn.LoginProfileView{} |
|
|
|
} |
|
|
|
} |
|
|
|
// TODO(nickkhyl): check if the returned profile is allowed on the device,
|
|
|
|
// TODO(nickkhyl): check if the returned profile is allowed on the device,
|
|
|
|
@ -443,10 +437,7 @@ func (h *ExtensionHost) DetermineBackgroundProfile(profiles ipnext.ProfileStore) |
|
|
|
|
|
|
|
|
|
|
|
// Attempt to resolve the background profile using the registered
|
|
|
|
// Attempt to resolve the background profile using the registered
|
|
|
|
// background profile resolvers (e.g., [ipn/desktop.desktopSessionsExt] on Windows).
|
|
|
|
// background profile resolvers (e.g., [ipn/desktop.desktopSessionsExt] on Windows).
|
|
|
|
h.mu.Lock() |
|
|
|
for _, resolver := range h.backgroundProfileResolvers { |
|
|
|
resolvers := slicesx.MapValues(h.backgroundProfileResolvers) |
|
|
|
|
|
|
|
h.mu.Unlock() |
|
|
|
|
|
|
|
for _, resolver := range resolvers { |
|
|
|
|
|
|
|
if profile := resolver(profiles); profile.Valid() { |
|
|
|
if profile := resolver(profiles); profile.Valid() { |
|
|
|
return profile |
|
|
|
return profile |
|
|
|
} |
|
|
|
} |
|
|
|
@ -458,35 +449,21 @@ func (h *ExtensionHost) DetermineBackgroundProfile(profiles ipnext.ProfileStore) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RegisterControlClientCallback implements [ipnext.Host].
|
|
|
|
// RegisterControlClientCallback implements [ipnext.Host].
|
|
|
|
func (h *ExtensionHost) RegisterControlClientCallback(cb ipnext.NewControlClientCallback) (unregister func()) { |
|
|
|
func (h *ExtensionHost) RegisterControlClientCallback(cb ipnext.NewControlClientCallback) { |
|
|
|
if h == nil { |
|
|
|
if h != nil { |
|
|
|
return func() {} |
|
|
|
addFuncHook(h, &h.newControlClientCbs, cb) |
|
|
|
} |
|
|
|
|
|
|
|
if cb == nil { |
|
|
|
|
|
|
|
panic("nil control client callback") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
handle := h.newControlClientCbs.Add(cb) |
|
|
|
|
|
|
|
return func() { |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
delete(h.newControlClientCbs, handle) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// NotifyNewControlClient invokes all registered control client callbacks.
|
|
|
|
// NotifyNewControlClient invokes all registered control client callbacks.
|
|
|
|
// It returns callbacks to be executed when the control client shuts down.
|
|
|
|
// It returns callbacks to be executed when the control client shuts down.
|
|
|
|
func (h *ExtensionHost) NotifyNewControlClient(cc controlclient.Client, profile ipn.LoginProfileView) (ccShutdownCbs []func()) { |
|
|
|
func (h *ExtensionHost) NotifyNewControlClient(cc controlclient.Client, profile ipn.LoginProfileView) (ccShutdownCbs []func()) { |
|
|
|
if h == nil { |
|
|
|
if !h.active() { |
|
|
|
return nil |
|
|
|
return nil |
|
|
|
} |
|
|
|
} |
|
|
|
h.mu.Lock() |
|
|
|
if len(h.newControlClientCbs) > 0 { |
|
|
|
cbs := slicesx.MapValues(h.newControlClientCbs) |
|
|
|
ccShutdownCbs = make([]func(), 0, len(h.newControlClientCbs)) |
|
|
|
h.mu.Unlock() |
|
|
|
for _, cb := range h.newControlClientCbs { |
|
|
|
if len(cbs) > 0 { |
|
|
|
|
|
|
|
ccShutdownCbs = make([]func(), 0, len(cbs)) |
|
|
|
|
|
|
|
for _, cb := range cbs { |
|
|
|
|
|
|
|
if shutdown := cb(cc, profile); shutdown != nil { |
|
|
|
if shutdown := cb(cc, profile); shutdown != nil { |
|
|
|
ccShutdownCbs = append(ccShutdownCbs, shutdown) |
|
|
|
ccShutdownCbs = append(ccShutdownCbs, shutdown) |
|
|
|
} |
|
|
|
} |
|
|
|
@ -496,20 +473,9 @@ func (h *ExtensionHost) NotifyNewControlClient(cc controlclient.Client, profile |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// RegisterAuditLogProvider implements [ipnext.Host].
|
|
|
|
// RegisterAuditLogProvider implements [ipnext.Host].
|
|
|
|
func (h *ExtensionHost) RegisterAuditLogProvider(provider ipnext.AuditLogProvider) (unregister func()) { |
|
|
|
func (h *ExtensionHost) RegisterAuditLogProvider(provider ipnext.AuditLogProvider) { |
|
|
|
if h == nil { |
|
|
|
if h != nil { |
|
|
|
return func() {} |
|
|
|
addFuncHook(h, &h.auditLoggers, provider) |
|
|
|
} |
|
|
|
|
|
|
|
if provider == nil { |
|
|
|
|
|
|
|
panic("nil audit log provider") |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
handle := h.auditLoggers.Add(provider) |
|
|
|
|
|
|
|
return func() { |
|
|
|
|
|
|
|
h.mu.Lock() |
|
|
|
|
|
|
|
defer h.mu.Unlock() |
|
|
|
|
|
|
|
delete(h.auditLoggers, handle) |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -523,20 +489,12 @@ func (h *ExtensionHost) RegisterAuditLogProvider(provider ipnext.AuditLogProvide |
|
|
|
// which typically includes the current profile and the audit loggers registered by extensions.
|
|
|
|
// which typically includes the current profile and the audit loggers registered by extensions.
|
|
|
|
// It must not be persisted outside of the auditable action context.
|
|
|
|
// It must not be persisted outside of the auditable action context.
|
|
|
|
func (h *ExtensionHost) AuditLogger() ipnauth.AuditLogFunc { |
|
|
|
func (h *ExtensionHost) AuditLogger() ipnauth.AuditLogFunc { |
|
|
|
if h == nil { |
|
|
|
if !h.active() { |
|
|
|
return func(tailcfg.ClientAuditAction, string) error { return nil } |
|
|
|
return func(tailcfg.ClientAuditAction, string) error { return nil } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
loggers := make([]ipnauth.AuditLogFunc, 0, len(h.auditLoggers)) |
|
|
|
h.mu.Lock() |
|
|
|
for _, provider := range h.auditLoggers { |
|
|
|
providers := slicesx.MapValues(h.auditLoggers) |
|
|
|
loggers = append(loggers, provider()) |
|
|
|
h.mu.Unlock() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
var loggers []ipnauth.AuditLogFunc |
|
|
|
|
|
|
|
if len(providers) > 0 { |
|
|
|
|
|
|
|
loggers = make([]ipnauth.AuditLogFunc, len(providers)) |
|
|
|
|
|
|
|
for i, provider := range providers { |
|
|
|
|
|
|
|
loggers[i] = provider() |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
return func(action tailcfg.ClientAuditAction, details string) error { |
|
|
|
return func(action tailcfg.ClientAuditAction, details string) error { |
|
|
|
// Log auditable actions to the host's log regardless of whether
|
|
|
|
// Log auditable actions to the host's log regardless of whether
|
|
|
|
@ -567,6 +525,7 @@ func (h *ExtensionHost) Shutdown() { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
func (h *ExtensionHost) shutdown() { |
|
|
|
func (h *ExtensionHost) shutdown() { |
|
|
|
|
|
|
|
h.shuttingDown.Store(true) |
|
|
|
// Prevent any queued but not yet started operations from running,
|
|
|
|
// Prevent any queued but not yet started operations from running,
|
|
|
|
// block new operations from being enqueued, and wait for the
|
|
|
|
// block new operations from being enqueued, and wait for the
|
|
|
|
// currently executing operation (if any) to finish.
|
|
|
|
// currently executing operation (if any) to finish.
|
|
|
|
|