Starting a process as a different user (within server 2022 container)

Some services in my stacks need creds to be passed into the container during runtime (via secrets preferably) so that it can access external resources. Basically, the process needs to be running under the user so that it can authenticate.

In my entrypoint script, I have functions defined to initialize the user, grant the proper perms, etc. Now - I am able to assign this user to a service, and when the service runs, it is able to authenticate remotely. Without defining this user in the dockerfile/during buildtime.

However, not all apps can be run as services, and not all apps are built to accept creds to auth with for remote shares (nor can I access the source code). IE. in the container, first running a net use \ip\dir and successfully authenticating, setting it to a drive letter, and defining the path using the new drive in the services config file still fails (all with the same user, ContainerAdmin). To get around this, I am trying to start the process as a different user - the one passed in via secrets and initialized during entrypoint execution.

(I am aware that I can SMB global map the share on the host machine using the credentials and then bind mount that drive letter into the container in the compose. However this approach requires more setup on the host level)

Both of these functions fail when passing in creds of a local user, but not when run as the ContainerAdmin (ie. not passing those in). I have also tried opening up a new shell under that user first and then passing in the true call to no avail:

Start-Process -FilePath $path -ArgumentList $args -Credential $creds
and
psexec -u $user -p $pass $path $args (with any flags, including the services dns name as a remote machine, host machine name/IP, IP of the service on the docker network, etc)

I have tried every iteration of $user, including ā€œusernameā€, ā€œ.\usernameā€, ā€œ$env:computername\usernameā€, ā€œBUILTIN\usernameā€. when creating a pscredentials object the pass is converted to secure string as well. This user exists on the host machine and the remote target. There is no UAC in containers to my knowledge. I have given this user full modify access, as well as the following perms using secedit:
SeServiceLogonRight
SeBatchLogonRight
SeImpersonatePrivilege
SeDelegateSessionUserImpersonatePrivilege
SeInteractiveLogonRight
SeNetworkLogonRight
SeAssignPrimaryTokenPrivilege

Using start-process - when querying the containers event log, I can see logon event errors being thrown. The exit code is (0xc0000142). I need help in troubleshooting this further to be honest.

Using psexec, it fails with a ā€œhandle not foundā€ and error code -1073741502 (which is the same in hex as the one above). When using psexec and passing in the -d flag, it says it started the process and spits out a PID, but running a tasklist -v right after shows nothing is actually running (and the app isnt generating logs so it was never ran).

I am at my wits end here. There has to be a way to do thisā€¦ what am I missing? Anyone get this to work?

The super hacky solution I am thinking is to make a service in Go that literally just runs a given exe with parameters and slap that in the image.

Update:

No luck trying to use the Win32 API by importing C# classes into powershell and impersonating the user.

What ended up working for me was actually creating the process and args via task scheduler and running the task. Still pretty hacky but wrapping up the following ps1 code into a script and dot sourcing it will do the trick and abstract away the inner workings.

This is just a rough draft/MVP and not very secure, still in a test environment. Obviously going to need to lock down access to users trying to execute this script within the image/container. The user running it also needs the SeLogonBatchPermission.

Hopefully this helps someone out (edited for formatting)

runas.ps1 =
#######################################################

[CmdletBinding()]
param (
    [Parameter(Mandatory)]
    [String]
    $un,
	[Parameter(Mandatory)]
    [String]
    $pw,
	[Parameter(Mandatory)]
    [String]
    $exe,
	[Parameter(Mandatory=$false)]
    [String]
    $args='',
	[Parameter(Mandatory=$false)]
    [Bool]
    $wait=$false
)

#remove invalid chars and ensure length in name so task can get created
function Get-CleanName {
	param (
    [Parameter(Mandatory)]
    [String]
    $name
)
	$invalidChars = @('\', '/', ':', '*', '?', '"', '<', '>', '|') 
	foreach ($c in $invalidChars) {
		$name = $name.Replace($c, '')
	}
	if ($name -ge 260) {
		$name = $name.Substring(0, 259)
	}
	return $name
}

$exeName = $exe.Split('\')[-1]
$exeName = $exeName.Split('.')[0]
$taskName = Get-CleanName -Name "$user$exeName$args"
$exe = '"' + $exe + '"'
if ($args -eq '' -or $args -eq $null) {
	$Action = New-ScheduledTaskAction -Execute $exe
} else {
	$exe = $exe + ' ' + $args
	$Action = New-ScheduledTaskAction -Execute "C:\Windows\System32\WindowsPowerShell\v1.0\PowerShell.exe" -Argument "-ExecutionPolicy Bypass -Command `"$exe`""
}
$Trigger = New-ScheduledTaskTrigger -AtStartup
try {
	Register-ScheduledTask -Action $Action -TaskName $taskName -Trigger $Trigger -User $un -Password $pw -RunLevel Highest
}
Write-Host "executing:$exe"
Start-ScheduledTask -TaskName $taskName

if ($wait) {
	$task = Get-ScheduledTaskInfo -TaskName $taskName
	$code = $task.LastTaskResult
	while (!($code)) {
		$task = Get-ScheduledTaskInfo -TaskName $taskName
		$code = $task.LastTaskResult
		Write-Host "$code"
		Start-Sleep -Seconds 1
	}
	Start-Sleep -Seconds 5
	#get highest PID process
	$spawn = Get-Process | Where-Object { $_.ProcessName -eq $exeName } | Sort-Object Id -Descending | Select-Object -First 1
	if ($spawn) {
		Write-Host "waiting for $exeName to finish executing..."
		Wait-Process -Id $spawn.Id
		$exitCode = $spawn.ExitCode
		Write-Host "$exeName finished, exit code: $exitCode"
	} else {
		Write-Host "$exeName failed to start"
	}
}
1 Like