Saturday, October 18, 2014

Multiple web applications in single cloud service web role with multiple SSL certificates and custom domains over https using Server Name Indication


I know the title of this post is quite big and hard to understand in first go. But I wanted to highlight the important aspect I am going to touch in the article and hence such a long name.
Ok, so here in this post I am going to demo how you can host multiple web applications in single cloud service. I know you may say that there are numerous posts on the same topic what is different in here? Yes the difference is there. You may find that most of the posts talks about hosting multiple web sites in single cloud service web role but they do not focus on running them on HTTPS and with custom domain name. Exactly this is what I am explaining in this post.
So in essence, we will see that, how can we host multiple web applications in single cloud service web role. Then we will see how the same applications can be hosted on HTTPS using SSL certificates in single cloud service web role. After that we will extend the talk to hosting same apps with custom domains using new feature of IIS 8.0 called as Server Name Indication (SNI).
Applicable software stack – VS 2013 and above, Azure SDK 2.3 and above, IIS 8.0 and above. [IIS 7 not supported].
Create required project in Visual Studio
This part is pretty straightforward implementation. To start with I created a simple cloud service project in VS2013 and added one web role in it. I renamed the web role to ManegementAppWebRole as shown below –

I changed the default.aspx UI text to just make it identify as Management App. Now I added another ASP.NET web project in the same solution. This will be my survey app. In this also I changed the default.aspx text to just make it identify as Survey app. So in all my projects structure is as follows –

Hosting multiple web applications in the same cloud service web role
Okay, so here I am representing a way by which you can have two web sites hosted in the same cloud service web role. In my case one of the applications is Management app web role itself and another is Survey web application which is not the part of cloud service. So I will run the survey app as part of Management app web role. To configure this full IIS capability can be leveraged. We have a special file in cloud service project know ServiceDefinition.csdef file. My current structure of .csdef file is as shown below –


The <Sites> tag is the key to make this possible. So I just added another site in .csdef file and provided the path of Survey web application. And provided the actual physical path of my survey app. Most importantly if you observe I have provided the host header for my survey app as “surveyapp.kunal.com”. This is the main differentiator of the survey app running in the Management app web role. IIS do not allow to have more than one site listening on the same port without adding host header. We need to provide host header because both of our applications will be running on the same Endpoint port which is 80. Therefore there has to be some way by which IIS can understand the request of user need to be served by which application. Therefore we need host header. The complete structure of my .csdef file is as shown below –

Now in reality my host header site “surveyapp.kunal.com” do not exists. Therefore to make survey app resolve to this URL I need to update “hosts” file of my machine. Hosts file is located at C:\Windows\System32\drivers\etc\hosts. I copied the file to desktop and opened in notepad and added an entry as highlighted below –
Then from desktop I replaced / copy-pasted the hosts file back to its location.
Now let’s run the project in debug mode by pressing F5 and it open the browser with management app with url similar to below – http://127.0.0.1:60123/ Here 60123 is port opened by my emulator, this will different in your case. Open another browser and cope the same url of management app and just replace 127.0.0.1 by our host header name i.e. surveyapp.kunal.com. So final url in new browser window will be - http://surveyapp.kunal.com:60123/and bingo!! I see my management app and survey app running on the same port as shown below –

Let’s run the same implementation in Full IIS and Full emulator mode. This can be configured from the cloud service project properties. To open the properties of cloud service project, right click on Cloud service project and select Properties.
Note – With Azure SDK 2.4 FULL Emulator has been deprecated; however I guess you should be able to run the Full IIS with Emulator Express. This is what I think!!
Ok, now let’s run the project by pressing Ctrl+F5 [DO NOT RUN in DEBUG MOE!!] and here is what I see in browser and IIS –

As you can see, host header has been applied for my Survey Application. This is how we can run multiple web application in single cloud service web role.
Advantage – You are running the tow different web apps in the same IIS. In other words you are running two different web applications in same instance of Azure role and hence you save money. In case of 2 different roles for 2 different web app, you will be charged for role VM instances of 2 applications. In this case you are charged for only one role VM instances.
Now in next part we will see how we can run these web applications over HTTPS and custom domain. This means, I have custom domain mapped for survey as “surveyapp.kunal.com” and now we will have custom domain name updated in hosts file for Management app as “managementapp.kunal.com” plus to run them on https and certificates.
Disclaimer – To make multiple web applications run in web role over different custom domains; of course neither I purchased the custom domains nor certificates from certificate authority. I will demo everything locally; however same actions will be required on production environment while running the apps in cloud service on Azure. I will describe details about running this on azure cloud service in later part of this post.
IMPORTANT [Action for you for next steps]– to host multiple web apps in single cloud service web role over https and custom domain, we are going to use Server Name Indication (SNI) capability of IIS and it is available only with IIS 8.0 and above. Therefore you can use Windows 8/ windows 8.1 or windows Server 2012 server machine to make this possible. I am using Windows Server 2012 server machine for this. I am not sure if windows 7 support IIS 8.0. Also when you publish your cloud service on Azure today; by default you get Server 2012 OS and IIS 8.0. Therefore not to worry about cloud environment J…now moving back to the topic!!
Create Self Signed Certificates
To run web roles over https/ SSL of course you need certificates. For the demo purpose, I am here going to use self-signed certificates created in IIS. So in all we need 2 certificates to be created IIS. Open IIS and then open feature Server Certificates. Click on “Create Self Signed certificate and specify name as “managementapp.kunal.com”. This will create the certificate and now we need to export it as .pfx file. Therefore right click on the generated certificate and select “Export”, provide the file name as “managementapp.kunal.com” and password as Xyz1234# and save to desktop as shown below –

Follow the same procedure for “surveyapp.kunal.com” certificate generation and provide the path of desktop for .pfx file, file name as “surveyapp.kunal.com” and password as Xyz1234#. I added these generated certificates in my web role, under Certificates folder and opened the properties of these .pfx files and changed the property of both – “Copy to output directory” as “Copy Always” option. These certificates are present in the download source code location.
Installing Certificates in Local Machine – Personal (My) store
To install certificates in Local Machine – Personal (My) store refer to the post written by me earlier –
After installation, certificates can be viewed along with names we provided as part Friendly name column.
Making Management app web role SSL enabled

Open web role properties, select “Certificates” tab and click on “Add Certificate”. Choose the Store Location as “LocalMachine” and Store Name as “My”. Click on the browse button and in the pop up select certificate managementapp.kunal.com and its thumbprint will automatically get listed.

Similary I added another certificate of surveyapp also in the same web role properties.
Let’s add the endpoint for 443 https and remove the original endpoint of 80 from the web role properties.

Now here, important point is, we did not select the Survey app certificate for endpoint. The survey app certificate is added in web role properties only for referencing it at runtime.
Making Survey app SSL enabled
Open the properties of survey web application and mark “SSL Enabled” as true.


Map custom domain to web role and web application in .csdef file
This is the most important step. In .csdef file of cloud service project we will map the custom domains and update the hosts file of local machine for both of domains. Now Management app custom domain we want is “managementapp.kunal.com” and survey app to “surveyapp.kunal.com”. Therefore open hosts file and add the entries as below –

Configure Server Name Indication (SNI) in cloud service web role
Server name indication is introduced to overcome the problem of SSL scalability. In real world most of the sites work on https. In IIS prior to 8.0 we could not bind more than one certificate for a single port. Hence we could not achieve same url mapped to many https custom domains. For example, https://myservice.cloudapp.net having endpoint as 80 could not get bind to https://site1.com and https://site2.com. Because both of these custom domains site1 and site2 will require 2 different certificates to get them work on https.
To overcome on this problem primarily SNI was introduced and it allows you to map same port, cloud service url to TWO different custom domains on https. More details about SNI are here.
Ok, so configure the SNI in web role, we need to create host header for the two sites we are planning to host as a part of single web role and mark sslFlag as 1 to enable SNI using C# code on web role OnStart method.
First let’s modify the .csdef file to have host headers for both of the application as shown below.

If you observe, I have marked execution context as Elevated. To make the code of web role OnStart method work we have to run the context in elevated mode. The name of Management App site tag in .csdef file is “Web”. This is special case name treated where in you don’t have to specify actual physical path; however the survey app site tag name is not the special case and therefore we have to specify the physical path like (c:\something). If you change the name of Management app from “Web” to “web1” you will get an error at runtime stating that, “Cannot find physical directory for virtual path Web1/”. I will keep this default which is “Web”.
Now add reference of Microsoft.WebAdministration dll to management web role using nuget as shown below –

Install-Package Microsoft.Web.Administration


To modify the binding of web role and web app at runtime, add following code in web role Onstart method. -


public override bool OnStart()
        {
            try
            {
                // For information on handling configuration changes
                // see the MSDN topic at http://go.microsoft.com/fwlink/?LinkId=166357.

                string certificateSurveyPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Certificates\\surveyapp.kunal.com.pfx";


                string surveySiteName = string.Empty, npsAppSiteName = string.Empty;

                ////install certificate
                var newCert = new X509Certificate2(certificateSurveyPath, "Xyz1234#", X509KeyStorageFlags.MachineKeySet
                    | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
                var readWriteMyStore = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                readWriteMyStore.Open(OpenFlags.ReadWrite);
                readWriteMyStore.Add(newCert);
                readWriteMyStore.Close();

                //create the biding
                var serverManager = new ServerManager();
                foreach (Site site in serverManager.Sites)
                {
                    if (site.Name.Contains("SurveyApplication"))
                    {
                        surveySiteName = site.Name;
                        break;
                    }
                }
                var surveySite = serverManager.Sites[surveySiteName];


                Binding surveyBinding = surveySite.Bindings.Where(x => x.BindingInformation.Contains("surveyapp.kunal.com")).FirstOrDefault();
                surveySite.Bindings.Remove(surveyBinding);

                var binding = surveySite.Bindings.Add(":443:surveyapp.kunal.com", newCert.GetCertHash(), "My");
                binding.SetAttributeValue("sslFlags", 1); //enables the SNI
                serverManager.CommitChanges();

                string certificateNPSPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) + "\\Certificates\\managementapp.kunal.com.pfx";
                //install certificate
                var npsCert = new X509Certificate2(certificateNPSPath, "Xyz1234#", X509KeyStorageFlags.MachineKeySet
                    | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
                var readWriteMyStoreNPS = new X509Store(StoreName.My, StoreLocation.LocalMachine);
                readWriteMyStoreNPS.Open(OpenFlags.ReadWrite);
                readWriteMyStoreNPS.Add(npsCert);
                readWriteMyStoreNPS.Close();

                //create the biding
                var serverManager1 = new ServerManager();
                foreach (Site site in serverManager1.Sites)
                {
                    if (site.Name.Contains("Web"))
                    {
                        npsAppSiteName = site.Name;
                        break;
                    }
                }

                var npsSite = serverManager1.Sites[npsAppSiteName];
               
                //while publish to cloud service uncomment this
                Binding npsBinding = npsSite.Bindings.Where(x => x.BindingInformation.Contains("managementapp.kunal.com")).FirstOrDefault();
                if (npsBinding != null)
                {
                    npsSite.Bindings.Remove(npsBinding);

                }
                var binding1 = npsSite.Bindings.Add(":443:managementapp.kunal.com", npsCert.GetCertHash(), "My");
                binding1.SetAttributeValue("sslFlags", 1); //enables the SNI
                serverManager1.CommitChanges();
            }
            catch (Exception ex)
            {
               
                throw ex;

            }

            return base.OnStart();
        }

If you observe, first I am removing the existing binding that gets created automatically and creating the same binding again with 443 port, associated certificate and custom domain and marking SNI as 1 through the property “sslFlag”.
Warning – The code of management app in web role OnStart method searched for sites containing name as “Web”. Your local IIS 8.0 may have “Default Web Site” running on port 80. The above code of binding change may get applied to “Default Web Site” instead of your actual management app “Web” named site in IIS at runtime and things may not work. Therefore open IIS 8.0 and remove the “Default Web Site” from IIS. Of course you can add it back any time you want. For the demo of SSL scalability in Azure cloud services, remove the “Default Web Site” running on port 80 if you have one.
Running the SSL scalability in IIS 8.0 implementation in Azure cloud service web role locally
We have 2 options in which we can run the implementation of SSL scalability in IIS locally. First is to run the application in Full IIS mode and other is to run in IIS Express and Emulator express mode. Let’s have a look in Full IIS mode. Out of this Emulator express and IIS express mode will not work as our code of web role Onstart method tries to find the sites in local machine IIS and not IIs express. Hence in case of IIS express we end up in exceptions. Therefore let’s run this application multiple web app in cloud service over https in full IIS and full emulator mode.
Open the properties of cloud service project SNIDemo from solution explorer and select Full IIS mode, Full emulator mode. Now in Full IIS debug will not work therefore run the application by pressing “Ctrl +F5” or by selecting the option “Run without Debug” from Debug menu. Now the explorer will open with some certificate error. Let it be as it is. Now open IIS 8.0 and you will see that, two web sites have been created in local IIS 8.0 but they are in STOPPED state. If you open the binding details of every site, you will observe that, SNI is set for both of them.


Now if you click on Start for management or survey app in IIS 8.0; you will receive an error as “The Process cannot access the file because it is being used by another process.” This is because the Compute emulator is using port 443 and hence the web sites cannot be started and ultimately you cannot see them running. To overcome on this problem WE HAVE TO REMOVE THE CURRENT DEPLOYMENT FROM COMPUTE EMULATOR AND NOT TO REFRESH THE IIS. Open compute emulator and right click on current deployment and select REMOVE. This frees the port 443 being used by emulator. Then minimize the compute emulator and without refreshing IIS select start option for both of the web sites and open the urls in browser https://managementapp.kunal.com/default.aspx and similarly open new browser and type https://surveyapp.kunal.com/default.aspx. You will see the certificate error here; click to continue with web site option and Bingo!!!! Both apps are running over HTTPS with different domain names on the same port 443 in IIS 8.0.



 Full IIS gives you an idea how the sites will get deployed in Azure cloud service role instance after deployment.
Deploying to Azure Cloud Service

The as is application if hosted on Azure cloud service then it will not work. This application with multiple web sites over https in single web role will run over azure cloud service only if you have valid certificates issued by certificate authority and custom domains provided by an Internet Service Provider are mapped to your cloud service URL. So once you have valid certificate and custom domain mapped to YourCloudService.cloudapp.net url; SSL scalability in IIS in Azure cloud service will work.
Hope this article has given you detailed insight on running multiple web application in azure cloud service web role, then running same on HTTPS over SSL using Server Name Indication feature of IIS 8.0.
Important – Please suggest your Feedback/ Changes / Comments to the article to improve it.
Download entire source code with test certificates - https://gallery.technet.microsoft.com/Multiple-web-sites-in-942e153e
Cheers!!

No comments:

Post a Comment