.NET

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.

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.

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.) 
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.

Related Articles

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
PUJA SEN
PUJA SEN
4 years ago

Please share the code if available

Back to top button