A few years ago I jumped from doing all my dev work on Windows to Linux. This migration has had a few challenges but one of the things I struggled with initially was the lack of native tooling that can be used to easily and seamlessly interact with other Microsoft products. I’ve typically found some great open source products that fill in that gap quite nicely, but one area that has always felt a bit lacking was support for PSRemoting. There are some fantastic WSMan/WinRM client libraries out there which are pretty close to feature parity with WSMan on Windows but typically lack 2 main features, being interactive and directly integrated in PowerShell.
I thought that with Microsoft joining the fray and creating an open source and cross platform release of PowerShell that this gap would be filled nicely. My main desire was being able to do Enter-PSSession
to target some of my dev Windows boxes just like I could do from a Windows workstation. Much to my disappointment, this has not lived up to my expectations. In this post, I will talk about my journey in trying to get WSMan as a client working on PowerShell from a Linux host, and what I’ve learnt along the way.
Current Landscape
PowerShell Remoting (PSRemoting) is a protocol that PowerShell uses to execute PowerShell on another PowerShell instance. This can be another PowerShell process on the same host, or even another host altogether. PSRemoting works by encapsulating .NET objects, serialised in CLIXML, inside another remoting protocol like WSMan or SSH. If you are interested in learning more about PSRemoting, my PowerShell Remoting on Python post goes into more detail.
Historically WSMan was the sole transport option due to it being the de facto protocol used by Windows for remote management. Over time more protocols have been added, like SSH, to take advantage of PowerShell’s move to open source and the new target audience that comes with that. Ultimately I feel like SSH is the superior transport option over WSMan but there are a few times where WSMan is still needed like:
- Targeting Windows hosts
- WSMan works out of the box
- SSH requires the SSH server and PowerShell 6+ to be installed
- The Win32-OpenSSH fork that comes with Windows does not support Kerberos authentication
- With Kerberos authentication you can easily solve the double hop limitation without sending your actual password across the wire
- A newer release will add support for this, negating this requirement somewhat
- Just Enough Administration (JEA) only works on WSMan
- There is talk about adding support for JEA on SSH but nothing it out as of yet
- Exchange Online and Exchange on premise only work with WSMan, no SSH at all
This wouldn’t be too much of an issue if the WSMan client that ships with PowerShell on Linux and macOS wasn’t so horribly broken and outdated. After quickly browsing the PowerShell repo for issues related to WSMan on Linux and macOS, a common pattern emerges:
- #4952 – general issues
- #5561 – old linked OpenSSL library on macOS
- #5634 – old linked OpenSSL library on macOS
- #5686 – GSSAPI issues
- #5970 – old linked OpenSSL library on macOS
- #6173 – Decryption failure when targeting an on-prem Exchange target
- #6647 – trouble getting GSSAPI auth working
- #7342 – fails in Docker image due to incorrect getaddrinfo() usage
- #7425 – no GSSAPI support on macOS
- #7896 – segfault on macOS due to GSSAPI code
- #10225 – library unavailable on Alpine
- #10600 – old linked OpenSSL library on macOS
- #11216 – old linked OpenSSL library on macOS
- #12219 – general issues
Adding more salt to the wound is the PowerShell team’s stance of WSMan in PowerShell is that it is deprecated and there are no plans on trying to fix any of the existing bugs that people are encountering. There is an issue that states OMI is deprecated and there are no plans for any future bugfixes or features.
OMI will not fix PowerShell bugs meaning PowerShell cannot offer any level of support
Known and unknown security issues will not be fixed
OMI does not load OpenSSL correctly causing segfaults
Don’t get me wrong, I understand their stance on this issue. They are a small team and need to prioritise their focus based on things that bring benefits to most users. There are some scattered comments stating that there are plans on creating a new WSMan client in .NET for PowerShell that fix a lot of these problems but so far these just seem to be plans with no concrete work.
So instead I thought “this is all open source, why don’t I fix the bugs and contribute it back?”. I fixed a few of the major issues like getting things to compile on some newer versions of macOS and some of the bigger GSSAPI problems. I opened PR 1 and PR 2 on the OMI repo but unfortunately these PRs were subsequently rejected by whatever team in Microsoft manages OMI, not the same as the PowerShell team, mostly on the grounds that “support” for PowerShell was dropped in 2018 and “new features” need to be internally prioritised. So ultimately even if someone wanted to try and improve OMI, any changes will be stonewalled when trying to get those fixes into the actual codebase.
So we are stuck in a situation where the library PowerShell uses has known issues and problems due to its reliance on older libraries, among other problems, and no way to get fixes for those issues into the codebase. You might be thinking, big deal, WSMan is only used for legacy Windows products. Just use your existing Windows hosts to manage those. Unfortunately even new products in development today, like the Exchange Online V2 PowerShell Module are still based on WSMan only. If you wanted to use this new module you are stuck with running it on Windows only. While this may not be a feature that’s massively in demand, I personally find the whole situation unfortunate and just leads to more barriers of entry for the adoption of PowerShell on Linux.
In the end, I decided to just continue on with fixing the bugs in the existing client and take advantage of the code being open sourced. I forked the OMI repo and starting fixing any of the bugs I encountered there. I’ll talk more about using this fork later on, first I want to talk about these bugs and what I did to fix them. I’ve found that the existing bugs in OMI can be split into 3 different categories:
- Dynamic linking library problems
- GSSAPI (authentication and encryption) problems
- Client behaviour problems
Dynamic Linking
OMI is written in C and is compiled to various shared library objects for the different functionality that it offers. PowerShell is only interested in using the mi
library that is produced as that is where the WSMan client code is stored. If you look into your PowerShell directory on your Linux/macOS host you will see two different lib<name>
libraries that affect WSMan:
The psrpclient
is basically a compatibility layer to translate the public interface that mi
exposes to the same interface as the Win32 WSMan API calls. This compat layer mostly just allows PowerShell to call one interface for any WSMan calls across the various platforms. We can see the actual PInvoke definitions in the PowerShell codebase
#if !UNIX
internal const string WSManClientApiDll = @"WsmSvc.dll";
internal const string WSManProviderApiDll = @"WsmSvc.dll";
#else
internal const string WSManClientApiDll = @"libpsrpclient";
internal const string WSManProviderApiDll = @"libpsrpomiprov";
#endif
[DllImport(WSManNativeApi.WSManClientApiDll, SetLastError = false, CharSet = CharSet.Unicode)]
internal static extern int WSManInitialize(int flags,
[In, Out] ref IntPtr wsManAPIHandle);
In this case it is calling the WSManInitialize
function, along with many others, that is exposed in WsmSvc.dll
on Windows and libpsrpclient
on other platforms. We can see in the psrpclient repo that WSManInitialize
is mostly a shim for MI_Application_InitializeV1
which is the public interface exposed by mi
.
MI_EXPORT MI_Uint32 WINAPI WSManInitialize(
MI_Uint32 flags,
_Out_ WSMAN_API_HANDLE *apiHandle
)
{
MI_Result miResult;
_GetLogOptionsFromConfigFile(SHELL_LOGGING_FILE);
LogFunctionStart("WSManInitialize");
(*apiHandle) = calloc(1, sizeof(struct WSMAN_API));
if (*apiHandle == NULL)
return MI_RESULT_SERVER_LIMITS_EXCEEDED;
miResult = MI_Application_InitializeV1(0, NULL, NULL, &(*apiHandle)->application);
if (miResult != MI_RESULT_OK)
{
free(*apiHandle);
*apiHandle = NULL;
}
LogFunctionEnd("WSManInitialize", miResult);
return miResult;
}
So for PowerShell to be able to create WSMan instances on Linux it needs to be able to load psrpclient
which in turn relies on mi
being available. If any one of those libraries, or any of their linked dependencies are not present then PowerShell will fail with the following error:
Enter-PSSession: This parameter set requires WSMan, and no supported WSMan client library was found. WSMan is either not installed or unavailable for this system.
It’s not very helpful in telling you what actually failed to load as it could be either psrpclient
, mi
, or one of their deps not being available. To investigate this a bit more, you can run the following to list the dynamic linked libraries of both libraries
PWSHDIR="$( dirname "$( readlink "$( which pwsh )" )" )"
# On macOS
otool -L "${PWSHDIR}/libpsrpclient.dylib"
otool -L "${PWSHDIR}/libmi.dylib"
# On Linux
ldd "${PWSHDIR}/libpsrpclient.so"
ldd "${PWSHDIR}/libmi.so"
On macOS we can see that the shipped libmi.dylib
is linked to a custom OpenSSL path.
/usr/local/microsoft/powershell/7/libmi.dylib:
@rpath/libmi.dylib (compatibility version 0.0.0, current version 0.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1238.60.2)
/usr/lib/libpam.2.dylib (compatibility version 3.0.0, current version 3.0.0)
/usr/local/opt/openssl/lib/libssl.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib (compatibility version 1.0.0, current version 1.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.8)
There are 2 problems here:
- It’s linked to a hardcoded location
/usr/local/opt/lib/libssl.1.0.0.dylib
that isn’t a system library - It’s linked to OpenSSL 1.0.0 which is an old, outdated, and probably insecure library
So when you try and load mi
on macOS it will most likely fail as you more than likely do not have the OpenSSL 1.0.0 libs at /usr/local/opt/openssl/lib
. You can’t even install this version of OpenSSL using brew
anymore unless you find a custom formula. Even if you use a custom formula, or manually compile your own OpenSSL to this location, it’s not going to change the fact the OpenSSL 1.0.0 is not an ideal library to use for creating secure backends due to its age. So to fix this problem on macOS we need to make sure we compile mi
against a newer version of OpenSSL and hopefully with one that is easily available on most systems.
For Linux, I tested with CentOS 7, we can see that we have a similar setup.
[user@hostname /home/user]# ldd "${PWSHDIR}/libmi.so"
ldd: warning: you do not have execution permission for `/opt/microsoft/powershell/7/libmi.so'
linux-vdso.so.1 => (0x00007ffd2f1eb000)
libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fe4775bf000)
libdl.so.2 => /lib64/libdl.so.2 (0x00007fe4773bb000)
libpam.so.0 => /lib64/libpam.so.0 (0x00007fe4771ac000)
libssl.so.1.0.0 => /opt/microsoft/powershell/7/libssl.so.1.0.0 (0x00007fe476f3a000)
libcrypto.so.1.0.0 => /opt/microsoft/powershell/7/libcrypto.so.1.0.0 (0x00007fe476ad7000)
libc.so.6 => /lib64/libc.so.6 (0x00007fe476709000)
/lib64/ld-linux-x86-64.so.2 (0x00007fe4777db000)
libaudit.so.1 => /lib64/libaudit.so.1 (0x00007fe4764e0000)
libgssapi_krb5.so.2 => /lib64/libgssapi_krb5.so.2 (0x00007fe476293000)
libkrb5.so.3 => /lib64/libkrb5.so.3 (0x00007fe475faa000)
libcom_err.so.2 => /lib64/libcom_err.so.2 (0x00007fe475da6000)
libk5crypto.so.3 => /lib64/libk5crypto.so.3 (0x00007fe475b73000)
libz.so.1 => /lib64/libz.so.1 (0x00007fe47595d000)
libcap-ng.so.0 => /lib64/libcap-ng.so.0 (0x00007fe475757000)
libkrb5support.so.0 => /lib64/libkrb5support.so.0 (0x00007fe475547000)
libkeyutils.so.1 => /lib64/libkeyutils.so.1 (0x00007fe475343000)
libresolv.so.2 => /lib64/libresolv.so.2 (0x00007fe475129000)
libselinux.so.1 => /lib64/libselinux.so.1 (0x00007fe474f02000)
libpcre.so.1 => /lib64/libpcre.so.1 (0x00007fe474ca0000)
[user@hostname /home/user]# ls -al /opt/microsoft/powershell/7/libssl.so.1.0.0
lrwxrwxrwx 1 root root 19 Aug 19 11:16 /opt/microsoft/powershell/7/libssl.so.1.0.0 -> /lib64/libssl.so.10
It is still linked to OpenSSL 1.0.0 but now it’s through a symlink to /lib64/libssl.so.10
. While it is still bad to use OpenSSL 1.0.0 if you can avoid it. It’s still less difficult to source OpenSSL 1.0.0 on most Linux distributions. As time goes on this will become more of an issue on Linux as newer distributions drop OpenSSL 1.0.0 altogether so it’s something to keep in mind.
Luckily the underlying OMI repo does actually compile and work against OpenSSL 1.1.0, we just need to make sure the compiled library we use was built against OpenSSL 1.1.0. By recompiling the code as is, we automatically solve this problem and the build process automtically selects the OpenSSL library that’s already installed and in the PATH
of the host.
GSSAPI
So once we sole the linking problem and have gotten PowerShell to load mi
, we move onto the next major problem with the library, the authentication process. The WSMan protocol supports the following authentication protocols:
- Basic
- Certificate
- Negotiate – Kerberos with an NTLM fallback
- Kerberos – with no NTLM fallback
- CredSSP
If you want to learn more about how this works in WSMan, you can have a read of my Demystifying WinRM post. WSMan on Windows supports all these protocols out of the box but mi
is only designed to work with Basic, Negotiate, and pure Kerberos authentication. Even if Certificate and CredSSP auth was implemented in the future in mi
, there is a hardcoded check in the psrpclient
library that returns an error if another authentication option was specified.
Typically Basic
authentication is used for connections to Exchange Online and is the simplest protocol overall. Negotiate/Kerberos is used for actual Windows endpoints and is required if you are authenticating with a domain account. Windows users are spoiled with a nice security provider called SSPI which does a wonderful job of abstracting all the complexities of Negotiate authentication. It is practically invisible to the end user and essentially just works. If they want to access a file on a file share, Windows will typically automatically authenticate using the user’s credentials without any further prompts.
On Linux the equivalent to SSPI is something called GSSAPI. GSSAPI typically comes in 2 different implementations, one called MIT krb5 and another called Heimdal. Typically on Linux you would find MIT whereas BSD based distributions use Heimdal, macOS uses a forked version of Heimdal with some Apple specific changes made to it. While GSSAPI can provide both Kerberos and NTLM authentication it is not included out of the box, requires domain specific config files to be created, DNS working properly, and probably most importantly it doesn’t normally integrate with your logon account. While these problems can be an obstacle for users who are new to GSSAPI on Linux, they are not insurmountable and once solved you can have GSSAPI act in a similar way to SSPI on your Linux host.
When trying to figure out why I was getting MI_RESULT_ACCESS_DENIED
errors on Linux I came across a the following problems with how authentication was implemented in mi
:
- There is a hardcoded password length of 1024 bytes
- This is a problem with modern auth for Exchange Online
- Modern auth uses JSON Web Tokens (JWT) which can exceed this length causing a failure
- The limitation has been increased to 8 KiB
- OMI tries to import the wrong symbols when targeting Heimdal
- This breaks Negotiate authentication completely on macOS
- By using the correct symbol names, GSSAPI will load properly and can be used on macOS
- OMI constructs the Service Principal Name (SPN) in a very strict fashion
- This makes Kerberos auth even harder to be used on Linux
- When Kerberos fails, Negotiate auth is designed to fallback to NTLM
- NTLM is not provided by MIT krb5 and is another optional package to install
- Now the SPN is constructed by passing in the name type
GSS_C_NT_HOSTBASED_SERVICE
with the valuehttp@<hostname>
- No support for using an implicit Kerberos credential, you always have to provide explicit credentials
- The hardcoded check that makes sure a username was set has now been removed
- The
GSS_C_DELEG_POLICY_FLAG
was not in the req_flags- This meant that Kerberos delegation was never enabled even when the SPN was trusted for delegation
- By adding that flag, the credential will be delegated if the SPN is trusted for delegation
- OMI resolves the target hostname to the Fully Qualified Domain Name (FQDN) of the target
- This invalidates the Kerberos server authentication model
- The code even causes a segfault if the hostname was not resolvable
- This whole process was removed due to it breaking the security model for Kerberos
Essentially this means that mi
as provided by PowerShell works only in a very specific setup, namely on Linux where Kerberos is configured in a very specific way. Fixing these problems were relatively simple and the end result is that modern auth is now working and Negotiate/Kerberos auth works in more situations than before. The only remaining holdout is supporting the NTLM fallback on macOS when connecting over HTTP. There is a bug in the macOS’ GSSAPI implementation that causes an interop failure and this issue sits in a place that I cannot touch. Luckily NTLM auth is old and insecure and you should really get Kerberos working. If you really want to use NTLM auth on macOS you are stuck with connecting over HTTPS with -UseSSL
.
Client Behaviour
So now that we’ve been able to authenticate with our servers over WSMan the last remaining issue is dealing with some invalid client behaviour assumptions. The biggest problem I encountered here was dealing with the message encryption payload that WSMan uses when connecting over HTTP. Message encryption in WSMan encrypts the raw WSMan payload and encodes it as a MIME multipart message. This MIME payload follows the format
--EncryptedBoundary
Content-Type: application/HTTP-SPNEGO-session-encrypted
OriginalContent: type=application/soap+xml;charset=UTF-8;Length=<plaintext length>
--EncryptedBoundary
Content-Type: application/octet-stream
<header length><header><encrypted payload>--EncryptedBoundary--\r\n
Some of these values depend on the authentication method chosen or how the client actually formats the data but the basic structure stays the same. On premise Exchange hosts changes the boundary to -- EncryptedBoundary
(with the space) which is technically against the spec and mi
was never able to handle that change breaking connections on those endpoints.
The decryption code in OMI was very temperamental in whether it worked or not. I was able to get it working from my main dev host but as soon as I was testing on a container host it was failing with the very unhelpful error:
MI_RESULT_FAILED For more information, see the about_Remote_Troubleshooting Help topic.
When stepping through the code I found that the HttpClient_DecryptData function was failing to find the encrypted payload in the MIME data causing that failure. To solve this issue I decided to refactor the MIME parser logic in this function and came up with a solution that works everytime I run it and even handles the -- Encrypted Boundary
setup that on premise Exchange endpoints send back.
Solution
So I’ve identified the bugs and have fixed them, how do I share the work I’ve done for others to use. I eventually decided on creating a fork of the OMI repository and contributing my changes back there. This repo had to satisfy the following criteria I set out for myself:
- Make it easy to merge any upstream changes, if any are done in the future
- Document the reasons for the fork and what was changed
- Provide an easy way to build the library for various Linux distributions
- Provide a compiled “release” version for those distributions for easier consumption
- Actually test out the changes with PowerShell targeting both Windows endpoints and Exchange Online
- Add CI integration to run on every change
I’ve mostly succeeded in achieving these goals, any PR will automatically be run in Azure Pipelines to make sure the code will still build for the distributions. While I can’t run the actual integration tests in CI I did create some scripts that will set up an environment and test out the various distributions with the libraries built in CI. The tests that are run just cover a basic set of scenarios and ensure the code can connect using Kerberos and optionally with Exchange Online. With this setup I can make a change in a PR then subsequently check if it still works when running from PowerShell itself.
Building
If you are wanting to build the code, you can either compile it manually just like you would with the upstream OMI repo with the following:
cd Unix
./configure --outputdirname=build --prefix=/opt/omi
make
You can then find the libmi
file in Unix/build/lib/libmi.so
. Building the code is quite simple, the hardest part is getting all the dependencies required to build OMI. I decided to write a Python script build.py
that reads the metadata for a “known” distribution and then generate the build code for you. You can even use build.py
to build the code in a Docker container for the distribution of your choice and not pollute your local environment. For example if you wanted to compile the code for CentOS 8 in an ephemeral Docker container, you can run the following:
./build.py centos8 --docker
The build.py
script will read the centos8.json distribution meta file and create a bash script on the fly that will handle the deps for you. If you use the --output-script
argument, the process will output the bash script that will build mi
for you.
$> /build.py centos8 --output-script
#!/usr/bin/env bash
set -o pipefail -eu
echo ""
echo "-------------------------------------------"
echo "| Installing build pre-requisite packages |"
echo "-------------------------------------------"
echo ""
yum install -y -q \
bind-utils \
gcc \
gcc-c++ \
krb5-devel \
make \
openssl \
openssl-devel \
pam-devel \
redhat-lsb-core \
rpm-build \
rpm-devel \
which
echo ""
echo "----------------------------------------"
echo "| Clearing build folder |"
echo "----------------------------------------"
echo ""
if [ -d "build-centos8" ]; then
rm -rf "build-centos8"
fi
echo ""
echo "----------------------------------------"
echo "| Running configure |"
echo "----------------------------------------"
echo ""
./configure \
--outputdirname="build-centos8" \
--prefix="/opt/omi"
echo ""
echo "----------------------------------------"
echo "| Running make |"
echo "----------------------------------------"
echo ""
make
When you run the build.py
script, the compiled library is stored at Unix/build-{distribution}/lib/libmi.so
(or libmi.dylib
for macOS). If one of your favourite distributions is missing you can add your own distribution_meta/{distribution}.json
file with the relevant details. I’m happy to add any more distributions to the list of ones built in CI.
Installation
If you haven’t compiled it yourself you can even get a prebuilt copy from the releases page. Make sure you select the correct distribution you want to use it on. Now that you’ve gotten a copy of the libmi
file, you simply need to copy it into the PowerShell directory. I recommend you copy the existing file in case you need to revert back to what was shipped with PowerShell at some point in the future.
PWSHDIR="$( dirname "$( readlink "$( which pwsh )" )" )"
LIBMI_NAME="$( basename "$( ls "${PWSHDIR}/libmi."* | head -n1 | awk '{print $1;}' )" )"
# Will probably need sudo for these 2 commands
cp "${PWSHDIR}/${LIBMI_NAME}" "${PWSHDIR}/${LIBMI_NAME}.bak"
cp "Unix/build-{distribution}/lib/${LIBMI_NAME}" "${PWSHDIR}/"
Once copied, any new PowerShell processes will now load the new library unlocking WSMan on Linux to it’s close to full potential. If you are wanting to make sure you have done this correctly, simply run the Get-OmiVersion.ps1 script that is in the repo.
$> pwsh -File tools/Get-OmiVersion.ps1
Major Minor Build Revision
----- ----- ----- --------
1 0 1 0
If that fails then you are still using the builtin version without any of the changes in the fork.
In Action
So now that I’ve got the new mi
library installed it’s time to use it in PowerShell. I don’t want to bore you with the details so here is it in action.
In this example I am connecting using explicit credentials and have specified the Negotiate
protocol. The WSMan client will attempt to get a Kerberos ticket for those credentials and then start the Kerberos authentication process for SPN HTTP/server2019.domain.local
. If any of those steps fail it will automatically fallback to using NTLM for the authentication exchange if the NTLM library is installed, otherwise it will fail. I could have also set -Authentication Kerberos
instead of Negotiate
if I wanted to ensure only Kerberos was used without the NTLM fallback. This could be ideal in your environments as NTLM is an old protocol and should be avoided where possible.
For example if I tried to connect using an IP address and Kerberos with Enter-PSSession 192.168.56.15 -Authentication Kerberos -Credential $cred
, it will fail to find the target SPN in the domain.
Enter-PSSession: Connecting to remote server 192.168.56.15 failed with the following error message : Authorization failed Unspecified GSS failure. Minor code may provide more information Server not found in Kerberos database For more information, see the about_Remote_Troubleshooting Help topic
Finally, a new addition in my mi
fork is the ability to utilise the existing Kerberos ticket cache and credential delegation. If you’ve already gotten a Kerberos ticket for a user account using something like kinit
, you can omit the credential altogether when creating your PSRemoting session like so.
You can even bypass the kinit
step yourself and automatically get a Kerberos ticket as part of the login process for your Linux account using the pam-krb5
package. In this last example I also made sure I requested a forwardable ticket using the -f
flag to kinit
. When you have a forwardable ticket and the SPN you are targeting is set as trusted for delegation in your domain then the PSRemoting session will be able to delegate your credentials. This means my credentials were delegated to the remote process allowing me to re-authenticate as that user for any outbound connections in that PSRemoting session. You can’t do that with SSH public key authentication :).
While you still cannot use the new Exchange Online v2 Modules you can still use something like New-EXOPSSession to connect to Exchange Online using modern auth. With that script you can import that Exchange Online PSSession and use many of the cmdlets to manage your Exchange instance.
Limitations
While the changes I’ve made in the fork solve a lot of the limitations with WSMan on Linux, there are a few problems that cannot be reasonably fixed. Some of the limitations are:
- Basic auth over HTTP will always be disabled
- There is a hard coded check in PowerShell that disables this
- Honestly this is a good thing, using Basic auth over HTTP has no encryption so everything would be in plaintext
- HTTPS connections have no certificate verification, reducing the effectiveness the protocol brings
- This is another hard coded check in PowerShell
- Even if cert verification was implemented in OMI we cannot remove that check in PowerShell
- This is unfortunate but also understandable, better to make people explicitly opt into disabling cert verification than think it is working
- Edit: This has been fixed since v1.2.0 of the fork release where cert verification is now enabled by default
- Edit2: Since the v2.0.0 release when used in conjunction with the PowerShell 7.2.0 release, it is no longer required to set the skip check options
- No CredSSP authentication
- While you could add this to
mi
there is another check inpsrpclient
that fails when-Authentication
is notBasic
,Negotiate
, orKerberos
- While you could add this to
- You always need to set
-Authentication Basic|Negotiate|Kerberos
unlike Windows where omiting the parameter usesNegotiate
auth- Due to the same check above, only those 3 will work
- Edit: Since the v2.0.0 release,
libpsrpclient
has also been forked and changed to fix this problem, now the default auth isNegotiate
removing the need to set this for default scenarios.
- NTLM auth on macOS only works over HTTPS
- This is due to a problem in macOS’ GSSAPI implementation which doesn’t implement NTLM wrapping when used in SPNEGO properly
- Once again nothing we can do about this
So even if I implement the features in OMI, PowerShell or psrpclient
will still fail due to those hardcoded checks.
Future
The MVP that I wanted out of this whole process was a library I can use to connect over WSMan to Windows targets and to Exchange Online. I think what I have right now meets that requirements but there is always room to improve things in the future. Some of the things that I think would be nice to add in the future are
- More distributions, maybe even a “universal” release like the one that OMI provides
- Improve the error messages for some known problems, right now failures can be quite vague in the error messages it reports
- Improve the logging situation, right now it requires a config file at
/opt/omi/etc/omicli.conf
to configure the logging details
If you decide to try out these changes, I’m happy to help as best as I can with any issues you find. Feel free to open a new issue but keep in mind this is work I do in my spare time. I cannot guarantee I will look at the issue in a timely fashion or even come up with a fix.
As for trying to get these changes included in PowerShell itself, I doubt there would be any appetite in the PowerShell team to do this. There is no guarantee of support and the general consensus is that WSMan is deprecated on Linux and what it ships with will be it. If, in the future, they would like to pursue this further and look into getting these changes in their version of mi
then I’m more than happy to work together to achieve that goal.
Edit: Since publishing this blog and officially releasing these changes there’s been a few changes and additions made to my fork. These changes are
- The forked libraries have been published under the PSWSMan module on the PowerShell Gallery
- People wishing to use my fork can install this module and run (as root)
Install-WSMan
to install my changes
- People wishing to use my fork can install this module and run (as root)
- HTTPS cert validation is enabled by default
- To disable cert validation you can use the
Disable-WSManCertVerification
cmdlet included withPSWSMan
- When PowerShell 7.2.0 is released, you will also be able to disable cert verification per session using the
-SessionOption (New-PSSessionOption -SkipCACheck -SkipCNCheck)
options
- To disable cert validation you can use the
- More distributions like Arch Linux and Alpine have been added
- With
libpsrpclient
being shipped withPSWSMan
it will now be easier to add more distributions not supported by PowerShell directly
- With