It’s been a couple months since we last looked at Windows Azure Pack so before we jump into the thick of it lets recap.
- Windows Azure Pack is an awesome on-premise private cloud platform
- The interactive portions are broken down into two sections: admin areas and tenant areas
- It relies on JWTs as bearer tokens to authenticate between UI surfaces and backend web services
- It uses federation to authenticate users at two separate Security Token Services using WS-Federation
- A JWT is used as the token in the WS-Fed protocol
- You can use your own STS or ADFS to authenticate users
All coming back now? Good! With that out of the way lets talk web services!
Web Services
Most of the management functionality in Windows Azure Pack is exposed via HTTP web services. This means that if we ever wanted to interact with Windows Azure Pack we could easily call into these web services and bypass the UI. This helps with automation or when there are requirements that don’t work through the UI — I don’t really need to explain to an audience of developers why API’s exposed by web service are useful.
As mentioned in previous articles there are two primary web services – one web service for each area (admin and tenant) – that drive the management UIs. You can do pretty much whatever you want by calling into these APIs, but they aren’t really designed for public consumption. These web services rely on JWTs to properly authenticate requests and getting a JWT minted for you to use may be more effort than you’re willing to put in.
Luckily this is where another web service comes in to play – the Tenant Public API. This API is designed to be consumed by public clients and also has the added benefit of being compatible with the Windows Azure service management API. This web service supports TLS Client Certificate authentication instead of relying on JWTs in the request header. This means you only need to generate a client certificate and upload it into the tenant management portal once, whereas with a JWT you’d need to request a new token every few hours. Of course, this also means that if the client certificate is compromised an attacker has free reign over the service until it is removed from the management portal. Tradeoffs…
Now, client certificate authentication has been around for quite awhile. IIS has supported this for as long as I can remember, but there was one very important caveat to this: the client certificate had to be CA-signed by a trusted CA.
If you’re wanting to enable authentication through client certificates you really need PKI, which is a PITA. In the case of Windows Azure Pack, I think the developers figured out a nice workaround which was to override the CA chain validation. This is just a theory and pure speculation though.
If you take a look at the MgmtSvc-TenantPublicAPI web.config file you’ll notice it’s very much like the rest of the web.config files in the other sites, but this one is unique in that it has a custom HttpModule.
<add name=”UxCertAuthModule”/>
If you’re familiar with how HttpModules work, you’ll notice this is actually an ISAPI module.
If you’re not familiar with how HttpModules work here’s a quick primer. IIS 7+ supports .NET HttpModules which can run all requests through these modules. This allows the developer to override a lot of relatively low-level processing of the request. Previous versions of IIS didn’t support this as .NET HttpModules could only be activated on requests going through the ASP.NET pipeline. Normally these modules require a fully qualified type which looks kind of like
Fully.Qualified.Namespace.Type, Assembly.Name, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
. One nice characteristic of .NET modules is that they can be configured to load per site or virtual application.
You can do quite a number of things with .NET modules, but certain low-level functionality can’t be overridden because the module is loaded too late in the IIS request lifecycle. As such you also have unmanaged HttpModules which are more affectionately called ISAPI modules and can interact with pretty much any part of the request lifecycle. These modules are written in C++ and have to be installed at the server level. When you add them to the site or application you have to reference the name specified in the applicationHost.config file located in the IIS process directory. This is why we only see the module name instead of the fully qualified type.
Normally IIS has already done an initial certificate chain validation by the time .NET modules get access to client certificate information. This means the certificates must have a CA root trusted by the server in the root certificate store before ASP.NET can interact with the certificates, otherwise the request is failed with a 403.16 error code. This particular problem can be solved by either deploying a custom PKI and require everyone to get client certificates issued (what a nightmare that would be), or on upload of the client certificate install it into the root trust. The last option is… scary.
Since neither options are particularly good or practical, and the application code is written in .NET it makes sense to simply skip the check by IIS for this one specific site and do the validation at an early enough point in the ASP.NET request lifecycle.
Anyway, lets get back on track.
Since the public API relies on client certificates for authentication we need to be able to manage which certificates can be used. If you navigate to the tenant management site and login you can take a look under the My Account section for the Management Certificates area.
This is just like the Windows Azure portal so all you have to do is generate a client certificate and upload it. Now you can access the service API. I’ve actually borrowed from the Windows Azure MSDN documentation on how to call into the web services because as I stated above the APIs are pretty much exactly the same.
private static void HttpWebRequestCertificateTest(X509Certificate2 certificate) { HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create( new Uri("https://api-cloud.syfuhs.net/subscriptions/386dd878-64b8-41a7-a02a-644f646e2df8")); request.Headers.Add("x-ms-version", "2010-10-28"); request.Method = "GET"; request.ContentType = "application/json"; request.ClientCertificates.Add(certificate); try { HttpWebResponse response = (HttpWebResponse)request.GetResponse(); ProcessResponse(response); } catch (WebException e) { Console.WriteLine(e.Message); Console.WriteLine("------------------------------------"); ProcessResponse(e.Response as HttpWebResponse); } } private static void ProcessResponse(HttpWebResponse response) { Console.WriteLine("Response status code: " + response.StatusCode); for (int i = 0; i < response.Headers.Count; i++) Console.WriteLine("{0}: {1}", response.Headers.Keys[i], response.Headers[i]); Stream responseStream = response.GetResponseStream(); StreamReader reader = new StreamReader(responseStream); if (response.StatusCode == HttpStatusCode.OK) { var sub = JsonConvert.DeserializeObject(reader.ReadToEnd()); Console.WriteLine("SubId: " + sub.SubscriptionID); } else { Console.WriteLine("Response output:"); Console.WriteLine(reader.ReadToEnd()); } response.Close(); responseStream.Close(); reader.Close(); }
If we run the code we get some output:
Response status code: OK Pragma: no-cache Content-Length: 1074 Cache-Control: no-cache Content-Type: application/json; charset=utf-8 Date: Sat, 03 May 2014 19:57:14 GMT Expires: -1 Server: Microsoft-IIS/8.5 X-AspNet-Version: 4.0.30319 X-Powered-By: ASP.NET SubId: 386dd878-64b8-41a7-a02a-644f646e2df8
Looks like it all worked.
Next time we’ll take a look at interacting with the other web services as well how you can enable the Tenant Public API to support JWTs instead of client certificates.