Closed janegilring closed 3 years ago
This is going to be tricky but after testing this myself I have found a way. To understand the solution we first need to see how the fork is being loaded. There are 2 libraries in this fork:
psrpclient
- Entrypoint from PowerShell, wraps mi
to make it match the Win32 API for WSMan so PowerShell stays consistentmi
- The code that implements the WSMan stack, linked to psrpclient
PowerShell itself hardcodes the DllImport
path for the WSMan calls to libpsrpclient
on Linux https://github.com/PowerShell/PowerShell/blob/2784add414393b0ecab798055cfbac0d363c1c1e/src/System.Management.Automation/engine/remoting/fanin/WSManNativeAPI.cs#L2339 so we are at the mercy of how .NET/dlopen resolves that library. Normally it's going to load libpsrpclient.so
that is in the PowerShell directory which it itself then loads libmi.so
in the same dir. So just replacing the files is the simplest way to get this working and it will automatically just happen.
Unfortunately in this scenario we don't have access to modify the files in the PowerShell directory so we need to somehow tell .NET that when it comes to loading psrpclient
it should use a custom path instead. To do this we need to add a custom import resolver for the PowerShell assembly which can modify the path used.
This is the code you can use to set this up:
# Defaults to installing in the CurrentUser, just need to run once
Install-Module -Name PSWSMan -Force
# Need to run each time the shell is opened, no idea if there's a profile this can be set to
Add-Type -TypeDefinition @'
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices;
namespace CloudShell
{
public static class MI
{
public static Dictionary<string, string> DllMap = new Dictionary<string, string>();
public static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
{
if (DllMap.ContainsKey(libraryName))
libraryName = DllMap[libraryName];
return NativeLibrary.Load(libraryName, assembly, searchPath);
}
}
}
'@
$libbase = Join-Path (Import-Module -Name PSWSMan -PassThru).ModuleBase lib debian10
[CloudShell.MI]::DllMap['libpsrpclient'] = Join-Path $libBase libpsrpclient.so
[Runtime.InteropServices.NativeLibrary]::SetDllImportResolver([PSObject].Assembly, [CloudShell.MI]::ImportResolver)
What this does is it gets the path to libpsrpclient.so
for the distribution we are running on (this case being Debian 10 based on /etc/os-release
). Adds it to the static field DllMap
which the new import resolver for PowerShell will use to modify the import path whenever libpsrpclient
is being imported. The libmi
module should just be loaded by dlopen()
automatically but this happens outside of .NET so we can't really control that functionality.
A few things to note:
Get-WSManVersion
from PSWSMan
will not use this new path and will thus report that it isn't installedSetDllImportResolver
only works when there isn't already a custom resolver
Hopefully this gets you moving forward, I tried a few other attempts like calling dlopen()
manually to cache the file but that never worked. In the end this route does seem to work for me when testing it so I assume it should also work for you. Good luck!
As per a conversation on Discord about this, it looks like gss-ntlmssp isn't installed on the Cloud Shell making Windows auth only workable through Kerberos. There's a request to install the package https://github.com/Azure/CloudShell/issues/81 but in the meantime this may work
# Run once to install gss-ntlmssp for the current user
apt-get install --download-only gss-ntlmssp
dpkg -x /var/cache/apt/archives/gss-ntlmssp_0.7.0-4_amd64.deb /tmp/gss-ntlmssp
mkdir -p ~/.config/gssapi
cp /tmp/gss-ntlmssp/usr/lib/x86_64-linux-gnu/gssntlmssp/gssntlmssp.so ~/.config/gssapi/gssntlmssp.so
cat <<EOT >> ~/.config/gssapi/mech.conf
gssntlmssp_v1 1.3.6.1.4.1.311.2.2.10 $( eval echo '~/.config/gssapi/gssntlmssp.so' )
EOT
# Add to the PowerShell profile so it runs everytime PowerShell starts
& (Import-Module PSWSMan -PassThru) { setenv 'GSS_MECH_CONFIG' '~/.config/gssapi/mech.conf' }
This will setup the gss-ntlmssp library in the user directory and set up a mech.conf
file for GSSAPI to read. The GSS_MECH_CONFIG
is set to the path of that file so it knows that NTLM is available and to use this library for any operations on NTLM.
This is untested due to the Azure AD outage right now but the basic workflow should be correct.
Azure is back up so I took another stab at this and I unfortunately don't have any good news. I had to swap up the code to install gss-ntlmssp with PowerShell in the local user dir with
wget http://ftp.br.debian.org/debian/pool/main/g/gss-ntlmssp/gss-ntlmssp_0.7.0-4_amd64.deb
dpkg -x gss-ntlmssp_0.7.0-4_amd64.deb /tmp/gss-ntlmssp
mkdir -p ~/.config/gssapi
cp /tmp/gss-ntlmssp/usr/lib/x86_64-linux-gnu/gssntlmssp/gssntlmssp.so ~/.config/gssapi/gssntlmssp.so
Set-Content -Path ~/.config/gssapi/mech.conf -Value @"
gssntlmssp_v1 1.3.6.1.4.1.311.2.2.10 $((Get-Item '~/.config/gssapi').FullName)/gssntlmssp.so
"@
This new change works fine but unfortunately the version of krb5 that is installed is just not new enough to support GSS_MECH_CONFIG
which we need to set so it can see our mech.conf
file. Without this env var the library will always look in the location /etc/gss/mech.d/*.conf
. This means that OMI will always default to using Kerberos authentication which is going to fail with
An invalid name was supplied SPNEGO cannot find mechanisms to negotiate For more information
Even if the name is in the UPN format (username@REALM.COM
) this is bound to fail unless that's a true domain user which the Kerberos implementation is able to resolve. This means that to support NTLM authentication in CloudShell one of 2 things need to happen
gss-ntlmssp
package needs to be installed and configured correctly - https://github.com/Azure/CloudShell/issues/81libkrb5-dev
package (or however MS is setting this up) needs to be on krb5 1.18 or newer
GSS_MECH_CONFIG
was added so we can rely on mechs installed in another locationUnfortunately what this means is that for now CloudShell it will only support the following
Thanks for looking into this - the options is clear now. Let's hope they will add the gss-ntlmssp
package to the Cloud Shell image 👍
SUMMARY
Azure Cloud Shell is based on a Linux container image running Ubuntu 16.04 - without any sudo permissions.
Are there any way of leveraging this custom OMI module without sudo access? For example by loading it from the user's home directory.
OS / ENVIRONMENT