diff --git a/.changelog/25496.txt b/.changelog/25496.txt new file mode 100644 index 000000000..4f2d25c3d --- /dev/null +++ b/.changelog/25496.txt @@ -0,0 +1,3 @@ +```release-note:improvement +rawexec: add support for setting the task user on windows platform +``` diff --git a/drivers/shared/executor/executor_basic.go b/drivers/shared/executor/executor_basic.go index b22183cf4..72f1e21dd 100644 --- a/drivers/shared/executor/executor_basic.go +++ b/drivers/shared/executor/executor_basic.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:build !linux +//go:build !linux && !windows package executor diff --git a/drivers/shared/executor/executor_windows.go b/drivers/shared/executor/executor_windows.go index 25134ece5..9523c0536 100644 --- a/drivers/shared/executor/executor_windows.go +++ b/drivers/shared/executor/executor_windows.go @@ -6,14 +6,108 @@ package executor import ( + "errors" "fmt" "os" + "os/exec" + "runtime" + "strings" "syscall" "unsafe" + hclog "github.com/hashicorp/go-hclog" + "github.com/hashicorp/go-set/v3" + "github.com/hashicorp/nomad/client/lib/cpustats" + "github.com/hashicorp/nomad/drivers/shared/executor/procstats" + "github.com/hashicorp/nomad/plugins/drivers" "golang.org/x/sys/windows" ) +func NewExecutorWithIsolation(logger hclog.Logger, compute cpustats.Compute) Executor { + logger = logger.Named("executor") + logger.Error("isolation executor is not supported on this platform, using default") + return NewExecutor(logger, compute) +} + +func (e *UniversalExecutor) configureResourceContainer(_ *ExecCommand, _ int) (func() error, func(), error) { + cleanup := func() {} + running := func() error { return nil } + return running, cleanup, nil +} + +func (e *UniversalExecutor) start(command *ExecCommand) error { + return e.childCmd.Start() +} + +func withNetworkIsolation(f func() error, _ *drivers.NetworkIsolationSpec) error { + return f() +} + +func setCmdUser(cmd *exec.Cmd, user string) error { + nameParts := strings.Split(user, "\\") + if len(nameParts) != 2 { + return errors.New("user name must contain domain") + } + token, err := createUserToken(nameParts[0], nameParts[1]) + if err != nil { + return fmt.Errorf("failed to create user token: %w", err) + } + + if cmd.SysProcAttr == nil { + cmd.SysProcAttr = &syscall.SysProcAttr{} + } + cmd.SysProcAttr.Token = *token + + runtime.AddCleanup(cmd, func(attr *syscall.SysProcAttr) { + _ = attr.Token.Close() + }, cmd.SysProcAttr) + + return nil +} + +var ( + advapiDll = windows.NewLazySystemDLL("advapi32.dll") + procLogonUserW = advapiDll.NewProc("LogonUserW") +) + +const ( + _LOGON_SERVICE uint32 = 5 + _PROVIDER_DEFAULT uint32 = 0 +) + +func createUserToken(domain, username string) (*syscall.Token, error) { + userw, err := syscall.UTF16PtrFromString(username) + if err != nil { + return nil, fmt.Errorf("failed to convert username to UTF-16: %w", err) + } + domainw, err := syscall.UTF16PtrFromString(domain) + if err != nil { + return nil, fmt.Errorf("failed to convert user domain to UTF-16: %w", err) + } + var token syscall.Token + ret, _, e := procLogonUserW.Call( + uintptr(unsafe.Pointer(userw)), + uintptr(unsafe.Pointer(domainw)), + uintptr(unsafe.Pointer(nil)), + uintptr(_LOGON_SERVICE), + uintptr(_PROVIDER_DEFAULT), + uintptr(unsafe.Pointer(&token)), + ) + if ret == 0 { + return nil, e + } + + return &token, nil +} + +func (e *UniversalExecutor) ListProcesses() set.Collection[int] { + return procstats.ListByPid(e.childCmd.Process.Pid) +} + +func (e *UniversalExecutor) setSubCmdCgroup(*exec.Cmd, string) (func(), error) { + return func() {}, nil +} + // configure new process group for child process and creates a JobObject for the // executor. Children of the executor will be created in the same JobObject // Ref: https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects diff --git a/website/content/docs/drivers/raw_exec.mdx b/website/content/docs/drivers/raw_exec.mdx index 73d4cd76c..a4edc359f 100644 --- a/website/content/docs/drivers/raw_exec.mdx +++ b/website/content/docs/drivers/raw_exec.mdx @@ -47,8 +47,11 @@ The `raw_exec` driver supports the following configuration in the job spec: conflict with other Nomad driver's cgroups and have unintended side effects. -~> The `task.user` field cannot be set on a Task using the `raw_exec` driver if -the Nomad client has been hardened according to the [production][hardening] guide. +~> On Linux, you cannot set the `task.user` field on a task using the `raw_exec` +driver if you have hardened the Nomad client according to the +[production][hardening] guide. On Windows, when Nomad is running as a [system +service][service], you may specify a less-privileged service user. For example, +`NT AUTHORITY\LocalService`, `NT AUTHORITY\NetworkService`. - `oom_score_adj` - (Optional) A positive integer to indicate the likelihood of the task being OOM killed (valid only for Linux). Defaults to 0. @@ -208,5 +211,6 @@ resources { ``` [hardening]: /nomad/docs/install/production/requirements#user-permissions +[service]: /nomad/docs/install/windows-service [plugin-options]: #plugin-options [plugin-block]: /nomad/docs/configuration/plugin diff --git a/website/content/docs/job-specification/task.mdx b/website/content/docs/job-specification/task.mdx index 8b634940a..8f3f238c7 100644 --- a/website/content/docs/job-specification/task.mdx +++ b/website/content/docs/job-specification/task.mdx @@ -110,6 +110,9 @@ job "docs" { [Docker][] images specify their own default users. This can only be set on Linux platforms, and clients can restrict [which drivers][user_drivers] are allowed to run tasks as [certain users][user_denylist]. + On Windows, when Nomad is running as a [system service][service] for the + [`raw_exec`][raw_exec] driver, you may specify a less-privileged service user. + For example, `NT AUTHORITY\LocalService`, `NT AUTHORITY\NetworkService`. - `template` ([Template][]: nil) - Specifies the set of templates to render for the task. Templates can be used to inject both static and @@ -222,6 +225,7 @@ task "server" { [vault]: /nomad/docs/job-specification/vault 'Nomad vault Job Specification' [volumemount]: /nomad/docs/job-specification/volume_mount 'Nomad volume_mount Job Specification' [exec]: /nomad/docs/drivers/exec 'Nomad exec Driver' +[raw_exec]: /nomad/docs/drivers/raw_exec 'Nomad raw_exec Driver' [java]: /nomad/docs/drivers/java 'Nomad Java Driver' [docker]: /nomad/docs/drivers/docker 'Nomad Docker Driver' [rkt]: /nomad/plugins/drivers/community/rkt 'Nomad rkt Driver' @@ -232,3 +236,4 @@ task "server" { [max_kill]: /nomad/docs/configuration/client#max_kill_timeout [kill_signal]: /nomad/docs/job-specification/task#kill_signal [Workload Identity]: /nomad/docs/concepts/workload-identity 'Nomad Workload Identity' +[service]: /nomad/docs/install/windows-service