jborean93 / omi

Open Management Infrastructure
Other
111 stars 13 forks source link

Using custom OMI version in Azure Cloud Shell #24

Closed janegilring closed 3 years ago

janegilring commented 3 years ago
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
PS /home/admin> $PSVersionTable

Name                           Value
----                           -----
PSVersion                      7.1.2
PSEdition                      Core
GitCommitId                    7.1.2
OS                             Linux 4.15.0-1108-azure #120~16.04.1-Ubuntu SMP Thu Feb 11 07:47:15 UTC 2021
Platform                       Unix
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0
jborean93 commented 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:

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:

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!

jborean93 commented 3 years ago

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.

jborean93 commented 3 years ago

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

Unfortunately what this means is that for now CloudShell it will only support the following

janegilring commented 3 years ago

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 👍