Sunday, December 4, 2011

SSL, IIS, and Host Headers


There is a lot of confusion about how IIS handles SSL.  With all the confusion out there, I thought I should put together a quick post.  This post will also explain the error message: At least one other site is using the same HTTPS binding and the binding is configured with a different certificate.

My discussions this week were specific to SharePoint, but the confusion is with host headers in IIS.  The crux of the issue is that when IIS receives a request, the host header is encrypted.  Therefore, IIS cannot determine which web site to route the request to.

Let’s start with a simple example without SSL.  Say that I have two URLs http://www.brianbeach.com and http://blog.brianbeach.com.  If I want to host them both on the same server, I have to set up bindings.  I can tell IIS to listen on a specific IP address and port combination, or I can use host headers.  See the image below.







If we look at the individual bindings, notice that IIS is listening on all IP addresses for any request to www.brianbeach.com on port 80.













So, how does this work?  When your browser requests a page from www.brianbeach.com, it first resolves the IP address from DNS.  Let’s say that IP address 192.168.1.2.  Then, it sends a TCP packet to 192.168.1.2 on port 80.  The body of the request looks like this.
GET http://www.brianbeach.com/
HTTP/1.1
Accept: text/html, application/xhtml+xml, */*
Accept-Language: en-US
User-Agent: Mozilla/5.0
Accept-Encoding: gzip, deflate
Connection: Keep-Alive
Host: www.brianbeach.com
When, IIS receives this request, it reads the last line, and routes it to the www.brianbeach.com site, based on bindings we configured above.

Herein lies the problem.  Note that the host header (Host: www.brianbeach.com) only exists inside the body of the message.  If I sent this request over SSL, IIS would not be able to read the host header until it decrypted the message.  And, IIS cannot decrypt the message until it knows which site’s private key to use.  Alas, a catch 22.

Therefore, it would seem that I cannot use host headers with SSL.  In fact, notice that when I create an http binding, the host header is disabled.  So, it would seem that, if we use SSL, we can only create bindings based on the IP address and port.  In general this is true, but there is an exception: wildcard certificates.













A wildcard certificate is a certificate that can be used to secure multiple sub-domains.  For example, a wildcard certificate, created for *.brianbeach.com can be used for both www.brianbeach.com and blog.brianbeach.com.  Notice that when I select a wildcard certificate in IIS, the host header textbox is again enabled.













If you have been paying attention, you should be asking yourself: How does IIS know which certificate to use?  Now I have two sites that both use the same certificate, but IIS still needs to decrypt the message to determine if it is destined for one of the two site that use this certificate.  We could be hosting other site after all.

IIS does this based on the port number.   You can only configure one certificate per port.  First, IIS looks up the certificate based on the port the message was received on.  Next, it decrypts the message and reads the host header.  Finally, it uses the host header to route your request to the correct web site.

Note: that this only works with wildcard certificates and subdomains.  I cannot host www.brianbeach.com and www.someothersite.com on the same server, because they do not use the same certificate.  Also, note that wildcard certificates only work for a single level.  Therefore, I cannot host www.blog.brianbeach.com using *.brianbeach.com.


BTW: Have you ever received the warning message below?  When you try to configure a new https binding on a port that is already in use by another site, IIS warns you that you are about to change the SSL certificate for all sites listening on that port.



Friday, September 30, 2011

SharePoint 2010: Full Trust Proxy


If you’re using the multi-tenant features of SharePoint, you will want tenants to use the sandbox.  But, you will quickly find limitations.  For example, developers cannot call a web service, read data from a external database, or write to the event log.  One solution is for the farm administrator to deploy a full trust proxy that developers can use.  Microsoft has a good description here, but there are no good examples.  So I created one.

Let’s create a full trust proxy that will allow developers to write to the event log.  Sandbox code is executed in SPUWorkerProcess.exe (see diagram below).  The Code Access Security (CAS) policy in this process does not allow developers to access the event log.  Therefore, we will write a full trust proxy that will marshal the call to SPUWorkerProcessProxy.exe which can access the event log.


Figure 1: http://msdn.microsoft.com/en-us/library/ff798433.aspx

Create the FullTrustProxy

Start by creating a new “Empty SharePoint Project” that is deployed as a “farm solution”.  Remember this is the solution that the farm administrator deploys.  The tenant developer’s code will be deployed as a sandbox solution.

Next, add a new class called EventLogProxyArgs that inherits from Microsoft.SharePoint.UserCode.SPProxyOperationArgs.  This is the class that will hold the data that needs to be marshaled from SPUWorkerProcess.exe to SPUWorkerProcessProxy.exe.  Therefore it needs to be marked with a Serializable attribute.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.UserCode;

namespace BrianBeach.FullTrustProxy.Diagnostics
{
    [Serializable]
    class EventLogProxyArgs : SPProxyOperationArgs
    {
        private string source;
        private string message;

        public string Source
        {
            get { return source; }
            set { source = value; }
        }

        public string Message
        {
            get { return message; }
            set { message = value; }
        }
    }
}

Now, add a another class called EventLogProxy that inherits from Microsoft.SharePoint.UserCode.SPProxyOperation.  You will need to implement the Execute method.  The execute method is passed a copy of the EventLogsProxyArgs we created above.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.UserCode;

namespace BrianBeach.FullTrustProxy.Diagnostics
{
    class EventLogProxy : SPProxyOperation
    {
        public override object Execute(SPProxyOperationArgs args)
        {
            string source = ((EventLogProxyArgs)args).Source;
            string message = ((EventLogProxyArgs)args).Message;
            System.Diagnostics.EventLog.WriteEntry(source, message);
            return null;
        }
    }
}

Create the Feature

Start by adding a new farm feature and fill out the title and description.



Now add an event receiver.  The event receiver will register the proxy with SharePoint. The code is below:

using System;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Security;
using Microsoft.SharePoint.UserCode;
using Microsoft.SharePoint.Administration;

namespace BrianBeach.FullTrustProxy.Diagnostics.Features.Feature1
{

    [Guid("2f2d1b78-c30b-4197-ba44-06052b97448c")]
    public class Feature1EventReceiver : SPFeatureReceiver
    {
        public override void FeatureActivated(SPFeatureReceiverProperties properties)
        {
            SPUserCodeService service = SPUserCodeService.Local;
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba8f794a3acc2269";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            SPProxyOperationType operation = new SPProxyOperationType(assembly, type);
            service.ProxyOperationTypes.Add(operation);
            service.Update();
        }

        public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
        {
            SPUserCodeService service = SPUserCodeService.Local;
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=ba8f794a3acc2269";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            SPProxyOperationType operation = new SPProxyOperationType(assembly, type);
            service.ProxyOperationTypes.Remove(operation);
            service.Update();
        }
    }
}

Deploy and Test

You’re ready to deploy your package.  Tenant developers can call the proxy from the sandbox.  Here is an example:

string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e6c3143148ad819b";
string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
EventLogProxyArgs args = new EventLogProxyArgs();
args.Source = “SP2010 Sandbox”;
args.Message = “A message from the sandbox!!!”;
SPUtility.ExecuteRegisteredProxyOperation(assembly, type, args);

Now, if you’re like me, the thought of writing seven lines of code to write one line to the log is crazy.  Therefore, I  added one more class to my solution with a helper method tenant developers can call.

Add a class called EventLog.  You will need to mark this class with the AllowPartiallyTrustedCallersAttribute attribute so that users can call it from the sandbox.  NOTE: I also marked the EventLogProxy and EventLogProxyArgs classes as internal so that developers would not be confused by them.  The code is below:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint.Utilities;

[assembly: System.Security.AllowPartiallyTrustedCallersAttribute()]
namespace BrianBeach.FullTrustProxy.Diagnostics
{
    public class EventLog
    {
        public static void WriteEntry(string Source, string Message)
        {
            string assembly = "BrianBeach.FullTrustProxy.Diagnostics, Version=1.0.0.0, Culture=neutral, PublicKeyToken=e6c3143148ad819b";
            string type = "BrianBeach.FullTrustProxy.Diagnostics.EventLogProxy";
            EventLogProxyArgs args = new EventLogProxyArgs();
            args.Source = Source;
            args.Message = Message;
            SPUtility.ExecuteRegisteredProxyOperation(assembly, type, args);
        }
    }
}

At this point a developer can write to the event log with a single line of code as follows:

EventLog.WriteEntry(“SP2010 Sandbox”, “A message from the sandbox!!!”);

Finally, you can download the solution here.

you will likely run into issues.  If you need to check which proxies are registered, you can use the following PowerShell script.

$Service = [Microsoft.SharePoint.Administration.SPUserCodeService]::Local
$service.ProxyOperationTypes | Format-List

Friday, September 23, 2011

Multi-Tenant TCO


I recently presented an overview of multi-tenancy in one of my MBA classes.  I discussed how multi-tenant applications allow cloud vendors to achieve economies of scale.  As an example, I showed the effect on total cost of ownership for the infrastructure if a cloud vendor moved from physical to virtual and then to a multi-tenant architecture.  The multi-tenant architecture cuts cost by 80%.



Current State

Imagine that you work at a midsize company that develops and hosts a SaaS (Software as a Service) solution.  You have been growing recently and now have 100 customers.  Each customer gets its own server that your company hosts in its datacenter.

You are currently supporting 100 physical servers.  Unfortunately, the average utilization is only about 10%.  Over three years, the total cost of ownership is about $2.2M ($1M for hardware acquisition, $400K for power, and $800K for support).

Virtualization

Now imagine that IT moves to virtualization.  If we are only 10% utilized, it follows that we only need 10 physical servers (each hosting 10 virtual servers).  Leaving some room for growth and sudden spikes in demand, we decide to be safe and buy 20 servers (each hosting 5 virtual servers).  

This cuts our acquisition costs by 30%, and our power costs 80%, but notice that our support costs have gone up by 20%.  Why?  We now have 20 additional servers to support (100 VMs and 20 physical).

Multi-Tenancy

Now image that we rewrote our applications to allow us to host multiple clients on the same server (i.e. multi-tenant).  Again we decide to be safe and buy 20 servers.  But, note that we longer need the 100 virtual servers.  Therefore, our acquisition costs drop dramatically.  We just cut $500K in licensing costs.  In addition, out support team is only supporting 20 servers saving another $750K.

Conclusion

Multi-tenant applications have dramatic impact on total cost of ownership.  This allows cloud vendors to achieve economies of scale.   What does this mean for the CIO?  In general the best option for the average IT department is virtualization.  Therefore, a SaaS solution can often be cheaper than hosting a solution in your own datacenter.  This is true even after the vendor applies his markup.

BTW: Checkout the spreadsheet here.  You can adjust my assumptions and tune the results.