cmd/tailscaled,ipn/{auditlog,desktop,ipnext,ipnlocal},tsd: extract LocalBackend extension interfaces and implementation

In this PR, we refactor the LocalBackend extension system, moving from direct callbacks to a more organized extension host model.

Specifically, we:
- Extract interface and callback types used by packages extending LocalBackend functionality into a new ipn/ipnext package.
- Define ipnext.Host as a new interface that bridges extensions with LocalBackend.
  It enables extensions to register callbacks and interact with LocalBackend in a concurrency-safe, well-defined, and controlled way.
- Move existing callback registration and invocation code from ipnlocal.LocalBackend into a new type called ipnlocal.ExtensionHost,
  implementing ipnext.Host.
- Improve docs for existing types and methods while adding docs for the new interfaces.
- Add test coverage for both the extracted and the new code.
- Remove ipn/desktop.SessionManager from tsd.System since ipn/desktop is now self-contained.
- Update existing extensions (e.g., ipn/auditlog and ipn/desktop) to use the new interfaces where appropriate.

We're not introducing new callback and hook types (e.g., for ipn.Prefs changes) just yet, nor are we enhancing current callbacks,
such as by improving conflict resolution when more than one extension tries to influence profile selection via a background profile resolver.
These further improvements will be submitted separately.

Updates #12614
Updates tailscale/corp#27645
Updates tailscale/corp#26435
Updates tailscale/corp#18342

Signed-off-by: Nick Khyl <nickk@tailscale.com>
This commit is contained in:
Nick Khyl
2025-04-10 20:24:58 -05:00
committed by Nick Khyl
parent 11d1dd2aed
commit 4941cd7c73
11 changed files with 2079 additions and 331 deletions

View File

@@ -14,19 +14,23 @@ import (
"tailscale.com/feature"
"tailscale.com/ipn"
"tailscale.com/ipn/ipnauth"
"tailscale.com/ipn/ipnlocal"
"tailscale.com/ipn/ipnext"
"tailscale.com/tailcfg"
"tailscale.com/tsd"
"tailscale.com/types/lazy"
"tailscale.com/types/logger"
)
// featureName is the name of the feature implemented by this package.
// It is also the the [extension] name and the log prefix.
const featureName = "auditlog"
func init() {
feature.Register("auditlog")
ipnlocal.RegisterExtension("auditlog", newExtension)
feature.Register(featureName)
ipnext.RegisterExtension(featureName, newExtension)
}
// extension is an [ipnlocal.Extension] managing audit logging
// extension is an [ipnext.Extension] managing audit logging
// on platforms that import this package.
// As of 2025-03-27, that's only Windows and macOS.
type extension struct {
@@ -48,19 +52,24 @@ type extension struct {
logger *Logger
}
// newExtension is an [ipnlocal.NewExtensionFn] that creates a new audit log extension.
// It is registered with [ipnlocal.RegisterExtension] if the package is imported.
func newExtension(logf logger.Logf, _ *tsd.System) (ipnlocal.Extension, error) {
return &extension{logf: logger.WithPrefix(logf, "auditlog: ")}, nil
// newExtension is an [ipnext.NewExtensionFn] that creates a new audit log extension.
// It is registered with [ipnext.RegisterExtension] if the package is imported.
func newExtension(logf logger.Logf, _ *tsd.System) (ipnext.Extension, error) {
return &extension{logf: logger.WithPrefix(logf, featureName+": ")}, nil
}
// Init implements [ipnlocal.Extension] by registering callbacks and providers
// Name implements [ipnext.Extension].
func (e *extension) Name() string {
return featureName
}
// Init implements [ipnext.Extension] by registering callbacks and providers
// for the duration of the extension's lifetime.
func (e *extension) Init(lb *ipnlocal.LocalBackend) error {
func (e *extension) Init(h ipnext.Host) error {
e.cleanup = []func(){
lb.RegisterControlClientCallback(e.controlClientChanged),
lb.RegisterProfileChangeCallback(e.profileChanged, false),
lb.RegisterAuditLogProvider(e.getCurrentLogger),
h.RegisterControlClientCallback(e.controlClientChanged),
h.Profiles().RegisterProfileChangeCallback(e.profileChanged),
h.RegisterAuditLogProvider(e.getCurrentLogger),
}
return nil
}
@@ -165,8 +174,8 @@ func noCurrentLogger(_ tailcfg.ClientAuditAction, _ string) error {
return errNoLogger
}
// getCurrentLogger is an [ipnlocal.AuditLogProvider] registered with [ipnlocal.LocalBackend].
// It is called when [ipnlocal.LocalBackend] needs to audit an action.
// getCurrentLogger is an [ipnext.AuditLogProvider] registered with [ipnext.Host].
// It is called when [ipnlocal.LocalBackend] or an extension needs to audit an action.
//
// It returns a function that enqueues the audit log for the current profile,
// or [noCurrentLogger] if the logger is unavailable.