(C# && SSPI && VMWARE®) == PITA

Yesterday I met a formidable adversary.  Turns out that SSPI and VMWARE had been waiting for me in the alley.  I needed to figure out how to login to the host server using the VMWARE api.  The challenge was the lack of first class support for SSPI in C# (At least I couldn’t find it).

images (1)

Some backstory:

We are writing integration tests to exercise our integration with the Kafka cluster.  Being the trendy guy that I am, I created a dockerfile that built a CentOS image  with a standalone Kafka.  I felt like I was floating on air (ok that was a bit much).  Anywho, we had been running our tests with the docker image when someone decided to tell Dev/Ops.

As expected, sharing our new awesome was another trap.  After a lot of (not fun) discussion, we came to an agreement that they will build us images on VMWARE that we can control the setup and teardown.  It really didn’t matter to me as long as we had some image that is clean when started and it seems like it will be delivered.

Luckily the guy that I was working with in Dev/Ops is cool and was willing to get some bare bone images created so that we can start creating the test harnesses.  And… this is where it went sideways.

I put on my hard hat and started to dig in.  I was at the top of my game!  My google skills were strong so I set out on the hunt for that needle.  I found lots of examples, as usual, but all of the examples did not work.  If you search for C# and SSPI you will get a million examples of a class called SSPIHelper.  When I sought out that mystical class, I found that it was internal in the BCL.  I found other references to some PInvoke imports, but I wasn’t interested in that.  Did I mention that I am lazy?

Finally I came across a sample application nuget package that was developed by Microsoft. After finding some information on how that is used, I had a starting point.  The end goal here is to get a token.

var clientCredential = new ClientCredential(Credential.Package.NTLM);
var clientContext = new ClientContext(clientCredential, 
"", ClientContext.ContextAttributeFlags.None);
var token = Convert.ToBase64String(clientContext.Token);

The next order of business is to start to use the VMWARE SDK to connect to the environment. We need to use the VimClient and get a session to connect to the server.

var client = new VimClient();
client.Connect(host, CommunicationProtocol.Https, 443);
var sessionManager = new SessionManager(client, client.ServiceContent.SessionManager);

This gets us ready to login, but I want to point out some silliness.  If you look above at the input parameter for the SessionManager class you might see something strange.  It takes a VimClient instance and the SessionManager that is attached to the VimClient.  I am curious why they didn’t just use the client instance and then inside the constructor access the field on the client, but Judge lest ye be judged.

Ok, now that we have a populated SessionManager, we can call it’s LoginBySSPI method.  The input to the LoginBySSPI is the token that we created above and a locale.  Using the token that we created earlier:

sessionManager.LoginBySSPI(token, "en");

I thought at this point I was solid, but it turns out that this will throw an exception, VimException.  When we  get this on the first try, we need to inspect the FaultMethod and see if it is a SSPIChallenge.  This is a normal part of the security negotiation.  It has a property, when you cast it out, that is the server token that it is responding with.  I will save you the brainache, but this is the token that you need to reseed your client with.  Once you have it, you can call LoginBySSPI again with the new token;

token = ((SSPIChallenge)ex.MethodFault).Base64Token;
clientContext.Initialize(Convert.FromBase64String(token));
token = Convert.ToBase64String(clientContext.Token);
sessionManager.LoginBySSPI(token, "en");

Side bar:  I could have chosen to use the regular login with user name and password.  Since this will be running from a developers desk and the build machine, storing a single user name and password seems like a bad practice.

Now that you have an authenticated session, you can start to interact with the vm environment and do cool things like inspect all of the vm’s that are on the host that you connected to above.  The following will grab all of the vm’s that exist.

IList<VirtualMachine> vms = vimClient
.FindEntityViews(typeof(VirtualMachine), null, null, null)
ConvertAll(r => r as VirtualMachine).ToList();

When you put it all together this is what you get.

var credential = new ClientCredential(Credential.Package.NTLM);
var context = new ClientContext(credential, "", ClientContext.ContextAttributeFlags.None);
var token = Convert.ToBase64String(context.Token);
var client = new VimClient();
client.Connect(host, CommunicationProtocol.Https, 443); 
var sessionManager = new SessionManager(client, client.ServiceContent.SessionManager);

var isUnauthenticated = true;
while (isUnauthenticated)
{
     try
     {
           sessionManager.LoginBySSPI(token, "en");
           isUnauthenticed = false;
     }
     catch (VimException ex)
     {
           if (ex.MethodFault is SSPIChallenge)
           {
                token = ((SSPIChallenge)ex.MethodFault).Base64Token;
                context.Initialize(Convert.FromBase64String(token));
                token = Convert.ToBase64String(context.Token);
           }
           else if (ex.MethodFault is SystemError)
           {
                throw;
           }
        }
     }
}

I hope that this helps someone.  If you have a question or I wasn’t clear, shoot me a comment and I will try to help you!  Cheers

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s