From a6f09c8dbff22c406172657e7c687ec0df39934e Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Mon, 13 Jan 2025 17:59:51 -0800 Subject: [PATCH 1/3] [vnet] Windows service --- lib/teleterm/vnet/service.go | 2 +- ...min_process.go => admin_process_darwin.go} | 19 +- ...cket_other.go => admin_process_windows.go} | 40 ++-- lib/vnet/escalate_daemon_darwin.go | 2 +- lib/vnet/escalate_other.go | 40 ---- lib/vnet/escalate_windows.go | 40 ---- lib/vnet/network_stack.go | 3 + lib/vnet/osconfig.go | 5 + lib/vnet/osconfig_windows.go | 36 ---- lib/vnet/process_manager.go | 85 ++++++++ lib/vnet/process_manager_test.go | 4 +- lib/vnet/service_windows.go | 189 ++++++++++++++++++ lib/vnet/socket_windows.go | 45 ----- .../{osconfig_other.go => unsupported_os.go} | 10 +- lib/vnet/user_process.go | 72 +++++++ lib/vnet/{run.go => user_process_darwin.go} | 105 +--------- lib/vnet/user_process_windows.go | 50 +++++ tool/tsh/common/putty_config_windows.go | 2 +- tool/tsh/common/tsh.go | 6 - tool/tsh/common/vnet.go | 10 +- tool/tsh/common/vnet_darwin.go | 14 +- tool/tsh/common/vnet_other.go | 8 - tool/tsh/common/vnet_windows.go | 46 +---- 23 files changed, 459 insertions(+), 374 deletions(-) rename lib/vnet/{admin_process.go => admin_process_darwin.go} (88%) rename lib/vnet/{socket_other.go => admin_process_windows.go} (51%) delete mode 100644 lib/vnet/escalate_other.go delete mode 100644 lib/vnet/escalate_windows.go delete mode 100644 lib/vnet/osconfig_windows.go create mode 100644 lib/vnet/process_manager.go create mode 100644 lib/vnet/service_windows.go delete mode 100644 lib/vnet/socket_windows.go rename lib/vnet/{osconfig_other.go => unsupported_os.go} (70%) create mode 100644 lib/vnet/user_process.go rename lib/vnet/{run.go => user_process_darwin.go} (54%) create mode 100644 lib/vnet/user_process_windows.go diff --git a/lib/teleterm/vnet/service.go b/lib/teleterm/vnet/service.go index 713198a187558..391f69a4fb48e 100644 --- a/lib/teleterm/vnet/service.go +++ b/lib/teleterm/vnet/service.go @@ -160,7 +160,7 @@ func (s *Service) Start(ctx context.Context, req *api.StartRequest) (*api.StartR } s.clusterConfigCache = vnet.NewClusterConfigCache(s.cfg.Clock) - processManager, err := vnet.Run(ctx, &vnet.RunConfig{ + processManager, err := vnet.RunUserProcess(ctx, &vnet.UserProcessConfig{ AppProvider: appProvider, ClusterConfigCache: s.clusterConfigCache, }) diff --git a/lib/vnet/admin_process.go b/lib/vnet/admin_process_darwin.go similarity index 88% rename from lib/vnet/admin_process.go rename to lib/vnet/admin_process_darwin.go index 4c2411d729763..da881ba761825 100644 --- a/lib/vnet/admin_process.go +++ b/lib/vnet/admin_process_darwin.go @@ -27,18 +27,17 @@ import ( "github.com/gravitational/teleport/lib/vnet/daemon" ) -// RunAdminProcess must run as root. It creates and sets up a TUN device and passes -// the file descriptor for that device over the unix socket found at config.socketPath. +// RunDarwinAdminProcess must run as root. It creates and sets up a TUN device +// and passes the file descriptor for that device over the unix socket found at +// config.socketPath. // -// It also handles host OS configuration that must run as root, and stays alive to keep the host configuration -// up to date. It will stay running until the socket at config.socketPath is deleted or until encountering an -// unrecoverable error. -// -// OS configuration is updated every [osConfigurationInterval]. During the update, it temporarily -// changes egid and euid of the process to that of the client connecting to the daemon. -func RunAdminProcess(ctx context.Context, config daemon.Config) error { +// It also handles host OS configuration that must run as root, and stays alive +// to keep the host configuration up to date. It will stay running until the +// socket at config.socketPath is deleted, [ctx] is canceled, or until +// encountering an unrecoverable error. +func RunDarwinAdminProcess(ctx context.Context, config daemon.Config) error { if err := config.CheckAndSetDefaults(); err != nil { - return trace.Wrap(err) + return trace.Wrap(err, "checking daemon process config") } ctx, cancel := context.WithCancel(ctx) diff --git a/lib/vnet/socket_other.go b/lib/vnet/admin_process_windows.go similarity index 51% rename from lib/vnet/socket_other.go rename to lib/vnet/admin_process_windows.go index 9b9ace5eaafdb..1c30c38eb36d1 100644 --- a/lib/vnet/socket_other.go +++ b/lib/vnet/admin_process_windows.go @@ -14,32 +14,32 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//go:build !darwin && !windows -// +build !darwin,!windows - package vnet import ( - "os" + "context" "github.com/gravitational/trace" "golang.zx2c4.com/wireguard/tun" ) -func createSocket() (*noSocket, string, error) { - return nil, "", trace.Wrap(ErrVnetNotImplemented) -} - -func sendTUNNameAndFd(socketPath, tunName string, tunFile *os.File) error { - return trace.Wrap(ErrVnetNotImplemented) -} - -func receiveTUNDevice(_ *noSocket) (tun.Device, error) { - return nil, trace.Wrap(ErrVnetNotImplemented) -} - -type noSocket struct{} - -func (_ noSocket) Close() error { - return trace.Wrap(ErrVnetNotImplemented) +// runWindowsAdminProcess must run as administrator. It creates and sets up a TUN +// device, runs the VNet networking stack, and handles OS configuration. It will +// continue to run until [ctx] is canceled or encountering an unrecoverable +// error. +func runWindowsAdminProcess(ctx context.Context) error { + device, err := tun.CreateTUN("TeleportVNet", mtu) + if err != nil { + return trace.Wrap(err, "creating TUN device") + } + defer device.Close() + tunName, err := device.Name() + if err != nil { + return trace.Wrap(err, "getting TUN device name") + } + log.InfoContext(ctx, "Created TUN interface", "tun", tunName) + // TODO(nklaassen): actually run VNet. For now, just stay alive until the + // context is canceled. + <-ctx.Done() + return trace.Wrap(ctx.Err()) } diff --git a/lib/vnet/escalate_daemon_darwin.go b/lib/vnet/escalate_daemon_darwin.go index 935c16afe9793..6e9572fef59b6 100644 --- a/lib/vnet/escalate_daemon_darwin.go +++ b/lib/vnet/escalate_daemon_darwin.go @@ -35,5 +35,5 @@ func execAdminProcess(ctx context.Context, config daemon.Config) error { // DaemonSubcommand runs the VNet daemon process. func DaemonSubcommand(ctx context.Context) error { - return trace.Wrap(daemon.Start(ctx, RunAdminProcess)) + return trace.Wrap(daemon.Start(ctx, RunDarwinAdminProcess)) } diff --git a/lib/vnet/escalate_other.go b/lib/vnet/escalate_other.go deleted file mode 100644 index 76adfdf1a6606..0000000000000 --- a/lib/vnet/escalate_other.go +++ /dev/null @@ -1,40 +0,0 @@ -// Teleport -// Copyright (C) 2024 Gravitational, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//go:build !darwin && !windows -// +build !darwin,!windows - -package vnet - -import ( - "context" - "runtime" - - "github.com/gravitational/trace" - - "github.com/gravitational/teleport/lib/vnet/daemon" -) - -var ( - // ErrVnetNotImplemented is an error indicating that VNet is not implemented on the host OS. - ErrVnetNotImplemented = &trace.NotImplementedError{Message: "VNet is not implemented on " + runtime.GOOS} -) - -// execAdminProcess is called from the normal user process to execute the admin -// subcommand as root. -func execAdminProcess(ctx context.Context, config daemon.Config) error { - return trace.Wrap(ErrVnetNotImplemented) -} diff --git a/lib/vnet/escalate_windows.go b/lib/vnet/escalate_windows.go deleted file mode 100644 index 3b5d4464eefe8..0000000000000 --- a/lib/vnet/escalate_windows.go +++ /dev/null @@ -1,40 +0,0 @@ -// Teleport -// Copyright (C) 2024 Gravitational, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//go:build windows -// +build windows - -package vnet - -import ( - "context" - - "github.com/gravitational/trace" - - "github.com/gravitational/teleport/lib/vnet/daemon" -) - -var ( - // ErrVnetNotImplemented is an error indicating that VNet is not implemented on the host OS. - ErrVnetNotImplemented = &trace.NotImplementedError{Message: "VNet is not implemented on windows"} -) - -// execAdminProcess is called from the normal user process to execute the admin -// subcommand as root. -func execAdminProcess(ctx context.Context, config daemon.Config) error { - // TODO(nklaassen): implement execAdminProcess on windows. - return trace.Wrap(ErrVnetNotImplemented) -} diff --git a/lib/vnet/network_stack.go b/lib/vnet/network_stack.go index 0479564033e19..6c7dbfa2f8563 100644 --- a/lib/vnet/network_stack.go +++ b/lib/vnet/network_stack.go @@ -41,9 +41,12 @@ import ( "gvisor.dev/gvisor/pkg/waiter" "github.com/gravitational/teleport" + logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/vnet/dns" ) +var log = logutils.NewPackageLogger(teleport.ComponentKey, "vnet") + const ( nicID = 1 mtu = 1500 diff --git a/lib/vnet/osconfig.go b/lib/vnet/osconfig.go index 0642ebd0980dd..05ac1dcb3a688 100644 --- a/lib/vnet/osconfig.go +++ b/lib/vnet/osconfig.go @@ -14,6 +14,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +// TODO(nklaassen): refactor OS configuration so this file isn't +// platform-specific. +//go:build darwin +// +build darwin + package vnet import ( diff --git a/lib/vnet/osconfig_windows.go b/lib/vnet/osconfig_windows.go deleted file mode 100644 index e1547ea69c108..0000000000000 --- a/lib/vnet/osconfig_windows.go +++ /dev/null @@ -1,36 +0,0 @@ -// Teleport -// Copyright (C) 2024 Gravitational, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -//go:build windows -// +build windows - -package vnet - -import ( - "context" - - "github.com/gravitational/trace" -) - -func configureOS(ctx context.Context, cfg *osConfig) error { - // TODO(nklaassen): implement configureOS on Windows. - return trace.Wrap(ErrVnetNotImplemented) -} - -func (c *osConfigurator) doWithDroppedRootPrivileges(ctx context.Context, fn func() error) (err error) { - // TODO(nklaassen): implement doWithDroppedPrivileges on Windows. - return trace.Wrap(ErrVnetNotImplemented) -} diff --git a/lib/vnet/process_manager.go b/lib/vnet/process_manager.go new file mode 100644 index 0000000000000..66464cda63b98 --- /dev/null +++ b/lib/vnet/process_manager.go @@ -0,0 +1,85 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package vnet + +import ( + "context" + "fmt" + "sync" + + "github.com/gravitational/trace" + "golang.org/x/sync/errgroup" +) + +func newProcessManager() (*ProcessManager, context.Context) { + ctx, cancel := context.WithCancel(context.Background()) + g, ctx := errgroup.WithContext(ctx) + pm := &ProcessManager{ + g: g, + cancel: cancel, + closed: make(chan struct{}), + } + pm.closeOnce = sync.OnceFunc(func() { + close(pm.closed) + }) + return pm, ctx +} + +// ProcessManager handles background tasks needed to run VNet. +// Its semantics are similar to an error group with a context, but it cancels the context whenever +// any task returns prematurely, that is, a task exits while the context was not canceled. +type ProcessManager struct { + g *errgroup.Group + cancel context.CancelFunc + closed chan struct{} + closeOnce func() +} + +// AddCriticalBackgroundTask adds a function to the error group. [task] is expected to block until +// the context returned by [newProcessManager] gets canceled. The context gets canceled either by +// calling Close on [ProcessManager] or if any task returns. +func (pm *ProcessManager) AddCriticalBackgroundTask(name string, task func() error) { + pm.g.Go(func() error { + err := task() + if err == nil { + // Make sure to always return an error so that the errgroup context is canceled. + err = fmt.Errorf("critical task %q exited prematurely", name) + } + return trace.Wrap(err) + }) +} + +// Wait blocks and waits for the background tasks to finish, which typically happens when another +// goroutine calls Close on the process manager. +func (pm *ProcessManager) Wait() error { + err := pm.g.Wait() + select { + case <-pm.closed: + // Errors are expected after the process manager has been closed, + // usually due to context cancellation. + return nil + default: + return trace.Wrap(err) + } +} + +// Close stops any active background tasks by canceling the underlying context, +// and waits for all tasks to terminate. +func (pm *ProcessManager) Close() { + pm.closeOnce() + pm.cancel() +} diff --git a/lib/vnet/process_manager_test.go b/lib/vnet/process_manager_test.go index 5309150e35b5e..1e95205a99117 100644 --- a/lib/vnet/process_manager_test.go +++ b/lib/vnet/process_manager_test.go @@ -70,8 +70,6 @@ func TestProcessManager_Close(t *testing.T) { }) pm.Close() - err := pm.Wait() - require.ErrorIs(t, err, context.Canceled) - require.ErrorIs(t, err, context.Cause(pmCtx)) + require.NoError(t, err) } diff --git a/lib/vnet/service_windows.go b/lib/vnet/service_windows.go new file mode 100644 index 0000000000000..1387d6cca4407 --- /dev/null +++ b/lib/vnet/service_windows.go @@ -0,0 +1,189 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package vnet + +import ( + "context" + "log/slog" + "os" + "path/filepath" + "syscall" + "time" + + "github.com/gravitational/trace" + "golang.org/x/sys/windows" + "golang.org/x/sys/windows/svc" + "golang.org/x/sys/windows/svc/mgr" +) + +const ( + ServiceCommand = "vnet-service" + serviceName = "TeleportVNet" + serviceDescription = "This service manages networking and OS configuration for Teleport VNet." + serviceAccessFlags = windows.SERVICE_START | windows.SERVICE_STOP | windows.SERVICE_QUERY_STATUS +) + +// runService is called from the normal user process to run the VNet Windows in +// the background and wait for it to exit. It will terminate the service and +// return immediately if [ctx] is canceled. +func runService(ctx context.Context) error { + service, err := startService(ctx) + if err != nil { + return trace.Wrap(err) + } + defer service.Close() + log.InfoContext(ctx, "Started Windows service", "service", service.Name) + ticker := time.Tick(time.Second) + for { + select { + case <-ctx.Done(): + log.InfoContext(ctx, "Context canceled, stopping Windows service") + if _, err := service.Control(svc.Stop); err != nil { + return trace.Wrap(err, "sending stop request to Windows service %s", service.Name) + } + return nil + case <-ticker: + status, err := service.Query() + if err != nil { + return trace.Wrap(err, "querying admin service") + } + if status.State != svc.Running && status.State != svc.StartPending { + return trace.Errorf("service stopped running prematurely, status: %+v", status) + } + } + } +} + +// startService starts the Windows VNet admin service in the background. +func startService(ctx context.Context) (*mgr.Service, error) { + // Avoid [mgr.Connect] because it requests elevated permissions. + scManager, err := windows.OpenSCManager(nil /*machine*/, nil /*database*/, windows.SC_MANAGER_CONNECT) + if err != nil { + return nil, trace.Wrap(err, "opening Windows service manager") + } + defer windows.CloseServiceHandle(scManager) + serviceNamePtr, err := syscall.UTF16PtrFromString(serviceName) + if err != nil { + return nil, trace.Wrap(err, "converting service name to UTF16") + } + serviceHandle, err := windows.OpenService(scManager, serviceNamePtr, serviceAccessFlags) + if err != nil { + return nil, trace.Wrap(err, "opening Windows service %v", serviceName) + } + service := &mgr.Service{ + Name: serviceName, + Handle: serviceHandle, + } + if err := service.Start(ServiceCommand); err != nil { + return nil, trace.Wrap(err, "starting Windows service %s", serviceName) + } + return service, nil +} + +// ServiceMain runs the Windows VNet admin service. +func ServiceMain() error { + if err := setupServiceLogger(); err != nil { + return trace.Wrap(err, "setting up logger for service") + } + if err := svc.Run(serviceName, &windowsService{}); err != nil { + return trace.Wrap(err, "running Windows service") + } + return nil +} + +// windowsService implements [svc.Handler]. +type windowsService struct{} + +// Execute implements [svc.Handler.Execute], the GoDoc is copied below. +// +// Execute will be called by the package code at the start of the service, and +// the service will exit once Execute completes. Inside Execute you must read +// service change requests from [requests] and act accordingly. You must keep +// service control manager up to date about state of your service by writing +// into [status] as required. args contains service name followed by argument +// strings passed to the service. +// You can provide service exit code in exitCode return parameter, with 0 being +// "no error". You can also indicate if exit code, if any, is service specific +// or not by using svcSpecificEC parameter. +func (s *windowsService) Execute(args []string, requests <-chan svc.ChangeRequest, status chan<- svc.Status) (svcSpecificEC bool, exitCode uint32) { + const cmdsAccepted = svc.AcceptStop // Interrogate is always accepted and there is no const for it. + status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + errCh := make(chan error) + go func() { errCh <- s.run(ctx, args) }() + +loop: + for { + select { + case request := <-requests: + switch request.Cmd { + case svc.Interrogate: + state := svc.Running + if ctx.Err() != nil { + state = svc.StopPending + } + status <- svc.Status{State: state, Accepts: cmdsAccepted} + case svc.Stop: + slog.InfoContext(ctx, "Received stop command, shutting down service") + cancel() + status <- svc.Status{State: svc.StopPending} + } + case err := <-errCh: + slog.ErrorContext(ctx, "Windows VNet service terminated", "error", err) + if err != nil { + exitCode = 1 + } + break loop + } + } + status <- svc.Status{State: svc.Stopped, Win32ExitCode: exitCode} + return false, exitCode +} + +func (s *windowsService) run(ctx context.Context, args []string) error { + if err := runWindowsAdminProcess(ctx); err != nil { + return trace.Wrap(err, "running admin process") + } + return nil +} + +func setupServiceLogger() error { + logFile, err := serviceLogFile() + if err != nil { + return trace.Wrap(err, "creating log file for service") + } + slog.SetDefault(slog.New(slog.NewTextHandler(logFile, &slog.HandlerOptions{ + Level: slog.LevelDebug, + }))) + return nil +} + +func serviceLogFile() (*os.File, error) { + // TODO(nklaassen): find a better path for Windows service logs. + exePath, err := os.Executable() + if err != nil { + return nil, trace.Wrap(err, "getting current executable path") + } + dir := filepath.Dir(exePath) + logFile, err := os.Create(filepath.Join(dir, "logs.txt")) + if err != nil { + return nil, trace.Wrap(err, "creating log file") + } + return logFile, nil +} diff --git a/lib/vnet/socket_windows.go b/lib/vnet/socket_windows.go deleted file mode 100644 index e76996edd3784..0000000000000 --- a/lib/vnet/socket_windows.go +++ /dev/null @@ -1,45 +0,0 @@ -// Teleport -// Copyright (C) 2024 Gravitational, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -package vnet - -import ( - "os" - - "github.com/gravitational/trace" - "golang.zx2c4.com/wireguard/tun" -) - -func createSocket() (*noSocket, string, error) { - // TODO(nklaassen): implement createSocket on windows. - return nil, "", trace.Wrap(ErrVnetNotImplemented) -} - -func sendTUNNameAndFd(socketPath, tunName string, tunFile *os.File) error { - // TODO(nklaassen): implement sendTUNNameAndFd on windows. - return trace.Wrap(ErrVnetNotImplemented) -} - -func receiveTUNDevice(_ *noSocket) (tun.Device, error) { - // TODO(nklaassen): receiveTUNDevice on windows. - return nil, trace.Wrap(ErrVnetNotImplemented) -} - -type noSocket struct{} - -func (_ noSocket) Close() error { - return trace.Wrap(ErrVnetNotImplemented) -} diff --git a/lib/vnet/osconfig_other.go b/lib/vnet/unsupported_os.go similarity index 70% rename from lib/vnet/osconfig_other.go rename to lib/vnet/unsupported_os.go index 8fd543024abe3..a807101f88801 100644 --- a/lib/vnet/osconfig_other.go +++ b/lib/vnet/unsupported_os.go @@ -21,14 +21,14 @@ package vnet import ( "context" + "runtime" "github.com/gravitational/trace" ) -func configureOS(ctx context.Context, cfg *osConfig) error { - return trace.Wrap(ErrVnetNotImplemented) -} +// ErrVnetNotImplemented is an error indicating that VNet is not implemented on the host OS. +var ErrVnetNotImplemented = &trace.NotImplementedError{Message: "VNet is not implemented on " + runtime.GOOS} -func (c *osConfigurator) doWithDroppedRootPrivileges(ctx context.Context, fn func() error) (err error) { - return trace.Wrap(ErrVnetNotImplemented) +func runPlatformUserProcess(_ context.Context, _ *UserProcessConfig) (*ProcessManager, error) { + return nil, trace.Wrap(ErrVnetNotImplemented) } diff --git a/lib/vnet/user_process.go b/lib/vnet/user_process.go new file mode 100644 index 0000000000000..c4acaf41301b1 --- /dev/null +++ b/lib/vnet/user_process.go @@ -0,0 +1,72 @@ +// Teleport +// Copyright (C) 2025 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package vnet + +import ( + "context" + "os" + + "github.com/gravitational/trace" + + "github.com/gravitational/teleport/api/profile" + "github.com/gravitational/teleport/api/types" +) + +// UserProcessConfig provides the necessary configuration to run VNet. +type UserProcessConfig struct { + // AppProvider is a required field providing an interface implementation for [AppProvider]. + AppProvider AppProvider + // ClusterConfigCache is an optional field providing [ClusterConfigCache]. If empty, a new cache + // will be created. + ClusterConfigCache *ClusterConfigCache + // HomePath is the tsh home used for Teleport clients created by VNet. Resolved using the same + // rules as HomeDir in tsh. + HomePath string +} + +func (c *UserProcessConfig) checkAndSetDefaults() error { + if c.AppProvider == nil { + return trace.BadParameter("missing AppProvider") + } + if c.HomePath == "" { + c.HomePath = profile.FullProfilePath(os.Getenv(types.HomeEnvVar)) + } + return nil +} + +// RunUserProcess is called by all VNet client applications (tsh, Connect) to +// start and run all VNet tasks. +// +// It returns a [ProcessManager] which controls the lifecycle of all tasks and +// background processes. The caller is expected to call Close on the process +// manager to clean up any resources, terminate all processes, and remove and OS +// configuration used for actively running VNet. +// +// ctx is used to wait for setup steps that happen before RunUserProcess hands out the +// control to the process manager. If ctx gets canceled during RunUserProcess, the process +// manager gets closed along with its background tasks. +func RunUserProcess(ctx context.Context, cfg *UserProcessConfig) (pm *ProcessManager, err error) { + if err := cfg.checkAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + defer func() { + if pm != nil && err != nil { + pm.Close() + } + }() + return runPlatformUserProcess(ctx, cfg) +} diff --git a/lib/vnet/run.go b/lib/vnet/user_process_darwin.go similarity index 54% rename from lib/vnet/run.go rename to lib/vnet/user_process_darwin.go index 6d7782e714438..530df4dc1c986 100644 --- a/lib/vnet/run.go +++ b/lib/vnet/user_process_darwin.go @@ -19,84 +19,37 @@ package vnet import ( "context" "errors" - "fmt" - "os" "time" "github.com/gravitational/trace" - "golang.org/x/sync/errgroup" "golang.zx2c4.com/wireguard/tun" - "github.com/gravitational/teleport" - "github.com/gravitational/teleport/api/profile" - "github.com/gravitational/teleport/api/types" - logutils "github.com/gravitational/teleport/lib/utils/log" "github.com/gravitational/teleport/lib/vnet/daemon" ) -var log = logutils.NewPackageLogger(teleport.ComponentKey, "vnet") - -// RunConfig provides the necessary configuration to run VNet. -type RunConfig struct { - // AppProvider is a required field providing an interface implementation for [AppProvider]. - AppProvider AppProvider - // ClusterConfigCache is an optional field providing [ClusterConfigCache]. If empty, a new cache - // will be created. - ClusterConfigCache *ClusterConfigCache - // HomePath is the tsh home used for Teleport clients created by VNet. Resolved using the same - // rules as HomeDir in tsh. - HomePath string -} - -func (c *RunConfig) CheckAndSetDefaults() error { - if c.AppProvider == nil { - return trace.BadParameter("missing AppProvider") - } - - if c.HomePath == "" { - c.HomePath = profile.FullProfilePath(os.Getenv(types.HomeEnvVar)) - } - - return nil -} - -// Run creates a network stack for VNet and runs it in the background. To do -// this, it also needs to launch an admin process in the background. It returns -// a [ProcessManager] which controls the lifecycle of both background tasks. +// runPlatformUserProcess creates a network stack for VNet and runs it in the +// background. To do this, it also needs to launch an admin process in the +// background. It returns a [ProcessManager] which controls the lifecycle of +// both background tasks. // // The caller is expected to call Close on the process manager to close the // network stack, clean up any resources used by it and terminate the admin // process. -// -// ctx is used to wait for setup steps that happen before Run hands out the -// control to the process manager. If ctx gets canceled during Run, the process -// manager gets closed along with its background tasks. -func Run(ctx context.Context, config *RunConfig) (*ProcessManager, error) { - if err := config.CheckAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) - } - +func runPlatformUserProcess(ctx context.Context, config *UserProcessConfig) (*ProcessManager, error) { ipv6Prefix, err := NewIPv6Prefix() if err != nil { return nil, trace.Wrap(err) } dnsIPv6 := ipv6WithSuffix(ipv6Prefix, []byte{2}) - pm, processCtx := newProcessManager() - success := false - defer func() { - if !success { - // Closes the socket and background tasks. - pm.Close() - } - }() - // Create the socket that's used to receive the TUN device from the admin process. socket, socketPath, err := createSocket() if err != nil { return nil, trace.Wrap(err) } log.DebugContext(ctx, "Created unix socket for admin process", "socket", socketPath) + + pm, processCtx := newProcessManager() pm.AddCriticalBackgroundTask("socket closer", func() error { // Keep the socket open until the process context is canceled. // Closing the socket signals the admin process to terminate. @@ -170,49 +123,5 @@ func Run(ctx context.Context, config *RunConfig) (*ProcessManager, error) { return trace.Wrap(ns.run(processCtx)) }) - success = true return pm, nil } - -func newProcessManager() (*ProcessManager, context.Context) { - ctx, cancel := context.WithCancel(context.Background()) - g, ctx := errgroup.WithContext(ctx) - - return &ProcessManager{ - g: g, - cancel: cancel, - }, ctx -} - -// ProcessManager handles background tasks needed to run VNet. -// Its semantics are similar to an error group with a context, but it cancels the context whenever -// any task returns prematurely, that is, a task exits while the context was not canceled. -type ProcessManager struct { - g *errgroup.Group - cancel context.CancelFunc -} - -// AddCriticalBackgroundTask adds a function to the error group. [task] is expected to block until -// the context returned by [newProcessManager] gets canceled. The context gets canceled either by -// calling Close on [ProcessManager] or if any task returns. -func (pm *ProcessManager) AddCriticalBackgroundTask(name string, task func() error) { - pm.g.Go(func() error { - err := task() - if err == nil { - // Make sure to always return an error so that the errgroup context is canceled. - err = fmt.Errorf("critical task %q exited prematurely", name) - } - return trace.Wrap(err) - }) -} - -// Wait blocks and waits for the background tasks to finish, which typically happens when another -// goroutine calls Close on the process manager. -func (pm *ProcessManager) Wait() error { - return trace.Wrap(pm.g.Wait()) -} - -// Close stops any active background tasks by canceling the underlying context. -func (pm *ProcessManager) Close() { - pm.cancel() -} diff --git a/lib/vnet/user_process_windows.go b/lib/vnet/user_process_windows.go new file mode 100644 index 0000000000000..bbcbcb0083706 --- /dev/null +++ b/lib/vnet/user_process_windows.go @@ -0,0 +1,50 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +package vnet + +import ( + "context" + + "github.com/gravitational/trace" +) + +// runPlatformUserProcess launches a Windows service in the background that will +// handle all networking and OS configuration. The user process exposes a gRPC +// interface that the admin process uses to query application names and get user +// certificates for apps. +// +// RunUserProcess returns a [ProcessManager] which controls the lifecycle of +// both the user and admin processes. +// +// The caller is expected to call Close on the process manager to clean up any +// resources and terminate the admin process, which will in turn stop the +// networking stack and deconfigure the host OS. +// +// ctx is used to wait for setup steps that happen before RunUserProcess hands out the +// control to the process manager. If ctx gets canceled during RunUserProcess, the process +// manager gets closed along with its background tasks. +func runPlatformUserProcess(ctx context.Context, config *UserProcessConfig) (pm *ProcessManager, err error) { + if err := config.checkAndSetDefaults(); err != nil { + return nil, trace.Wrap(err) + } + pm, processCtx := newProcessManager() + pm.AddCriticalBackgroundTask("VNet Windows service", func() error { + return trace.Wrap(runService(processCtx), "running VNet Windows service in the background") + }) + // TODO(nklaassen): run user process gRPC service. + return pm, nil +} diff --git a/tool/tsh/common/putty_config_windows.go b/tool/tsh/common/putty_config_windows.go index d1ea9eea0f419..9efc50b7665c6 100644 --- a/tool/tsh/common/putty_config_windows.go +++ b/tool/tsh/common/putty_config_windows.go @@ -389,7 +389,7 @@ func onPuttyConfig(cf *CLIConf) error { if err := addPuTTYSession(proxyHost, hostname, port, login, ppkFilePath, certificateFilePath, localCommandString, cf.LeafClusterName); err != nil { logger.ErrorContext(cf.Context, "Failed to add PuTTY session", "user_host", userHostString, - "error",err, + "error", err, ) return trace.Wrap(err) } diff --git a/tool/tsh/common/tsh.go b/tool/tsh/common/tsh.go index f9d4a038a9ae6..0326a8acdc2a3 100644 --- a/tool/tsh/common/tsh.go +++ b/tool/tsh/common/tsh.go @@ -1261,8 +1261,6 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { vnetCommand := newVnetCommand(app) vnetAdminSetupCommand := newVnetAdminSetupCommand(app) vnetDaemonCommand := newVnetDaemonCommand(app) - vnetInstallServiceCommand := newVnetInstallServiceCommand(app) - vnetUninstallServiceCommand := newVnetUninstallServiceCommand(app) vnetServiceCommand := newVnetServiceCommand(app) gitCmd := newGitCommands(app) @@ -1647,10 +1645,6 @@ func Run(ctx context.Context, args []string, opts ...CliOption) error { err = vnetAdminSetupCommand.run(&cf) case vnetDaemonCommand.FullCommand(): err = vnetDaemonCommand.run(&cf) - case vnetInstallServiceCommand.FullCommand(): - err = vnetInstallServiceCommand.run(&cf) - case vnetUninstallServiceCommand.FullCommand(): - err = vnetUninstallServiceCommand.run(&cf) case vnetServiceCommand.FullCommand(): err = vnetServiceCommand.run(&cf) case gitCmd.list.FullCommand(): diff --git a/tool/tsh/common/vnet.go b/tool/tsh/common/vnet.go index 8bcd80a57590f..d85f3536f94e5 100644 --- a/tool/tsh/common/vnet.go +++ b/tool/tsh/common/vnet.go @@ -51,7 +51,7 @@ func (c *vnetCommand) run(cf *CLIConf) error { if err != nil { return trace.Wrap(err) } - processManager, err := vnet.Run(cf.Context, &vnet.RunConfig{AppProvider: appProvider}) + processManager, err := vnet.RunUserProcess(cf.Context, &vnet.UserProcessConfig{AppProvider: appProvider}) if err != nil { return trace.Wrap(err) } @@ -68,14 +68,6 @@ func newVnetDaemonCommand(app *kingpin.Application) vnetCLICommand { return newPlatformVnetDaemonCommand(app) } -func newVnetInstallServiceCommand(app *kingpin.Application) vnetCLICommand { - return newPlatformVnetInstallServiceCommand(app) -} - -func newVnetUninstallServiceCommand(app *kingpin.Application) vnetCLICommand { - return newPlatformVnetUninstallServiceCommand(app) -} - func newVnetServiceCommand(app *kingpin.Application) vnetCLICommand { return newPlatformVnetServiceCommand(app) } diff --git a/tool/tsh/common/vnet_darwin.go b/tool/tsh/common/vnet_darwin.go index 20c1f1b55d141..289efe0b035a1 100644 --- a/tool/tsh/common/vnet_darwin.go +++ b/tool/tsh/common/vnet_darwin.go @@ -68,7 +68,6 @@ func (c *vnetAdminSetupCommand) run(cf *CLIConf) error { // This runs as root so we need to be configured with the user's home path. return trace.BadParameter("%s must be set", types.HomeEnvVar) } - config := daemon.Config{ SocketPath: c.socketPath, IPv6Prefix: c.ipv6Prefix, @@ -80,18 +79,7 @@ func (c *vnetAdminSetupCommand) run(cf *CLIConf) error { Euid: c.euid, }, } - - return trace.Wrap(vnet.RunAdminProcess(cf.Context, config)) -} - -// the vnet-install-service command is only supported on windows. -func newPlatformVnetInstallServiceCommand(app *kingpin.Application) vnetCommandNotSupported { - return vnetCommandNotSupported{} -} - -// the vnet-uninstall-service command is only supported on windows. -func newPlatformVnetUninstallServiceCommand(app *kingpin.Application) vnetCommandNotSupported { - return vnetCommandNotSupported{} + return trace.Wrap(vnet.RunDarwinAdminProcess(cf.Context, config)) } // the vnet-service command is only supported on windows. diff --git a/tool/tsh/common/vnet_other.go b/tool/tsh/common/vnet_other.go index 86e0ee764725b..d6ed0d03280c7 100644 --- a/tool/tsh/common/vnet_other.go +++ b/tool/tsh/common/vnet_other.go @@ -30,14 +30,6 @@ func newPlatformVnetAdminSetupCommand(app *kingpin.Application) vnetCLICommand { return vnetCommandNotSupported{} } -func newPlatformVnetInstallServiceCommand(app *kingpin.Application) vnetCLICommand { - return vnetCommandNotSupported{} -} - -func newPlatformVnetUninstallServiceCommand(app *kingpin.Application) vnetCLICommand { - return vnetCommandNotSupported{} -} - func newPlatformVnetServiceCommand(app *kingpin.Application) vnetCLICommand { return vnetCommandNotSupported{} } diff --git a/tool/tsh/common/vnet_windows.go b/tool/tsh/common/vnet_windows.go index 67aa8722fd2dd..bb34adeb19e84 100644 --- a/tool/tsh/common/vnet_windows.go +++ b/tool/tsh/common/vnet_windows.go @@ -20,41 +20,9 @@ import ( "github.com/alecthomas/kingpin/v2" "github.com/gravitational/trace" "golang.org/x/sys/windows/svc" -) - -var windowsServiceNotImplemented = &trace.NotImplementedError{Message: "VNet Windows service is not yet implemented"} - -type vnetInstallServiceCommand struct { - *kingpin.CmdClause -} - -func newPlatformVnetInstallServiceCommand(app *kingpin.Application) *vnetInstallServiceCommand { - cmd := &vnetInstallServiceCommand{ - CmdClause: app.Command("vnet-install-service", "Install the VNet Windows service.").Hidden(), - } - return cmd -} - -func (c *vnetInstallServiceCommand) run(cf *CLIConf) error { - // TODO(nklaassen): implement VNet Windows service installation. - return trace.Wrap(windowsServiceNotImplemented) -} -type vnetUninstallServiceCommand struct { - *kingpin.CmdClause -} - -func newPlatformVnetUninstallServiceCommand(app *kingpin.Application) *vnetUninstallServiceCommand { - cmd := &vnetUninstallServiceCommand{ - CmdClause: app.Command("vnet-uninstall-service", "Uninstall (delete) the VNet Windows service.").Hidden(), - } - return cmd -} - -func (c *vnetUninstallServiceCommand) run(cf *CLIConf) error { - // TODO(nklaassen): implement VNet Windows service uninstallation. - return trace.Wrap(windowsServiceNotImplemented) -} + "github.com/gravitational/teleport/lib/vnet" +) // vnetServiceCommand is the command that runs the Windows service. type vnetServiceCommand struct { @@ -63,17 +31,19 @@ type vnetServiceCommand struct { func newPlatformVnetServiceCommand(app *kingpin.Application) *vnetServiceCommand { cmd := &vnetServiceCommand{ - CmdClause: app.Command("vnet-service", "Start the VNet service.").Hidden(), + CmdClause: app.Command(vnet.ServiceCommand, "Start the VNet service.").Hidden(), } return cmd } func (c *vnetServiceCommand) run(_ *CLIConf) error { if !isWindowsService() { - return trace.Errorf("not running as a Windows service, cannot run vnet-service command") + return trace.Errorf("not running as a Windows service, cannot run %s command", vnet.ServiceCommand) + } + if err := vnet.ServiceMain(); err != nil { + return trace.Wrap(err, "running VNet Windows service") } - // TODO(nklaassen): implement VNet Windows service. - return trace.Wrap(windowsServiceNotImplemented) + return nil } func isWindowsService() bool { From f39d44413bbda20c8a91172433c85fb912cde914 Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Thu, 16 Jan 2025 12:17:45 -0800 Subject: [PATCH 2/3] address alan's comments --- lib/vnet/admin_process_darwin.go | 8 ++++---- lib/vnet/process_manager.go | 7 ++++++- lib/vnet/user_process.go | 22 ++++++++-------------- lib/vnet/user_process_darwin.go | 13 ++++++++----- lib/vnet/user_process_windows.go | 24 +++++++++--------------- 5 files changed, 35 insertions(+), 39 deletions(-) diff --git a/lib/vnet/admin_process_darwin.go b/lib/vnet/admin_process_darwin.go index da881ba761825..dd2f7ad5c761b 100644 --- a/lib/vnet/admin_process_darwin.go +++ b/lib/vnet/admin_process_darwin.go @@ -33,7 +33,7 @@ import ( // // It also handles host OS configuration that must run as root, and stays alive // to keep the host configuration up to date. It will stay running until the -// socket at config.socketPath is deleted, [ctx] is canceled, or until +// socket at config.socketPath is deleted, ctx is canceled, or until // encountering an unrecoverable error. func RunDarwinAdminProcess(ctx context.Context, config daemon.Config) error { if err := config.CheckAndSetDefaults(); err != nil { @@ -73,7 +73,7 @@ func RunDarwinAdminProcess(ctx context.Context, config daemon.Config) error { } // createAndSendTUNDevice creates a virtual network TUN device and sends the open file descriptor on -// [socketPath]. It returns the name of the TUN device or an error. +// socketPath. It returns the name of the TUN device or an error. func createAndSendTUNDevice(ctx context.Context, socketPath string) (string, error) { tun, tunName, err := createTUNDevice(ctx) if err != nil { @@ -106,7 +106,7 @@ func createTUNDevice(ctx context.Context) (tun.Device, string, error) { return dev, name, nil } -// osConfigurationLoop will keep running until [ctx] is canceled or an unrecoverable error is encountered, in +// osConfigurationLoop will keep running until ctx] is canceled or an unrecoverable error is encountered, in // order to keep the host OS configuration up to date. func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, homePath string, clientCred daemon.ClientCred) error { osConfigurator, err := newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath, clientCred) @@ -127,7 +127,7 @@ func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, home } defer func() { - // Shutting down, deconfigure OS. Pass context.Background because [ctx] has likely been canceled + // Shutting down, deconfigure OS. Pass context.Background because ctx has likely been canceled // already but we still need to clean up. if err := osConfigurator.deconfigureOS(context.Background()); err != nil { log.ErrorContext(ctx, "Error deconfiguring host OS before shutting down.", "error", err) diff --git a/lib/vnet/process_manager.go b/lib/vnet/process_manager.go index 66464cda63b98..08c7d35f6028e 100644 --- a/lib/vnet/process_manager.go +++ b/lib/vnet/process_manager.go @@ -18,6 +18,7 @@ package vnet import ( "context" + "errors" "fmt" "sync" @@ -70,7 +71,11 @@ func (pm *ProcessManager) Wait() error { select { case <-pm.closed: // Errors are expected after the process manager has been closed, - // usually due to context cancellation. + // usually due to context cancellation, but other error types may be + // returned. Log unexpected errors at debug level but return nil. + if err != nil && !errors.Is(err, context.Canceled) { + log.DebugContext(context.Background(), "ProcessManager exited with error after being closed", "error", err) + } return nil default: return trace.Wrap(err) diff --git a/lib/vnet/user_process.go b/lib/vnet/user_process.go index c4acaf41301b1..820c70504a753 100644 --- a/lib/vnet/user_process.go +++ b/lib/vnet/user_process.go @@ -49,24 +49,18 @@ func (c *UserProcessConfig) checkAndSetDefaults() error { } // RunUserProcess is called by all VNet client applications (tsh, Connect) to -// start and run all VNet tasks. +// start and run all VNet tasks. It returns a [ProcessManager] which controls +// the lifecycle of all tasks and background processes. // -// It returns a [ProcessManager] which controls the lifecycle of all tasks and -// background processes. The caller is expected to call Close on the process -// manager to clean up any resources, terminate all processes, and remove and OS -// configuration used for actively running VNet. -// -// ctx is used to wait for setup steps that happen before RunUserProcess hands out the -// control to the process manager. If ctx gets canceled during RunUserProcess, the process -// manager gets closed along with its background tasks. +// ctx is used for setup steps that happen before RunUserProcess passes control +// to the process manager. Canceling ctx after RunUserProcess returns will _not_ +// cancel the background tasks. If [RunUserProcess] returns without error, the +// caller is expected to call Close on the process manager to clean up any +// resources, terminate all processes, and remove any OS configuration used for +// actively running VNet. func RunUserProcess(ctx context.Context, cfg *UserProcessConfig) (pm *ProcessManager, err error) { if err := cfg.checkAndSetDefaults(); err != nil { return nil, trace.Wrap(err) } - defer func() { - if pm != nil && err != nil { - pm.Close() - } - }() return runPlatformUserProcess(ctx, cfg) } diff --git a/lib/vnet/user_process_darwin.go b/lib/vnet/user_process_darwin.go index 530df4dc1c986..a1b07da1b38ff 100644 --- a/lib/vnet/user_process_darwin.go +++ b/lib/vnet/user_process_darwin.go @@ -31,11 +31,14 @@ import ( // background. To do this, it also needs to launch an admin process in the // background. It returns a [ProcessManager] which controls the lifecycle of // both background tasks. -// -// The caller is expected to call Close on the process manager to close the -// network stack, clean up any resources used by it and terminate the admin -// process. -func runPlatformUserProcess(ctx context.Context, config *UserProcessConfig) (*ProcessManager, error) { +func runPlatformUserProcess(ctx context.Context, config *UserProcessConfig) (pm *ProcessManager, err error) { + // Make sure to close the process manager if returning a non-nil error. + defer func() { + if pm != nil && err != nil { + pm.Close() + } + }() + ipv6Prefix, err := NewIPv6Prefix() if err != nil { return nil, trace.Wrap(err) diff --git a/lib/vnet/user_process_windows.go b/lib/vnet/user_process_windows.go index bbcbcb0083706..9fccd0bb528b3 100644 --- a/lib/vnet/user_process_windows.go +++ b/lib/vnet/user_process_windows.go @@ -25,22 +25,16 @@ import ( // runPlatformUserProcess launches a Windows service in the background that will // handle all networking and OS configuration. The user process exposes a gRPC // interface that the admin process uses to query application names and get user -// certificates for apps. -// -// RunUserProcess returns a [ProcessManager] which controls the lifecycle of -// both the user and admin processes. -// -// The caller is expected to call Close on the process manager to clean up any -// resources and terminate the admin process, which will in turn stop the -// networking stack and deconfigure the host OS. -// -// ctx is used to wait for setup steps that happen before RunUserProcess hands out the -// control to the process manager. If ctx gets canceled during RunUserProcess, the process -// manager gets closed along with its background tasks. +// certificates for apps. It returns a [ProcessManager] which controls the +// lifecycle of both the user and admin processes. func runPlatformUserProcess(ctx context.Context, config *UserProcessConfig) (pm *ProcessManager, err error) { - if err := config.checkAndSetDefaults(); err != nil { - return nil, trace.Wrap(err) - } + // Make sure to close the process manager if returning a non-nil error. + defer func() { + if pm != nil && err != nil { + pm.Close() + } + }() + pm, processCtx := newProcessManager() pm.AddCriticalBackgroundTask("VNet Windows service", func() error { return trace.Wrap(runService(processCtx), "running VNet Windows service in the background") From 10e1c96ee6250385eb3d6bcc939089379c6d70c4 Mon Sep 17 00:00:00 2001 From: Nic Klaassen Date: Thu, 16 Jan 2025 13:42:35 -0800 Subject: [PATCH 3/3] fix typo in comment --- lib/vnet/admin_process_darwin.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/vnet/admin_process_darwin.go b/lib/vnet/admin_process_darwin.go index dd2f7ad5c761b..f9ee788327842 100644 --- a/lib/vnet/admin_process_darwin.go +++ b/lib/vnet/admin_process_darwin.go @@ -106,7 +106,7 @@ func createTUNDevice(ctx context.Context) (tun.Device, string, error) { return dev, name, nil } -// osConfigurationLoop will keep running until ctx] is canceled or an unrecoverable error is encountered, in +// osConfigurationLoop will keep running until ctx is canceled or an unrecoverable error is encountered, in // order to keep the host OS configuration up to date. func osConfigurationLoop(ctx context.Context, tunName, ipv6Prefix, dnsAddr, homePath string, clientCred daemon.ClientCred) error { osConfigurator, err := newOSConfigurator(tunName, ipv6Prefix, dnsAddr, homePath, clientCred)