-
Notifications
You must be signed in to change notification settings - Fork 7.6k
sudo command does not work in remote session to Linux machine #1527
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
Comments
Thanks for looking into this. Yes, PowerShell remoting does now support passing SecureString objects over all platforms, but unfortunately was only added to 7.1+ versions. I am not worried about prompting for sudo password over a remote session, that can be handled with remote host calls, and probably an update to the protocol (PSRP). My main concern was how to interface with sudo, and it looks like @awakecoding 'AskPass' is the mechanism to use. It would be nice if there was some sort of API hook, but the above PoC may be workable. |
Slight bump, since pwsh 7.1 is out I've tweaked the script for #!/usr/bin/env pwsh
# Parent is sudo, grandparent is the pwsh process
$parentPid = (Get-Process -Id $pid).Parent.Parent.Id
# First arg is the prompt from sudo itself
$sudoPrompt = $args[0]
$ci = [Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($parentPid)
$rs = $ps = $null
try {
$rs = [runspacefactory]::CreateRunspace($ci)
$rs.Open()
$ps = [powershell]::Create()
$ps.Runspace = $rs
$null = $ps.AddScript({
$rs = Get-Runspace 1
$remoteHost = $rs.GetType().GetProperty('Host', 60).GetValue($rs)
$remoteHost.UI.Write($args[0])
$remoteHost.UI.ReadLineAsSecureString()
}).AddArgument($sudoPrompt)
$output = $ps.Invoke()
if ($output) {
$pass = [PSCredential]::new('noop', $output[0]).GetNetworkCredential().Password
return $pass
}
} finally {
if ($null -ne $ps) {
$ps.Dispose()
}
if ($null -ne $rs) {
$rs.Dispose()
}
} Connecting over SSH PSRemoting this is what happens
We can see that the actual sudo prompt appears and the password that I typed in is hidden behind
This is fine if
Without Maybe someone knows a
This works fine for a normal
Ideally it would be great if we could get the PSHost without relying on any private/protected properties but I'm unsure if that's going to be possible. There are some UX concerns I have as well, namely
In my mind this should all be transparent to the end user but even that is fraught with danger. |
Oh and because feedback on failures is also important, here is what happens when you put in the wrong password
Also when you press CTRL+C
|
@jborean93 this is awesome. This completely proves that this solution is the right way forward. My thinking on your UX concerns... Most of the concerns can be solved with a module + overriding Theoretically a user could install a module (called
Module installation is simple, and in the psm1 the call to chmod to make the file executable could be done (chmod might require sudo in some cases...)
Again in the psm1 we can set this env var.
We could do this in the
Same deal. A
A PowerShell API feature request. This should be a separate issue... But I totally agree. @jborean93 interested in throwing a module up on the Gallery for this? |
While it does still help in these scenarios I think this request goes beyond just calling What I feel like this example solves is a way to automatically get the prompt from Function Enable-SudoPrompt {
[CmdletBinding()]
param()
$tempPath = [IO.Path]::GetTempFileName()
# Probably a better way but just for testing
$cleanupAction = [ScriptBlock]::Create("Remove-Item -Path '$tempPath'")
$null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action $cleanupAction
$askPassScript = '@
#!/usr/bin/env pwsh
# Parent is sudo, grandparent is the pwsh process
$parentPid = {0}
# First arg is the prompt from sudo itself
$sudoPrompt = $args[0]
$ci = [Management.Automation.Runspaces.NamedPipeConnectionInfo]::new($parentPid)
$rs = $ps = $null
try {
$rs = [runspacefactory]::CreateRunspace($ci)
$rs.Open()
$ps = [powershell]::Create()
$ps.Runspace = $rs
$null = $ps.AddScript({
$rs = Get-Runspace -InstanceId '{1}'
$remoteHost = $rs.GetType().GetProperty('Host', 60).GetValue($rs)
$remoteHost.UI.Write($args[0])
$remoteHost.UI.ReadLineAsSecureString()
}).AddArgument($sudoPrompt)
$output = $ps.Invoke()
if ($output) {
$pass = [PSCredential]::new('noop', $output[0]).GetNetworkCredential().Password
return $pass
}
} finally {
if ($null -ne $ps) {
$ps.Dispose()
}
if ($null -ne $rs) {
$rs.Dispose()
}
}
'@ -f ($pid, [Runspace]::DefaultRunspace.InstanceId)
chmod +x $tempPath
$env:SUDO_ASKPASS = $tempPath
} It still needs to solve the fact that you need to call |
@TylerLeonhardt @jborean93 a module could be suitable to get something with older versions of PowerShell, but I think we should consider a built-in solution that comes with PowerShell, and not externally distributed or added on top. @jborean93 is correct when he says that wrapping the command inside PowerShell would only work when calling "sudo" from PowerShell and wouldn't fix the issue of non-PowerShell scripts or native programs that call sudo directly. Of course, there is the issue of existing scripts that call "sudo" without "sudo -A", but this is still something that can be worked with. For instance, you can call sudo a first time to get prompted and run the unmodified script, the second sudo won't trigger the prompt because of the sudo cache, etc. We can also look into adding the sudo alias in bash, or look into advanced sudo configuration options. In any case, if sudo -A works, it would be a zillion times better than the current state of things. As for wrapping and cleaning up afterwards, let's just look at the alternative: a guaranteed broken sudo when called from a PowerShell remoting session. I say we should automatically modify the environment to set SUDO_ASKPASS to point to a "pwsh-askpass" program in the PowerShell remoting server. As for the pwsh-askpass program itself, rather than distribute it as an external module, it should probably be part of the distributed PowerShell files. The first issue we face is that SUDO_ASKPASS works with an executable, and not an executable with a parameter. The ideal solution would be to add an "-AskPass" parameter to the pwsh executable, such that the automatic SUDO_ASKPASS injection would point to the same PowerShell executable as the current PowerShell server. Here's the trick to make it work: just make a "pwsh-askpass" relative symbolic link to make it point to its corresponding "pwsh" executable. In the pwsh executable, all we would need to is inspect argv[0] which contains the executable name. When called through the pwsh-askpass symlink, argv[0] would contain "pwsh-askpass" instead of pwsh, making it easy to run pwsh as if it was called with the -AskPass parameter. I should note that PowerShell $arg[0] is the first parameter is not the executable, but in the pwsh executable I am sure we can extract the same information as it is normally available in C. SUDO_ASKPASS is not something that is set in most cases, so I suggest we set it automatically unless it is already set, making it possible to override through some global system configuration. As for helper environment variables to let pwsh-askpass find the parent pwsh to attach to, PowerShell should simply be modified to create and set those environment variables by default. We should take special care of setting the PID for the right process, which I understand should be the one containing the PowerShell remoting server, not some nested pwsh session inside a PowerShell remoting server. I suggest the following names for the automatic environment variables to set that would be used by pwsh-askpass: Also, even if we can make a working pwsh-askpass script, I suggest we drop the .ps1 extension because we don't necessarily wish it to remain a script. All you need is to make it executable and add the following line at the beginning: #!/usr/bin/env pwsh However, I would rather have pwsh-askpass point to pwsh -AskPass, and implement askpass inside pwsh. Maybe we could plan this for PowerShell 7.2, and make a simple pwsh-askpass script that does best effort detection for PWSH_ASKPASS_PROCESS_ID and PWSH_ASKPASS_RUNSPACE_ID. The script could be installed globally on the system, and modifications to the OpenSSH server configuration could be used to set SUDO_ASKPASS in the PowerShell remoting environment by default. This way we have something that works right now, and it would become a built-in feature in PowerShell 7.2. |
Like a lot of things we consider to be the main package, if we can have it on the Gallery first, that is the first step. Actually, the perfect model for such a function would be: @SteveL-MSFT @PaulHigin where is that repo?
|
@TylerLeonhardt totally agree, I think we should make it available as a separate component in a first step, then consider including it in the main package. On the long run, I do think this is something that should be built-in to PowerShell, it looks a bit odd to have to install a separate package to get sudo to work in a PowerShell remoting session, which is something most users would expect to work out of the box. I suggest we make a first pwsh-askpass executable as a PowerShell script that would do best-effort detection of PWSH_ASKPASS_PROCESS_ID and PWSH_ASKPASS_RUNSPACE_ID when not available. For my own usage I think I would just "install" it manually in /usr/bin/pwsh-askpass and set SUDO_ASKPASS to 'pwsh-askpass' in my OpenSSH server environment. Once this script-based pwsh-askpass solution has had the chance of being used by some people, we could plan for possible inclusion in the main pwsh executable. Do this sound like a good plan? |
@jborean93 do you want to add what you have to this module? Then we can start getting feedback.
|
So, Since PS 7.0.3 is LTS (support lasts longer than 7.1), how can those of us that use/require an LTS version utilize sudo in a PSSession? Since security is of utmost importance, the script above that utilizes |
Why is that not preferred? I don't see how it would work without that. |
@TylerLeonhardt 7.1+ can use |
Ahh that's right. I forgot about that fix. Also it's too complex to backport to 7.0 so unfortunately you are out of luck. Maybe we could "fake it" with reading characters one at a time and using some vt100 to clear the character typed... But that sounds so painful. |
The other issue is that the types of calls you can make over a remote PSHost is limited. You are limited to just the ones listed at https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-psrp/ddd2a4d1-797d-4d73-8372-7a77a62fb204. Any of the methods that return a secure string will be out of luck with 7.0 :( Maybe you could have some sort of complex manual buffer manager at the RawUI layer but honestly I have no desire to investigate this :) For people on 7.0 they could just do
|
Which would honestly be fine, if the following worked (which it doesn't).
In 7.0, I cannot find a single way to pass secure strings to a PSSession, so scripting a Outside of a PSSession, it is possible to run something like |
That's because you are trying to send a secure string across which doesn't work on non-Windows hosts until 7.1. If the password was plaintext like so then it would work $creds = Get-Credential
$sudoPass = $creds.GetNetworkCredential().Password
$session = New-PSSession -hostname Workstation1 -Username foo -SSHTransport
Invoke-Command -Session $session {
param(
[String]
$SudoPass
)
$null = $SudoPass | sudo -S whoami
if ($LastExitCode -ne 0) {
Write-Error -Message "Failed to cache sudo password"
return
}
# Now you have cached your sudo password you should be able to call it normally (up to whatever
# timeout you have configured)
sudo whoami
} -Argumentlist $sudoPass Note this is not tested and it may require some better logic to check for failures but it should work. I will agree it's also not good to send passwords as plaintext across the wire but at least SSH encrypts the traffic. There's nothing that can be done about this unless you are on 7.1+ as we've mentioned. Even then on 7.1+ the SecureString will not be encrypted in memory on the remote process due to how SecureStrings work on non-Windows hosts. The 2 benefits 7.1 provides are:
It's up to the PowerShell team to backport the changes in #11185. I have no idea what the general policy is for this for this project so I can't really comment on it more. Also your example had |
@jborean93 in your example here you do:
Where that Do you still need to do the |
Good catch, that should just be |
@jborean93 do you have interest in contributing that cmdlet to the RemotingTools module so we can start getting validation? Your own separate module would also be totally cool. |
I would be interested but I don't have any spare time in the current week. I would probably only be able to work on it properly in about 2 weeks or so. People are more than welcome to take what I have and work on it if I'm taking too long :). |
Haven't seen any movement on this but see it as still open. I just started trying to manage *nix systems with remote PS sessions and this is a definite blocker. |
@PaulHigin despite the lack of updates to this issue, I just tested again on pwsh 7.3.9 running on Debian 11 & 12. Still doesn't work when an interactive password is required but it does work with passwordless sudo, and you can workaround by piping a securestring into the sudo command (I don't remember if this was possible before). example
|
This issue has been marked as "No Activity" as there has been no activity for 6 months. It has been closed for housekeeping purposes. |
Steps to reproduce
Enter-PSSession -HostName -UserName
[] C:> sudo ls /proc/powershell
Expected behavior
sudo command to run in remote interactive session.
Actual behavior
Environment data
Name Value
PSVersion 5.1.10032.0
PSEdition PowerShellCore
PSCompatibleVersions {1.0, 2.0, 3.0, 4.0...}
BuildVersion 3.0.0.0
GitCommitId v0.6.0-392-ga5b5dc576b8006c0d0a83a5c538f382d9fa65dc6
CLRVersion
WSManStackVersion 3.0
PSRemotingProtocolVersion 2.3
SerializationVersion 1.1.0.1
The text was updated successfully, but these errors were encountered: