This blog post will explain how to use self signed certificates with a Windows Azure cloud service and how to configure the Azure IIS server by code in the web role. I’m assuming that you have created a cloud service in the management portal and read my two earlier blog posts about “creating self signed certificates” and how to “configure IIS to use them”. So you already have a root CA certificate, a server ssl certificate with “CN=” matching your azure cloud service domain name and is signed by your root CA as well as a client certificate (or more) also signed by your root CA for authentication ready.
Please note that all visiting clients/users need to have your root CA in their Trusted Root Certificate Authorities store on their local machine as well as the client certificate in their Current User store. This means that your clients/users must be willing to install your self signed certificates if you don’t want them to get a big fat trust warning when visiting your website. If you are making a closed intranet solution of sorts and have access to all client machines, then you can use the self signed certificates approach but if not you should purchase your certificates as well as a domain and a static IP address.
Either way self signed certificates are great for development!
I’m using a PC with Windows 8.1 Pro and Visual Studio Premium 2013 with Update 4 and Azure SDK 2.5 installed.
Creating our sample project
Let’s start from scratch and make a really simple cloud service project. You can also clone mine here. (I included my self signed certificates and scripts that I used for the example in the utilities project.)
Add a web role and rename it if you like
We’ll need a simple controller
using System.Web.Http; namespace AzureWithCertificatesSample.WebApi.Controllers { public class NinjaController : ApiController { public IHttpActionResult Get() { return Ok("HAAAAAIIYA!!! I'm in ^_^"); } } }
Open up your cloud role properties by double clicking the web role located in the Roles folder of the cloud service project.
Go to Certificates and add your server certificate. You can fill in the thumbprint manually without whitespace and if you are copying it directly from your certificate or mmc make sure not to include the invisible unicode characters. If your certificate is located in your store just click the … button and choose it directly from your Local Machine store. I made a wildcard server certificate with “CN=*.cloudapp.net” for simplicity because then I can use this certificate for both staging and production.
Now we will set up an HTTPS endpoint with this server certificate. I’m renaming the HTTP endpoint that was already there and adding my HTTPS endpoint with port 443 and my server certificate.
Uploading the Certificate Authority certificate to Azure
Azure doesn’t allow you to install certificates directly into it’s Trusted Root Certification Authorities store, so we will have to do this with a startup task.
1) Add your CARoot.cer file to the bin folder and make sure the Build Action is set to ‘Content’ and the Copy to Output Directory is set to ‘Copy Always’.
2) Create a command batch file with the startup task command
certutil -addstore root CARoot.cer
(Change to your .cer file name if it’s not CARoot.cer)
Put this in your bin folder and include it in the project as well as making sure that the Build Action is set to ‘Content’ and the Copy to Output Directory to ‘Copy Always’.
3) Now add the following xml to your ServiceDefinition.csdef in the cloud project right under the <WebRole> starting tag:
<Startup> <Task commandLine="AddCertToTrustedRootCA.cmd" executionContext="elevated" taskType="simple" /> </Startup>
The azure server will run the AddCertToTrustedRootCA.cmd on startup and will make sure your CARoot.cer is trusted.
Uploading the server certificate to Azure
If you want to use your own domain name , follow these steps and come back with a server certificate with a CN parameter that matches your domain name.
Upload your server certificate .pfx file to your Azure cloud service management portal.
NOTE: You might get some problems with running the application locally with the cloud emulator. Try to change the cloud emulator to run in Full mode instead of Express and for that to work you need to start Visual Studio in administrator mode.
IIS Client Certificate Authentication
We need to enable this on the Azure server’s IIS and since we want to be able to scale up to multiple servers with the same configuration we need to have a script and code approach:
1) Create a new command batch file to install the IIS Client Certificate Mapping Authentication server role with the following command
@echo off @echo Installing "IIS Client Certificate Mapping Authentication" server role powershell -ExecutionPolicy Unrestricted -command "Install-WindowsFeature Web-Cert-Auth"
2) Put the command batch file in your web role bin folder, with the Build Action set to ‘Content’ and the Copy to Output Directory set to ‘Copy Always’
3) Add it to the <Startup> node in the ServiceDefinition.csdef
<Task commandLine="InstallServerRoleIISClientCertMappingAuth.cmd" executionContext="elevated" taskType="simple" />
We’ll configure some IIS settings by code and add another startup task so whenever we want to scale up our instance count we don’t have to do anything manually on each server. I’ll add code for creating both the many-to-one mapping and the one-to-one mapping.
The following code will configure the applicationHost.config of our site’s IIS to map the client certificate to a user and set up rules for validation. We will also tell IIS to use SSL and require a SSl certificate. Let’s do it piece by piece:
1) Install the IIS.Microsoft.Web.Administration nuget package
2) Open up your WebRole.cs and replace the class with:
public class WebRole : RoleEntryPoint { public override bool OnStart() { try { using (var server = new ServerManager()) { const string site = "Web"; var siteName = string.Format("{0}_{1}", RoleEnvironment.CurrentRoleInstance.Id, site); var config = server.GetApplicationHostConfiguration(); ConfigureAccessSection(config, siteName); var iisClientCertificateMappingAuthenticationSection = EnableIisClientCertificateMappingAuthentication(config, siteName); ConfigureManyToOneMappings(iisClientCertificateMappingAuthenticationSection); ConfigureOneToOneMappings(iisClientCertificateMappingAuthenticationSection); server.CommitChanges(); } } catch (Exception e) { Debug.WriteLine(e); // handle error here } return base.OnStart(); } private static void ConfigureAccessSection(Configuration config, string siteName) { var accessSection = config.GetSection("system.webServer/security/access", siteName); accessSection["sslFlags"] = @"Ssl,SslNegotiateCert,SslRequireCert"; } private static void ConfigureOneToOneMappings(ConfigurationSection iisClientCertificateMappingAuthenticationSection) { var oneToOneMappings = iisClientCertificateMappingAuthenticationSection.GetCollection("oneToOneMappings"); var oneToOneElement = oneToOneMappings.CreateElement("add"); oneToOneElement["enabled"] = true; oneToOneElement["userName"] = @"ValidUsernameOfServerUser"; oneToOneElement["password"] = @"PasswordForTheValidUser"; oneToOneElement["certificate"] = @"Base64-Encoded-Certificate-Data"; oneToOneMappings.Add(oneToOneElement); } private static ConfigurationSection EnableIisClientCertificateMappingAuthentication(Configuration config, string siteName) { var iisClientCertificateMappingAuthenticationSection = config.GetSection("system.webServer/security/authentication/iisClientCertificateMappingAuthentication", siteName); iisClientCertificateMappingAuthenticationSection["enabled"] = true; return iisClientCertificateMappingAuthenticationSection; } private static void ConfigureManyToOneMappings(ConfigurationSection iisClientCertificateMappingAuthenticationSection) { var manyToOneMappings = iisClientCertificateMappingAuthenticationSection.GetCollection("manyToOneMappings"); var element = manyToOneMappings.CreateElement("add"); element["name"] = @"Allowed Clients"; element["enabled"] = true; element["permissionMode"] = @"Allow"; element["userName"] = @"ValidUsernameOfServerUser"; element["password"] = @"PasswordForTheValidUser"; manyToOneMappings.Add(element); ConfigureCertificateRules(element); } private static void ConfigureCertificateRules(ConfigurationElement element) { var rulesCollection = element.GetCollection("rules"); var issuerRule = rulesCollection.CreateElement("add"); issuerRule["certificateField"] = @"Issuer"; issuerRule["certificateSubField"] = @"CN"; issuerRule["matchCriteria"] = @"CARoot"; issuerRule["compareCaseSensitive"] = true; var subjectRule = rulesCollection.CreateElement("add"); subjectRule["certificateField"] = @"Subject"; subjectRule["certificateSubField"] = @"CN"; subjectRule["matchCriteria"] = @"ClientCert"; subjectRule["compareCaseSensitive"] = true; rulesCollection.Add(issuerRule); rulesCollection.Add(subjectRule); } }
IMPORTANT: Change the @”ValidUsernameOfServerUser”; and @”PasswordForTheValidUser”; of both the many-to-many and the one-to-many mappings to match the credentials of a valid user on your server as well as the change the @”Base64-Encoded-Certificate-Data”; to match your client certificate. Also remember to change the many-to-many mapping rules if you need to.
In order to give your web role permission to alter the iis applicationHost.config file, add the following line right under the <WebRole> in the ServiceDefinition.csdef:
<Runtime executionContext="elevated" />
Let’s deploy!
Right-click your cloud service project and go to Publish… ➜ choose your subscription ➜ Go next ➜ Choose your settings and publish.
I’m going to enable Remote Desktop for all roles so I can take a look inside my azure instance and enable web deploy as well.
In the Advanced Settings I’ll choose one of my storage accounts and enable remote debugging in case I want to attach Visual Studio to the Azure process later to debug.
If you get an error like this:
Then unload the cloud service project
And add this to the first <PropertyGroup> node:
<ServiceOutputDirectory>C:\Azure\</ServiceOutputDirectory>
Reload the project and you should now be able to deploy.
Client Access
Install a client certificate that matches either of the mapping we created in the step before in the Current User store on the client device and the browser should prompt you for it. I’ve added it to my machine here and it works like a charm!
That’ll be all for now Folks! Hope you found this useful ^_^
Great articles on dealing with SSL and Cloud Services….thanks so much. Your article got me over a big hump I was stuck on. I was unable to upload any .PFX I had been creating with pvk2pfx to the cloud service, they always failed with a generic message. I was able to export a cert from the MMC to a .PFX and upload it but I could never get one to Azure that was created with pvk2pfx.exe.
I noted your version of the command line for pvk2pfx.exe to create the .PFX was using the password option and I had not been using this, low and behold this is the password I need to use with Azure. I guess now that I know what was happening is that I was creating this second password during the export from MMC when I thought I was entering the password used when creating the certificate in the first place.
Would be a great followup to explain why we have the password for the .CER file and another for the pvk2pfx and when to know when\why\which to use.
Thanks again,
Mike Schellenberger
Hi,
After reading your wonderful article, I have already finish the job deploying my authenticated web service on the Azure. But I still got one question. Can we modify the path for the browser to access our certificates? As you can see, the default path is CurrentUser\Personnal. So I am wondering that there have some sort of settings can let me change the path for example to LocalMachine\TrustedRoot ?
Thank you so much.
Sharry
Wow, great article! Let me tell you I love your awesome band “Frantic Amber”, and I never thought you are an IT professional at the same time :)
Thank you. This really helped :)
For trusted root CA it should be
certutil -addstore AuthRoot filename.cer
AuthRoot instead of Root
Pingback: itemprop="name">How To Configure Iis On Windows 7 Professional | Goods News
will one to one and one to many mapping work if VM instances increased to more than 1, and site is access via LB?