Recently Microsoft released their on-premise Private Cloud offering called Windows Azure Pack for Windows Server.
Windows Azure Pack for Windows Server is a collection of Windows Azure technologies, available to Microsoft customers at no additional cost for installation into your data center. It runs on top of Windows Server 2012 R2 and System Center 2012 R2 and, through the use of the Windows Azure technologies, enables you to offer a rich, self-service, multi-tenant cloud, consistent with the public Windows Azure experience.
Cool!
There are a fair number of articles out there that have nice write ups on how it works, what it looks like, how to manage it, etc., but I’m not going to bore you with the introductions. Besides, Marc over at hyper-v.nu has already written up a fantastic collection of blog posts and I couldn’t do nearly as good a job introducing it.
Today I want to look at how Windows Azure Pack does authentication.
Architecture
Before we jump head first into authentication we should take a look at how Windows azure Pack works at an architectural level. It’s important to understand all the pieces that depend on authentication. If you take a look at the TechNet articles you can see there are a number of moving parts.
The primary components of Windows Azure Pack are broken down into services. Depending on how you want it all to scale you can install the services on one server or multiple servers, or multiple redundant servers. There are 7+1 primary services, and 5+ secondary services involved.
The primary services are:
- Admin Portal – a UI for backend management (https://admin-cloud.syfuhs.net)
- Admin API (https://admin-cloud.syfuhs.net:30004)
- Admin Authentication Site (https://adminauth-cloud.syfuhs.net)
- Tenant Portal – the equivalent management portal we know from Windows Azure (https://manage-cloud.syfuhs.net)
- Tenant API (https://manage-cloud.syfuhs.net:30005)
- Tenant Public API – the web services we know from Windows Azure (https://services-cloud.syfuhs.net)
- Tenant Authentication Site (https://manage-cloud.syfuhs.net)
- SQL Server (localhost)
To help simplify some future samples I’ve included the base URLs of the services above. Anything public-ish facing has its own subdomain, and the related backend APIs are on the same domain but a different port (the ports coincide with the default installation). Also, these are public endpoints – be kind please!
The Secondary services are for resource providers which are things like Web Sites, VM Cloud, Service Bus, etc. While the secondary services are absolutely important to a private cloud deployment and perhaps “secondary” is an inappropriate adjective, they aren’t necessarily in scope when talking about authentication. Not at this point at least. Maybe in a future post. Let me know if that’s something you would like to read about.
Admin Portal
The Admin Portal is a UI surface that allows administrators to manage resource providers like Web Sites and VM Clouds. It calls into the Admin API to do all the heavy lifting. The Admin API is a collection of Web API interfaces.
The Admin Portal and the Admin API are Relying Parties of the Admin Authentication Site. The Admin Authentication Site is a STS that authenticates users using Windows Auth.
During initial authentication the Admin Portal will redirect to the STS and request a WS-Federation-wrapped JWT (JSON Web Token – pronounced “jot”). Once the Admin Portal receives the token it validates the token and begins issuing requests to the Admin API attaching that unwrapped JWT in an Authorization header.
This is how a login would flow:
- Request admin-cloud.syfuhs.net
- No auth – redirect to adminauth-cloud.syfuhs.net
- Do Windows Auth and mint a token
- Return the JWT to the Admin Portal
- Attach the JWT to the session
It’s just a WS-Fed passive flow. Nothing particularly fancy here besides using a JWT instead of a SAML token. WS-Federation is a token-agnostic protocol so you can use any kind of token format so long as both the IdP and RP understand it. A JWT looks something like this:
Header: { "x5t": "3LFgh5SzFeO4sgYfGJ5idbHxmEo", "alg": "RS256", "typ": "JWT" }, Claims: { "upn": "SYFUHS-CLOUD\\Steve", "primarysid": "S-1-5-21-3349883041-1762849023-1404173506-500", "aud": "https://azureservices/AdminSite", "primarygroupsid": "S-1-5-21-3349883041-1762849023-1404173506-513", "iss": "https://azureservices/WindowsAuthSite", "exp": 1391086240, "group": [ "SYFUHS-CLOUD\\None", "Everyone", "NT AUTHORITY\\Local account and member of Administrators group", "SYFUHS-CLOUD\\MgmtSvc Operators", "BUILTIN\\Administrators", "BUILTIN\\Users", "NT AUTHORITY\\NETWORK", "NT AUTHORITY\\Authenticated Users", "NT AUTHORITY\\This Organization", "NT AUTHORITY\\Local account", "NT AUTHORITY\\NTLM Authentication" ], "nbf": 1391057440 }, Signature: "..."
Actually, that’s a bit off because its not represented as { Header: {…}, Claims: {…} }, but that’s the logical representation.
If we look at the token there are some important bits. The UPN claim is the user identifier; the AUD claim is the audience receiving the token; the ISS claim is the issuer of the token. This is pretty much all the Admin Portal needs for proper authentication. Since this is an administrators portal it should probably do some authorization checks too though. The Admin Portal uses the UPN and/or group membership claims to decide whether a user is authorized.
If we quickly take a look at the configuration databases, namely theMicrosoft.MgmtSvc.Store database, we can see a table called [mp].[AuthorizedAdminUsers]. This table lists the principals that are currently authorized to log into the Admin Portal. Admittedly, we probably don’t want to go mucking around the database though so we can use PowerShell to take a look.
PS C:\Windows\system32> Get-MgmtSvcAdminUser -Server localhost\sqlexpress SYFUHS-CLOUD\MgmtSvc Operators SYFUHS-CLOUD\Steve
My local user account and the MgmtSvc Operators group matches the claims in my token, so I can log in. Presumably its built so I just need a UPN or group claim matched up to let me in, but I must confess I haven’t gotten to testing that yet. Surely there’s documentation on TechNet about it…
As an aside, it looks like PowerShell is the only way to modify the admin user list currently, so you can use Windows groups to easily manage authorization.
So anyway, now we have this token attached to the user session as part of the FedAuth cookie. I’m guessing they’ve set the BootstrapToken BootstrapContext to be the JWT because this token will have to always be present behind the scenes while the users session is still valid. However, in order for the Admin Portal to do anything it needs to call into the Admin API. Here’s the cool part: the JWT that is part of the session is simply attached to the request as an Authorization header (snipped for clarity).
HTTP/1.1 200 OK Cache-Control: no-cache Pragma: no-cache Content-Type: application/json; charset=utf-8 Expires: -1 Server: Microsoft-IIS/8.5 X-AspNet-Version: 4.0.30319 x-ms-request-id: 3a96aabb91e7403b968a8aa9b569ad5f.2014-01-30T04:42:02.8142310Z X-Powered-By: ASP.NET Date: Thu, 30 Jan 2014 04:42:04 GMT Content-Length: 1124 { "items":[ { "SubscriptionID":"386dd878-64b8-41a7-a02a-644f646e2df8", "SubscriptionName":"Sample Plan", "AccountAdminLiveEmailId":"steve@syfuhs.net", "ServiceAdminLiveEmailId":null, "CoAdminNames":[ ], "AddOnReferences":[ ], "AddOns":[ ], "State":1, "QuotaSyncState":0, "ActivationSyncState":0, "PlanId":"Samplhr1gggmr", "Services":[ { "Type":"sqlservers", "State":"registered", "QuotaSyncState":0, "ActivationSyncState":0, "BaseQuotaSettings":[ …snip… ] }, { "Type":"mysqlservers", "State":"registered", "QuotaSyncState":0, "ActivationSyncState":0, "BaseQuotaSettings":[ …snip… ] } ], "LastErrorMessage":null, "Features":null, "OfferFriendlyName":"Sample Plan", "OfferCategory":null, "Created":"2014-01-30T03:21:43.533" } ], "filteredTotalCount":1, "totalCount":1 }
At this point (or rather, before it returns the response *cough*) the Admin API needs to authenticate and authorize the incoming request. The service is somewhat RESTful so it looks to the headers to check for an Authorization header (because the HTTP spec suggests that’s how it be done, REST blah blah blah, et al ).
The authorization header states that it has a Bearer token, which basically means the caller has already proven they are who they say they are based on the fact that they hold a token from a trusted STS (hence “bearer”). In other words they don’t have to do anything else. That token is the key to the kingdom.
Yet another aside: bearer tokens are sometimes considered insecure because anyone who has a copy of one could impersonate the user. With that being said, it’s better to use a token than say send the users password with each request.
Now at this point I can’t say for certain how things work internally as I don’t have any access to the source (I *could* use Reflector but that’s cheating), but since it’s Web API I could guess that they’re using a DelegatingHandler or something similar to do the verification. Vittorio actually has a great sample on how this could be done via what he’s calling Poor Mans Delegation/ActAs. It’s the same principle – receive token on web login, validate token so user can actually log in, keep token, want to call authenticated web service, still have token, stick token in header, web service authorizes token, done. Not too shabby.
Conclusion
So at this point we’ve seen how the Admin Portal authenticates users and how it securely calls its backend web services. Next time we’ll look at how the Tenant Portal does it (SPOILERS: it does it the same way!). Ahem — more specifically, we’ll look at how the Tenant Portal is configured so it can actually trust the token it receives. From there we can take a look at how other IdPs can be configured for log in, and if we’re really wanting to be daring we could build our own custom STS for log in (it’s a wee-bit more complicated than you might think).