diff --git a/README.md b/README.md index 69eedcf..85c878b 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,19 @@ # Orchard +> [!IMPORTANT] +> +> **macOS 15 (Sequoia)** +> +> The [newly introduced "Local Network" permission](https://developer.apple.com/documentation/technotes/tn3179-understanding-local-network-privacy) in macOS Sequoia requires accepting a GUI pop-up on each host machine that runs the Orchard Worker. +> +> To work around this, upgrade your workers to Orchard 0.32.0 or newer and invoke the `orchard worker run` as `root` with an additional `--user` command-line argument, which takes a name of your regular, non-privileged user on the host machine. +> +> This will cause the Orchard Worker to start a small `orchard localnetworkhelper` process in the background and then drop the privileges to the specified user. +> +>The helper process is privileged and needed to establish network connections on behalf of the Orchard Worker without triggering a GUI pop-up. +> +>This approach is more secure than simply running `orchard worker run` as `root`, because only a small part of Orchard Worker runs privileged and the only functionality that this part has is establishing new connections. + Orchard is an orchestration system for [Tart](https://github.com/cirruslabs/tart). Create a cluster of bare-metal Apple Silicon machines and manage dozens of VMs with ease! diff --git a/go.mod b/go.mod index 4340adb..1df850e 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,13 @@ module github.com/cirruslabs/orchard -go 1.23.3 -toolchain go1.24.1 +go 1.24 + +toolchain go1.24.2 require ( github.com/avast/retry-go v3.0.0+incompatible github.com/avast/retry-go/v4 v4.6.1 + github.com/cirruslabs/chacha v0.10.0 github.com/coder/websocket v1.8.13 github.com/deckarep/golang-set/v2 v2.8.0 github.com/dgraph-io/badger/v3 v3.2103.5 @@ -37,7 +39,7 @@ require ( go.opentelemetry.io/otel/sdk/metric v1.35.0 go.uber.org/zap v1.27.0 golang.org/x/crypto v0.36.0 - golang.org/x/exp v0.0.0-20230321023759-10a507213a29 + golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa golang.org/x/net v0.38.0 golang.org/x/term v0.30.0 google.golang.org/grpc v1.71.1 @@ -62,7 +64,7 @@ require ( github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e // indirect github.com/cloudwego/base64x v0.1.5 // indirect github.com/containerd/console v1.0.4 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/dgraph-io/ristretto v0.1.1 // indirect github.com/fatih/color v1.13.0 // indirect github.com/gabriel-vasile/mimetype v1.4.8 // indirect @@ -107,10 +109,11 @@ require ( github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/oklog/ulid v1.3.1 // indirect github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.62.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect + github.com/puzpuzpuz/xsync/v4 v4.0.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/spf13/pflag v1.0.6 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect diff --git a/go.sum b/go.sum index 63e552d..2716c20 100644 --- a/go.sum +++ b/go.sum @@ -50,6 +50,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cirruslabs/chacha v0.10.0 h1:Ny/U7sVFkB3DAZM0PAvbkEX4/IN0bUI97XlQGx4kzzA= +github.com/cirruslabs/chacha v0.10.0/go.mod h1:qbGp1psGAO8KfN4ZkXSHiTWkyZh9az5DUmLyP1ihe3g= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= @@ -66,8 +68,9 @@ github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/deckarep/golang-set/v2 v2.8.0 h1:swm0rlPCmdWn9mESxKOjWk8hXSqoxOp+ZlfuyaAdFlQ= github.com/deckarep/golang-set/v2 v2.8.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= github.com/dgraph-io/badger/v3 v3.2103.5 h1:ylPa6qzbjYRQMU6jokoj4wzcaweHylt//CH0AKt0akg= @@ -240,8 +243,9 @@ github.com/penglongli/gin-metrics v0.1.13 h1:a1wyrXcbUVxL5w4c2TSv+9kyQA9qM1o23h0 github.com/penglongli/gin-metrics v0.1.13/go.mod h1:VEmSyx/9TwUG50IsPCgjMKOUuGO74V2lmkLZ6x1Dlko= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -260,6 +264,8 @@ github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5b github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/puzpuzpuz/xsync/v4 v4.0.0 h1:F1za+MBXzDQtQq+OVgFsojSX4w66rsNDmQNebPFAncA= +github.com/puzpuzpuz/xsync/v4 v4.0.0/go.mod h1:VJDmTCJMBt8igNxnkQd86r+8KUeN1quSfNKu5bLYFQo= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= @@ -354,8 +360,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug= -golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa h1:t2QcU6V556bFjYgu4L6C+6VrCPyJZ+eyRsABUPs1mz4= +golang.org/x/exp v0.0.0-20250218142911-aa4b98e5adaa/go.mod h1:BHOTPb3L19zxehTsLoJXVaTktb06DFgmdW6Wb9s8jqk= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= diff --git a/internal/command/dev/dev.go b/internal/command/dev/dev.go index 5fdcb9f..c8a7026 100644 --- a/internal/command/dev/dev.go +++ b/internal/command/dev/dev.go @@ -1,3 +1,5 @@ +//go:build unix + package dev import ( diff --git a/internal/command/dev/dev_unsupported.go b/internal/command/dev/dev_unsupported.go new file mode 100644 index 0000000..5ce6acf --- /dev/null +++ b/internal/command/dev/dev_unsupported.go @@ -0,0 +1,9 @@ +//go:build !unix + +package dev + +import "github.com/spf13/cobra" + +func NewCommand() *cobra.Command { + return nil +} diff --git a/internal/command/localnetworkhelper/localnetworkhelper.go b/internal/command/localnetworkhelper/localnetworkhelper.go new file mode 100644 index 0000000..a81aede --- /dev/null +++ b/internal/command/localnetworkhelper/localnetworkhelper.go @@ -0,0 +1,31 @@ +//go:build unix + +package localnetworkhelper + +import ( + "github.com/cirruslabs/chacha/pkg/localnetworkhelper" + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: localnetworkhelper.CommandName, + Short: "Run the macOS \"Local Network\" permission helper process", + Hidden: true, + RunE: func(cmd *cobra.Command, args []string) error { + // In localnetworkhelper.New(), a macOS "Local Network" + // permission helper process is spawned and receives + // its socketpair(2) end via ExtraFiles field of Golang's + // exec.Cmd[1]. + // + // This socketpair(2) end becomes FD number 3 here, + // according to the ExtraFiles documentation[1]: + // + // >If non-nil, entry i becomes file descriptor 3+i. + // + // [1]: https://pkg.go.dev/os/exec#Cmd + return localnetworkhelper.Serve(3) + }, + } + return cmd +} diff --git a/internal/command/localnetworkhelper/localnetworkhelper_unsupported.go b/internal/command/localnetworkhelper/localnetworkhelper_unsupported.go new file mode 100644 index 0000000..e2f143b --- /dev/null +++ b/internal/command/localnetworkhelper/localnetworkhelper_unsupported.go @@ -0,0 +1,11 @@ +//go:build !unix + +package localnetworkhelper + +import ( + "github.com/spf13/cobra" +) + +func NewCommand() *cobra.Command { + return nil +} diff --git a/internal/command/root.go b/internal/command/root.go index 35c81f0..618bc5a 100644 --- a/internal/command/root.go +++ b/internal/command/root.go @@ -8,6 +8,7 @@ import ( "github.com/cirruslabs/orchard/internal/command/dev" "github.com/cirruslabs/orchard/internal/command/get" "github.com/cirruslabs/orchard/internal/command/list" + "github.com/cirruslabs/orchard/internal/command/localnetworkhelper" "github.com/cirruslabs/orchard/internal/command/logs" "github.com/cirruslabs/orchard/internal/command/pause" "github.com/cirruslabs/orchard/internal/command/portforward" @@ -37,6 +38,10 @@ func NewRootCmd() *cobra.Command { }, } + if localNetworkHelperCommand := localnetworkhelper.NewCommand(); localNetworkHelperCommand != nil { + command.AddCommand(localNetworkHelperCommand) + } + addGroupedCommands(command, "Working With Resources:", create.NewCommand(), deletepkg.NewCommand(), @@ -51,12 +56,20 @@ func NewRootCmd() *cobra.Command { vnc.NewCommand(), ) - addGroupedCommands(command, "Administrative Tasks:", + administrativeCommands := []*cobra.Command{ context.NewCommand(), controller.NewCommand(), - worker.NewCommand(), - dev.NewCommand(), - ) + } + + if devCommand := dev.NewCommand(); devCommand != nil { + administrativeCommands = append(administrativeCommands, devCommand) + } + + if workerCommand := worker.NewCommand(); workerCommand != nil { + administrativeCommands = append(administrativeCommands, workerCommand) + } + + addGroupedCommands(command, "Administrative Tasks:", administrativeCommands...) return command } diff --git a/internal/command/worker/run.go b/internal/command/worker/run.go index d5f30bb..4b6822e 100644 --- a/internal/command/worker/run.go +++ b/internal/command/worker/run.go @@ -1,8 +1,12 @@ +//go:build unix + package worker import ( "errors" "fmt" + "github.com/cirruslabs/chacha/pkg/localnetworkhelper" + "github.com/cirruslabs/chacha/pkg/privdrop" "github.com/cirruslabs/orchard/internal/bootstraptoken" "github.com/cirruslabs/orchard/internal/netconstants" "github.com/cirruslabs/orchard/internal/worker" @@ -32,6 +36,7 @@ var labels map[string]string var noPKI bool var defaultCPU uint64 var defaultMemory uint64 +var username string var debug bool func newRunCommand() *cobra.Command { @@ -62,17 +67,46 @@ func newRunCommand() *cobra.Command { "that do not explicitly specify a value") cmd.PersistentFlags().Uint64Var(&defaultMemory, "default-memory", 8*1024, "megabytes of memory "+ "to use for VMs that do not explicitly specify a value") + cmd.Flags().StringVar(&username, "user", "", "username to drop privileges to "+ + "(\"Local Network\" permission workaround: requires starting \"orchard worker run\" as \"root\", "+ + "the privileges will be then dropped to the specified user after starting the \"orchard localnetworkhelper\" "+ + "helper process)") cmd.PersistentFlags().BoolVar(&debug, "debug", false, "enable debug logging") return cmd } func runWorker(cmd *cobra.Command, args []string) (err error) { + var clientOpts []client.Option + + workerOpts := []worker.Option{ + worker.WithName(name), + worker.WithLabels(labels), + worker.WithDefaultCPUAndMemory(defaultCPU, defaultMemory), + } + + // Run the macOS "Local Network" permission helper + // when privilege dropping is requested + if username != "" { + localNetworkHelper, err := localnetworkhelper.New(cmd.Context()) + if err != nil { + return err + } + + clientOpts = append(clientOpts, client.WithDialContext(localNetworkHelper.PrivilegedDialContext)) + workerOpts = append(workerOpts, worker.WithLocalNetworkHelper(localNetworkHelper)) + + if err := privdrop.Drop(username); err != nil { + return err + } + } + // Parse controller URL controllerURL, err := netconstants.NormalizeAddress(args[0]) if err != nil { return err } + clientOpts = append(clientOpts, client.WithAddress(controllerURL.String())) // Parse bootstrap token bootstrapTokenRaw, err := readBootstrapToken() @@ -86,17 +120,15 @@ func runWorker(cmd *cobra.Command, args []string) (err error) { if err != nil { return err } + clientOpts = append(clientOpts, client.WithCredentials(bootstrapToken.ServiceAccountName(), + bootstrapToken.ServiceAccountToken())) // Convert resources resources, err := v1.NewResourcesFromStringToString(stringToStringResources) if err != nil { return fmt.Errorf("%w: %v", ErrRunFailed, err) } - - clientOpts := []client.Option{ - client.WithAddress(controllerURL.String()), - client.WithCredentials(bootstrapToken.ServiceAccountName(), bootstrapToken.ServiceAccountToken()), - } + workerOpts = append(workerOpts, worker.WithResources(resources)) if trustedCertificate := bootstrapToken.Certificate(); trustedCertificate != nil { clientOpts = append(clientOpts, client.WithTrustedCertificate(trustedCertificate)) @@ -120,14 +152,11 @@ func runWorker(cmd *cobra.Command, args []string) (err error) { err = syncErr } }() + workerOpts = append(workerOpts, worker.WithLogger(logger)) workerInstance, err := worker.New( controllerClient, - worker.WithName(name), - worker.WithResources(resources), - worker.WithLabels(labels), - worker.WithDefaultCPUAndMemory(defaultCPU, defaultMemory), - worker.WithLogger(logger), + workerOpts..., ) if err != nil { return err diff --git a/internal/command/worker/worker.go b/internal/command/worker/worker.go index 0870ca4..d1221a3 100644 --- a/internal/command/worker/worker.go +++ b/internal/command/worker/worker.go @@ -1,3 +1,5 @@ +//go:build unix + package worker import ( diff --git a/internal/command/worker/worker_unsupported.go b/internal/command/worker/worker_unsupported.go new file mode 100644 index 0000000..f1286ae --- /dev/null +++ b/internal/command/worker/worker_unsupported.go @@ -0,0 +1,9 @@ +//go:build !unix + +package worker + +import "github.com/spf13/cobra" + +func NewCommand() *cobra.Command { + return nil +} diff --git a/internal/worker/option.go b/internal/worker/option.go index aa0a170..83697bc 100644 --- a/internal/worker/option.go +++ b/internal/worker/option.go @@ -1,6 +1,7 @@ package worker import ( + "github.com/cirruslabs/chacha/pkg/localnetworkhelper" v1 "github.com/cirruslabs/orchard/pkg/resource/v1" "go.uber.org/zap" ) @@ -32,6 +33,12 @@ func WithDefaultCPUAndMemory(defaultCPU uint64, defaultMemory uint64) Option { } } +func WithLocalNetworkHelper(localNetworkHelper *localnetworkhelper.LocalNetworkHelper) Option { + return func(worker *Worker) { + worker.localNetworkHelper = localNetworkHelper + } +} + func WithLogger(logger *zap.Logger) Option { return func(worker *Worker) { worker.logger = logger.Sugar() diff --git a/internal/worker/vmmanager/vm.go b/internal/worker/vmmanager/vm.go index 8e0d2cf..03b7579 100644 --- a/internal/worker/vmmanager/vm.go +++ b/internal/worker/vmmanager/vm.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "github.com/avast/retry-go" + "github.com/cirruslabs/chacha/pkg/localnetworkhelper" "github.com/cirruslabs/orchard/internal/worker/ondiskname" "github.com/cirruslabs/orchard/internal/worker/tart" "github.com/cirruslabs/orchard/pkg/client" @@ -51,12 +52,15 @@ type VM struct { cancel context.CancelFunc wg *sync.WaitGroup + + localNetworkHelper *localnetworkhelper.LocalNetworkHelper } func NewVM( vmResource v1.VM, eventStreamer *client.EventStreamer, vmPullTimeHistogram metric.Float64Histogram, + localNetworkHelper *localnetworkhelper.LocalNetworkHelper, logger *zap.SugaredLogger, ) *VM { vmContext, vmContextCancel := context.WithCancel(context.Background()) @@ -74,6 +78,8 @@ func NewVM( cancel: vmContextCancel, wg: &sync.WaitGroup{}, + + localNetworkHelper: localNetworkHelper, } vm.wg.Add(1) @@ -429,11 +435,18 @@ func (vm *VM) shell( addr := ip + ":22" - dialer := net.Dialer{ - Timeout: 5 * time.Second, - } + dialCtx, dialCtxCancel := context.WithTimeout(ctx, 5*time.Second) + defer dialCtxCancel() - netConn, err := dialer.DialContext(ctx, "tcp", addr) + var netConn net.Conn + + if vm.localNetworkHelper != nil { + netConn, err = vm.localNetworkHelper.PrivilegedDialContext(dialCtx, "tcp", addr) + } else { + dialer := net.Dialer{} + + netConn, err = dialer.DialContext(dialCtx, "tcp", addr) + } if err != nil { return fmt.Errorf("failed to dial %s: %w", addr, err) } diff --git a/internal/worker/worker.go b/internal/worker/worker.go index 89c1090..5d40026 100644 --- a/internal/worker/worker.go +++ b/internal/worker/worker.go @@ -5,6 +5,7 @@ import ( "errors" "fmt" "github.com/avast/retry-go/v4" + "github.com/cirruslabs/chacha/pkg/localnetworkhelper" "github.com/cirruslabs/orchard/internal/opentelemetry" "github.com/cirruslabs/orchard/internal/worker/dhcpleasetime" "github.com/cirruslabs/orchard/internal/worker/iokitregistry" @@ -40,6 +41,8 @@ type Worker struct { vmPullTimeHistogram metric.Float64Histogram + localNetworkHelper *localnetworkhelper.LocalNetworkHelper + logger *zap.SugaredLogger } @@ -409,7 +412,8 @@ func (worker *Worker) deleteVM(vm *vmmanager.VM) error { func (worker *Worker) createVM(odn ondiskname.OnDiskName, vmResource v1.VM) { eventStreamer := worker.client.VMs().StreamEvents(vmResource.Name) - vm := vmmanager.NewVM(vmResource, eventStreamer, worker.vmPullTimeHistogram, worker.logger) + vm := vmmanager.NewVM(vmResource, eventStreamer, worker.vmPullTimeHistogram, + worker.localNetworkHelper, worker.logger) worker.vmm.Put(odn, vm) } diff --git a/pkg/client/client.go b/pkg/client/client.go index 6e825a0..6406d68 100644 --- a/pkg/client/client.go +++ b/pkg/client/client.go @@ -40,6 +40,8 @@ type Client struct { serviceAccountName string serviceAccountToken string + + dialContext func(ctx context.Context, network, addr string) (net.Conn, error) } type Config struct { @@ -77,15 +79,21 @@ func New(opts ...Option) (*Client, error) { } // Instantiate the HTTP client + transport := &http.Transport{ + TLSClientConfig: client.tlsConfig, + } + + if client.dialContext != nil { + transport.DialContext = client.dialContext + } + client.httpClient = &http.Client{ // The default is zero, which means no timeout, which means that // the requests may hang indefinitely. See [1] for more details. // // [1]: https://github.com/cirruslabs/orchard/issues/152#issuecomment-1927091747 - Timeout: 30 * time.Second, - Transport: &http.Transport{ - TLSClientConfig: client.tlsConfig, - }, + Timeout: 30 * time.Second, + Transport: transport, } url, err := url.Parse(client.address) diff --git a/pkg/client/option.go b/pkg/client/option.go index b5c291c..cd5c203 100644 --- a/pkg/client/option.go +++ b/pkg/client/option.go @@ -1,7 +1,9 @@ package client import ( + "context" "crypto/x509" + "net" ) type Option func(*Client) @@ -24,3 +26,9 @@ func WithCredentials(serviceAccountName string, serviceAccountToken string) Opti client.serviceAccountToken = serviceAccountToken } } + +func WithDialContext(dialContext func(ctx context.Context, network, addr string) (net.Conn, error)) Option { + return func(client *Client) { + client.dialContext = dialContext + } +}