Building Scalable and Secure WCF Services

The key to building scalable WCF services is to eliminate binding configurations that could result in server affinity.  For this reason you should avoid bindings that establish a session with the service, such as NetTcpBinding or WsHttpBinding with secure conversation enabled.  Both BasicHttpBinding and WebHttpBinding, however, are sessionless and allow you to call a service multiple times without concern for which physical server responds to the call.  This means the load can be more efficiently distributed across multiple servers.

Download the code for this blog post here.

Nevertheless, there is one wrinkle: by default the WCF HTTP bindings enable Keep-Alive, which can result in server affinity and thereby impede scalability in a load-balanced environment.  Here is a snapshot of 5 calls to a service with HTTP Keep-Alive enabled over SSL.  Notice the tunneling only takes place once because a consistent connection is maintained with the server.  This is fine and dandy when clients are talking to the same back-end server and the SSL handshake only takes place when the connection is established.

fiddler-keep-alive-true

Here is a snapshot of the same five calls but with Keep-Alive disabled. Notice how the handshake takes place each time the service is called.

fiddler-keep-alive-false

To disable Keep-Alive, you need to create a custom binding, because this setting cannot be configured for the standard HTTP bindings.  Here is a web.config file with custom SOAP and REST bindings and Keep-Alive disabled.

<configuration>

  <system.serviceModel>
    
    <services>
      <service name="SecureTransportDemo.Service.GreetingService">
        <endpoint address="Soap"
                  binding="customBinding" 
                  bindingConfiguration="soap-secure-nokeepalive"
                  contract="SecureTransportDemo.Service.IGreetingService"
                  name="soap-nokeepalive"/>
        <endpoint address="Rest"
                  binding="customBinding"
                  bindingConfiguration="rest-secure-nokeepalive"
                  behaviorConfiguration="web"
                  contract="SecureTransportDemo.Service.IGreetingService"
                  name="rest-nokeepalive"/>
      </service>
    </services>
    <bindings>
      <customBinding>
        <binding name="soap-secure-nokeepalive">
          <textMessageEncoding />
          <httpsTransport allowCookies="false" 
                          keepAliveEnabled="false"/>
        </binding>
        <binding name="rest-secure-nokeepalive">
          <webMessageEncoding />
          <httpsTransport manualAddressing="true" 
                          allowCookies="false" 
                          keepAliveEnabled="false"/>
        </binding>
      </customBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="web">
          <webHttp automaticFormatSelectionEnabled="true"
                   faultExceptionEnabled="true"
                   helpEnabled="true"/>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

</configuration>

There’s a problem however – we’re still not getting optimal efficiency for a load-balanced environment.  The ideal solution would be a load balancer in the DMZ that would accept requests from clients over SSL with Keep-Alive enabled, but would turn around and communicate with backend servers over a non-secure channel with Keep-Alive disabled.  For that we need a load balancer capable of SSL pass-through, such as F5’s BIG-IP. (Note that you’ll still want to secure the communication between the load balancer and the service using a mechanism such as IPSec, so that privacy is also maintained inside the corporate firewall.)

ssl-pass-thru

The problem here is that WCF will not allow you to pass credentials, such as username and password, over a non-secure channel. And for good reason: credentials sent in the clear could be intercepted. However, presuming it is safe to send credentials from the load balancer to the backend service from behind a firewall, this is exactly what we want to do.  The trick is to fool WCF into thinking we are using a secure channel when in fact we are not.

Michele Leroux Bustamante has written an excellent article showing precisely how to do this.  It entails creating a custom HttpTransportBindingElement that can assert security capabilities indicating encryption and signing at the transport level when in fact this is not the case.  Essentially the custom binding element has a GetProperty method which returns an implementation of ISecurityCapabilities asserting that a protection level of EncryptAndSign is supported.

public class NonSslAuthHttpTransportBindingElement : HttpTransportBindingElement
{
    public override BindingElement Clone()
    {
        return new NonSslAuthHttpTransportBindingElement
            {
                AuthenticationScheme = AuthenticationScheme,
                ManualAddressing = ManualAddressing
            };
    }

    public override T GetProperty<T>(BindingContext context)
    {
        if (typeof(T) == typeof(ISecurityCapabilities))
        {
            return (T)(object)new NonSslAuthSecurityCapabilities();
        }
        return base.GetProperty<T>(context);
    }
}
public class NonSslAuthSecurityCapabilities : ISecurityCapabilities
{
    public ProtectionLevel SupportedRequestProtectionLevel
    {
        get { return ProtectionLevel.EncryptAndSign; }
    }

    public ProtectionLevel SupportedResponseProtectionLevel
    {
        get { return ProtectionLevel.EncryptAndSign; }
    }

    public bool SupportsClientAuthentication
    {
        get { return false; }
    }

    public bool SupportsClientWindowsIdentity
    {
        get { return false; }
    }

    public bool SupportsServerAuthentication
    {
        get { return true; }
    }
}

The next step would be to create a class extending HttpTransportElement, so that you can specify the transport element for the custom binding in the config file of the service host.

public class NonSslAuthTransportElement : HttpTransportElement
{
    public override Type BindingElementType
    {
        get { return typeof(NonSslAuthHttpTransportBindingElement); }
    }

    protected override TransportBindingElement CreateDefaultBindingElement()
    {
        return new NonSslAuthHttpTransportBindingElement();
    }
}

For this you’ll need to define a binding element extension in the config file that transport element type.  Here is what the service config file looks like with the extension defined and used in a custom binding.

<extensions>
  <bindingElementExtensions>
    <add name="nonSslAuthTransport"
          type="NonSslAuthSecurity.NonSslAuthTransportElement, NonSslAuthSecurity"/>
  </bindingElementExtensions>
</extensions>

<bindings>
  <customBinding>
    <binding name="nonSslAuthBinding">
      <binaryMessageEncoding />
      <security authenticationMode="UserNameOverTransport"/>
      <nonSslAuthTransport authenticationScheme="Anonymous"
                            allowCookies="false"
                            keepAliveEnabled="false"/>
    </binding>
    <binding name="webNonSslAuthBinding">
      <webMessageEncoding />
      <nonSslAuthTransport authenticationScheme="Basic"
                            manualAddressing="true"
                            allowCookies="false"
                            keepAliveEnabled="false"/>
    </binding>
  </customBinding>
</bindings>

In this example I’m also specifying binary message encoding for the SOAP endpoint because I’m certain that a .NET client will be calling the service, and this will result in a more compact wire representation of the message. (This is a very good practice in an all-.NET environment, but it is not taken advantage of as often as it should be.)

If you download the code for this post, you’ll see that it contains a FakeLoadBalancer project with a routing service that simulates a load balancer with SSL pass-through capability.  With a real load balancer, you could also get performance benefits from hardware-based acceleration for SSL.  The sample project contains the necessary binding configurations for both SOAP and REST style endpoints. Enjoy.

About Tony Sneed

Married with three children.
This entry was posted in Technical and tagged , , . Bookmark the permalink.

7 Responses to Building Scalable and Secure WCF Services

  1. Pingback: Distributed Weekly 160 — Scott Banwart's Blog

  2. Image with Keep-Alive disabled is not visible for me.

  3. Pingback: Building Scalable and Secure WCF Services - .NET Code Geeks

  4. Pingback: Hello world! | Ramesh's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s