Skip to content

raw_exec windows: add support for setting the task user #25641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions drivers/shared/executor/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,7 @@ func (e *UniversalExecutor) Launch(command *ExecCommand) (*ProcessState, error)
// setting the user of the process
if command.User != "" {
e.logger.Debug("running command as user", "user", command.User)
if err := setCmdUser(&e.childCmd, command.User); err != nil {
if err := setCmdUser(e.logger, &e.childCmd, command.User); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -527,7 +527,7 @@ func (e *UniversalExecutor) ExecStreaming(ctx context.Context, command []string,
},
processStart: func() error {
if u := e.command.User; u != "" {
if err := setCmdUser(cmd, u); err != nil {
if err := setCmdUser(e.logger, cmd, u); err != nil {
return err
}
}
Expand Down
2 changes: 1 addition & 1 deletion drivers/shared/executor/executor_basic.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ func withNetworkIsolation(f func() error, _ *drivers.NetworkIsolationSpec) error
return f()
}

func setCmdUser(*exec.Cmd, string) error { return nil }
func setCmdUser(hclog.Logger, *exec.Cmd, string) error { return nil }

func (e *UniversalExecutor) ListProcesses() set.Collection[int] {
return procstats.ListByPid(e.childCmd.Process.Pid)
Expand Down
3 changes: 2 additions & 1 deletion drivers/shared/executor/executor_universal_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strconv"
"syscall"

"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-set/v3"
"github.com/hashicorp/nomad/client/lib/cgroupslib"
"github.com/hashicorp/nomad/client/lib/nsutil"
Expand All @@ -30,7 +31,7 @@ const (

// setCmdUser takes a user id as a string and looks up the user, and sets the command
// to execute as that user.
func setCmdUser(cmd *exec.Cmd, userid string) error {
func setCmdUser(logger hclog.Logger, cmd *exec.Cmd, userid string) error {
u, err := users.Lookup(userid)
if err != nil {
return fmt.Errorf("failed to identify user %v: %v", userid, err)
Expand Down
69 changes: 60 additions & 9 deletions drivers/shared/executor/executor_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
package executor

import (
"errors"
"fmt"
"os"
"os/exec"
Expand All @@ -19,6 +18,7 @@ import (
"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/drivers/shared/executor/s4u"
"github.com/hashicorp/nomad/plugins/drivers"
"golang.org/x/sys/windows"
)
Expand All @@ -43,20 +43,16 @@ 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])
func setCmdUser(logger hclog.Logger, cmd *exec.Cmd, user string) error {
token, err := createUserToken(logger, user)
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
cmd.SysProcAttr.Token = token

runtime.AddCleanup(cmd, func(attr *syscall.SysProcAttr) {
_ = attr.Token.Close()
Expand All @@ -75,7 +71,62 @@ const (
_PROVIDER_DEFAULT uint32 = 0
)

func createUserToken(domain, username string) (*syscall.Token, error) {
// username can be of the form "domain\username", ".\username" or "username@domain"
func createUserToken(logger hclog.Logger, username string) (syscall.Token, error) {
var token windows.Token
var err error

var runAsUpn string
if strings.IndexByte(username, '\\') != -1 {
runAsUpn, err = convertUserToUpn(username)
if err != nil {
return 0, fmt.Errorf("failed to convert username %q to UPN : %w", username, err)
}
} else if strings.IndexByte(username, '@') != -1 {
runAsUpn = username
}

logger.Debug("creating user token", "username", username, "runAsUpn", runAsUpn)

if runAsUpn != "" {
token, err = s4u.GetDomainS4uToken(runAsUpn)
} else {
token, err = s4u.GetLocalS4uToken(username)
}
if err != nil {
return 0, fmt.Errorf("failed to create S4U token for user : %w", err)
}

return syscall.Token(token), nil
}

func convertUserToUpn(username string) (string, error) {
usernameUtf16, err := windows.UTF16FromString(username)
if err != nil {
return "", fmt.Errorf("error converting username to UTF16 : %w", err)
}

upnUtf16, err := translateSamToUpn(usernameUtf16)
if err != nil {
return "", err
}

return windows.UTF16ToString(upnUtf16), nil
}

const MAX_UPN_LEN = 1024

func translateSamToUpn(samAccountNameUtf16 []uint16) ([]uint16, error) {
var domainUpnLen uint32 = MAX_UPN_LEN + 1
domainUpn := make([]uint16, domainUpnLen)
err := windows.TranslateName(&samAccountNameUtf16[0], windows.NameSamCompatible, windows.NameUserPrincipal, &domainUpn[0], &domainUpnLen)
if err != nil {
return nil, err
}
return domainUpn[:domainUpnLen-1], nil
}

func createUserTokenOld(username, domain 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)
Expand Down
203 changes: 203 additions & 0 deletions drivers/shared/executor/s4u/lsa.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

//go:build windows

package s4u

import (
"log"
"syscall"
"unsafe"

"golang.org/x/sys/windows"
)

var (
modsecur32 = windows.NewLazySystemDLL("secur32.dll")

procLsaRegisterLogonProcess = modsecur32.NewProc("LsaRegisterLogonProcess")
procLsaDeregisterLogonProcess = modsecur32.NewProc("LsaDeregisterLogonProcess")
procLsaLookupAuthenticationPackage = modsecur32.NewProc("LsaLookupAuthenticationPackage")
procLsaLogonUser = modsecur32.NewProc("LsaLogonUser")
procLsaFreeReturnBuffer = modsecur32.NewProc("LsaFreeReturnBuffer")
)

type LSA_STRING struct {
Length uint16
MaximumLength uint16
Buffer *byte
}

type LSA_UNICODE_STRING struct {
Length uint16
MaximumLength uint16
Buffer *uint16
}

func mustStringToLsaString(s string) *LSA_STRING {
var result LSA_STRING
var err error
result.Length = uint16(len(s))
result.MaximumLength = uint16(len(s))
result.Buffer, err = windows.BytePtrFromString(s)
if err != nil {
log.Fatal(err)
}
return &result
}

func LsaRegisterLogonProcess(logonProcessName *LSA_STRING, handle *windows.Handle) error {
var mode uint32
status, _, _ := syscall.SyscallN(
procLsaRegisterLogonProcess.Addr(),
uintptr(unsafe.Pointer(logonProcessName)),
uintptr(unsafe.Pointer(handle)),
uintptr(unsafe.Pointer(&mode)))

if status != 0 {
return windows.NTStatus(status)
}
return nil
}

func LsaDeregisterLogonProcess(hnd windows.Handle) error {
status, _, _ := syscall.SyscallN(procLsaDeregisterLogonProcess.Addr(), uintptr(hnd))
if status != 0 {
return windows.NTStatus(status)
}
return nil
}

func LsaLookupAuthenticationPackage(hnd windows.Handle, packageName *LSA_STRING, authPackageId *uint32) error {
status, _, _ := syscall.SyscallN(
procLsaLookupAuthenticationPackage.Addr(),
uintptr(hnd),
uintptr(unsafe.Pointer(packageName)),
uintptr(unsafe.Pointer(authPackageId)))

if status != 0 {
return windows.NTStatus(status)
}
return nil
}

type SecurityLogonType uint32

const (
LogonTypeUndefined = 0
LogonTypeInteractive = 1 + iota
LogonTypeNetwork
LogonTypeBatch
LogonTypeService
LogonTypeProxy
LogonTypeUnlock
LogonTypeNetworkCleartext
LogonTypeNewCredentials
LogonTypeRemoteInteractive
LogonTypeCachedInteractive
LogonTypeCachedRemoteInteractive
LogonTypeCachedUnlock
)

type TokenGroups struct {
PrivilegeCount uint32
Privileges [1]windows.LUIDAndAttributes
}
type TokenSource struct {
SourceName [8]byte
SourceId windows.LUID
}
type QuotaLimits struct {
PagedPoolLimit uintptr
NonPagedPoolLimit uintptr
MinimumWorkingSetSize uintptr
MaximumWorkingSetSize uintptr
PagefileLimit uintptr
TimeLimit uint64
}

func LsaLogonUser(hnd windows.Handle,
originName *LSA_STRING,
logonType SecurityLogonType,
authPackageId uint32,
accountInformation *byte, accountInformationLength uint32,
tokenGroups *TokenGroups,
tokenSource *TokenSource,
profileBuffer **byte, profileBufferLength *uint32,
logonId *windows.LUID,
token *windows.Token,
quotas *QuotaLimits,
subStatus *windows.NTStatus) error {
status, _, _ := syscall.SyscallN(
procLsaLogonUser.Addr(),
uintptr(hnd),
uintptr(unsafe.Pointer(originName)),
uintptr(logonType),
uintptr(authPackageId),
uintptr(unsafe.Pointer(accountInformation)),
uintptr(accountInformationLength),
uintptr(unsafe.Pointer(tokenGroups)),
uintptr(unsafe.Pointer(tokenSource)),
uintptr(unsafe.Pointer(profileBuffer)),
uintptr(unsafe.Pointer(profileBufferLength)),
uintptr(unsafe.Pointer(logonId)),
uintptr(unsafe.Pointer(token)),
uintptr(unsafe.Pointer(quotas)),
uintptr(unsafe.Pointer(subStatus)))

if status != 0 {
return windows.NTStatus(status)
}
return nil
}

type MSV1_0_S4U_LOGON struct {
MessageType MSV1_0_LOGON_SUBMIT_TYPE
Flags uint32
UserPrincipalName LSA_UNICODE_STRING // username or username@domain
DomainName LSA_UNICODE_STRING // Optional: if missing, using the local machine
}

type MSV1_0_LOGON_SUBMIT_TYPE uint32

const (
MsV1_0InteractiveLogon = 2
MsV1_0Lm20Logon = 3
MsV1_0NetworkLogon = 4
MsV1_0SubAuthLogon = 5
MsV1_0WorkstationUnlockLogon = 7
MsV1_0S4ULogon = 12
MsV1_0VirtualLogon = 82
)

type KERB_S4U_LOGON struct {
MessageType KERB_LOGON_SUBMIT_TYPE
Flags uint32
ClientUpn LSA_UNICODE_STRING
ClientRealm LSA_UNICODE_STRING
}

type KERB_LOGON_SUBMIT_TYPE uint32

const (
KerbInteractiveLogon = 2
KerbSmartCardLogon = 6
KerbWorkstationUnlockLogon = 7
KerbSmartCardUnlockLogon = 8
KerbProxyLogon = 9
KerbTicketLogon = 10
KerbTicketUnlockLogon = 11
KerbS4ULogon = 12
KerbCertificateLogon = 13
KerbCertificateS4ULogon = 14
KerbCertificateUnlockLogon = 15
)

func LsaFreeReturnBuffer(buff *byte) error {
status, _, _ := syscall.SyscallN(procLsaFreeReturnBuffer.Addr(), uintptr(unsafe.Pointer(buff)))
if status != 0 {
return windows.NTStatus(status)
}
return nil
}
25 changes: 25 additions & 0 deletions drivers/shared/executor/s4u/misc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//go:build windows
// +build windows

package s4u

import (
"syscall"
"unsafe"

"golang.org/x/sys/windows"
)

var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")

procAllocateLocallyUniqueId = modadvapi32.NewProc("AllocateLocallyUniqueId")
)

func AllocateLocallyUniqueId(result *windows.LUID) error {
r1, _, e1 := syscall.SyscallN(procAllocateLocallyUniqueId.Addr(), uintptr(unsafe.Pointer(result)))
if r1 == 0 {
return e1
}
return nil
}
Loading
Loading