AppFabric Caching Service Using SSL

1 בFebruary 2013

2 comments

I really like AppFabric distributed cache (previously called Velocity). It’s so simple to use and it has really strong features like notification based local cache. It basically has two versions, one is targeting for on premise and one is for the cloud (Azure). The API is similar which makes it very easy to move your application in later phases to the cloud. But back to my customer which security was a strong requirement because of the nature of his system which I won’t mention here but I’m sure you get the idea. His security requirements are that all connection between machines will be sign and encrypted using a given certificate.

App Fabric Caching Security mode

When looking for information regarding AppFabric security model you find only few pages (like this) that says that AppFabirc Cache is secured in transport level by default and you can choose whether you want to enable encryption and signing (they are enabled by default) but I also found bunch of Stackoverflow question that wanted to understand more regarding how the security is implemented  like this and this.

To conclude both of the posts they say that app fabric transport relay on WCF – the binding protocol is TCP and the security is similar to WindowsStreamSecurityBinding which means the encryption and signing using the windows identity token. unfortunately This security model is not sufficient for my client so…

Try #1– Microsoft Support

It sounded to me like a reasonable request to extend/control the security model and it should be simple considering the fact that as far as I understand the communication relay on WCF. so we tried Microsoft support and this is what Microsoft answered:

“Following our discussion last week, I contacted our developers to confirm if AppFabric caching has a feature to extend or there’s any provider which allows to change binding for communication between cache client and caching service i.e. from NetTcpBinding to BasicHttpBinding or WsHttpBinding to use SSL. As discussed earlier, there’s NO feature or provider which allows this. So, it leaves only two security options to be set on the cache cluster: None and Transport (default being Transport with Encrypt and Sign).”

dead end Sad smile

Try #2 – let’s look inside

As a last resort I decides looking inside the code. It’s a longshot but still AppFabic is written in .NET meaning you can use decompiler tool to look at the code. I use DotPeek which is nice, gives great navigation functionality and most important free.This is what I learned:

  • DistributedCachingService code has some implementations parts specific for azure meaning that it could be that the same code is used in Azure caching which is very nice in my opinion allowing system that use the cache to move easily to the cloud and change only the configuration.
  • DataCacheSection– when browsing them I discovered the configuration section has exactly what I needed – SSL

The Element AdvanceProperty contains ServerSecurityProperty this is how it looks:

// Type: Microsoft.ApplicationServer.Caching.ServerSecurityProperties
// Assembly: Microsoft.ApplicationServer.Caching.Core, Version=1.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
// Assembly location: C:\Program Files\Microsoft SDKs\Windows Azure\.NET SDK\2012-10\ref\Caching\Microsoft.ApplicationServer.Caching.Core.dll

using System;
using System.Configuration;
using System.Runtime.Serialization;

namespace Microsoft.ApplicationServer.Caching
{
[Serializable]
internal class ServerSecurityProperties : ConfigurationElement, ISerializable
{
internal const string MODE = "mode";
internal const string PROTECTION_LEVEL = "protectionLevel";
internal const string AUTHORIZATION = "authorization";
internal const string ALLOW = "allow";
internal const string SERVER_ACS_PROPERTIES = "serverAcs";
internal const string USE_ACS_FOR_CLIENT = "useAcsForClient";
internal const string SHARED_KEY_AUTH = "sharedKeyAuth";
internal const string SSL_ENABLED = "sslEnabled";
internal const string SSL_PROPERTIES = "sslProperties";

[ConfigurationProperty("mode", DefaultValue = DataCacheSecurityMode.Transport)]
public DataCacheSecurityMode DataCacheSecurityMode
{
get
{
return (DataCacheSecurityMode) this["mode"];
}
set
{
this["mode"] = (object) value;
}
}

[ConfigurationProperty("protectionLevel", DefaultValue = DataCacheProtectionLevel.EncryptAndSign)]
public DataCacheProtectionLevel DataCacheProtectionLevel
{
get
{
return (DataCacheProtectionLevel) this["protectionLevel"];
}
set
{
this["protectionLevel"] = (object) value;
}
}

[ConfigurationCollection(typeof (AuthorizationElement), AddItemName = "allow")]
[ConfigurationProperty("authorization", IsDefaultCollection = false, IsRequired = false)]
public AuthorizationElement Authorization
{
get
{
return (AuthorizationElement) this["authorization"];
}
set
{
this["authorization"] = (object) value;
}
}

[ConfigurationProperty("serverAcs", IsDefaultCollection = false, IsRequired = false)]
public ServerAcsSecurityElement AcsSecurity
{
get
{
return (ServerAcsSecurityElement) this["serverAcs"];
}
set
{
this["serverAcs"] = (object) value;
}
}

[ConfigurationProperty("useAcsForClient", DefaultValue = false)]
public bool UseAcsForClient
{
get
{
return (bool) this["useAcsForClient"];
}
set
{
this["useAcsForClient"] = (object) (bool) (value ? 1 : 0);
}
}

[ConfigurationProperty("sharedKeyAuth", IsRequired = false)]
public SharedKeyAuthorization SharedKeyAuth
{
get
{
return (SharedKeyAuthorization) this["sharedKeyAuth"];
}
set
{
this["sharedKeyAuth"] = (object) value;
}
}

[ConfigurationProperty("sslEnabled", DefaultValue = false, IsRequired = false)]
public bool SslEnabled
{
get
{
return (bool) this["sslEnabled"];
}
set
{
this["sslEnabled"] = (object) (bool) (value ? 1 : 0);
}
}

[ConfigurationProperty("sslProperties", IsRequired = false)]
public SslProperties SslProperties
{
get
{
return (SslProperties) this["sslProperties"];
}
set
{
this["sslProperties"] = (object) value;
}
}

internal ServerSecurityProperties()
{
}

protected ServerSecurityProperties(SerializationInfo info, StreamingContext context)
{
this.DataCacheSecurityMode = (DataCacheSecurityMode) info.GetValue("mode", typeof (DataCacheSecurityMode));
this.DataCacheProtectionLevel = (DataCacheProtectionLevel) info.GetValue("protectionLevel", typeof (DataCacheProtectionLevel));
this.Authorization = (AuthorizationElement) info.GetValue("authorization", typeof (AuthorizationElement));
try
{
this.AcsSecurity = (ServerAcsSecurityElement) info.GetValue("serverAcs", typeof (ServerAcsSecurityElement));
this.UseAcsForClient = (bool) info.GetValue("useAcsForClient", typeof (bool));
this.SharedKeyAuth = (SharedKeyAuthorization) info.GetValue("sharedKeyAuth", typeof (SharedKeyAuthorization));
}
catch (SerializationException ex)
{
this.UseAcsForClient = false;
this.SharedKeyAuth = new SharedKeyAuthorization();
}
try
{
this.SslProperties = (SslProperties) info.GetValue("sslProperties", typeof (SslProperties));
this.SslEnabled = info.GetBoolean("sslEnabled");
}
catch (SerializationException ex)
{
this.SslEnabled = false;
}
}

internal DataCacheSecurity GetDataCacheSecurity()
{
return new DataCacheSecurity(this);
}

public void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("mode", (object) this.DataCacheSecurityMode);
info.AddValue("protectionLevel", (object) this.DataCacheProtectionLevel);
info.AddValue("authorization", (object) this.Authorization);
info.AddValue("serverAcs", (object) this.AcsSecurity);
info.AddValue("useAcsForClient", this.UseAcsForClient);
info.AddValue("sharedKeyAuth", (object) this.SharedKeyAuth);
info.AddValue("sslEnabled", this.SslEnabled);
if (!this.SslEnabled)
return;
info.AddValue("sslProperties", (object) this.SslProperties);
}
}
}

 

 

As you can see it has SSL enabled property and SSLProperties which include “certificateSubject”. So I tried setting this values with a legal certificate and the service worked Smile. But I celebrated too early because when drilling down to the usage of this property I discovered two things:

  • The communication channel  relays on WCF: the type WcfTransportChannel with two derives WcfClientChannel and WcfServerChannel – I learned exactly the binding that is used:
protected void SetupBindings()
{
BinaryMessageEncodingBindingElement encodingBindingElement = new BinaryMessageEncodingBindingElement();
XmlDictionaryReaderQuotas.Max.CopyTo(encodingBindingElement.ReaderQuotas);
TcpTransportBindingElement transportBindingElement = this.PrepareTcpTransportBindingElement();
BindingElementCollection bindingCollection = new BindingElementCollection();
bindingCollection.Add((BindingElement) encodingBindingElement);
this.AddSecurityBinding(bindingCollection);
this.AddCustomBindingElements(bindingCollection);
bindingCollection.Add((BindingElement) transportBindingElement);
this._tcpBinding = new CustomBinding((IEnumerable<BindingElement>) bindingCollection);
this._pipeBinding = new NetNamedPipeBinding();
}

  • In security aspect certificate is ignored when using transport mode meaning the fact you can put it in configuration (SSLEnabled=”true”) file doesn’t mean it will really use SSL.

Here is the code from the server side:

 

protected override void AddSecurityBinding(BindingElementCollection bindingCollection)
{
if (this.DataCacheSecurity.SecurityMode == DataCacheSecurityMode.Transport)
{
this._securityBindingElement = new VelocityStreamSecurityBindingElement(this._serviceConfigurationManager, true);
bindingCollection.Add((BindingElement) this._securityBindingElement);
}
else
{
if (!this._sslEnabled)
return;
bindingCollection.Add((BindingElement) new SslStreamSecurityBindingElement()
{
RequireClientCertificate = false
});
string sslCertIdentity = this._serviceConfigurationManager.AdvancedProperties.SecurityProperties.SslProperties.SslCertIdentity;
int certificateCount = WcfServerChannel.GetCertificateCount(sslCertIdentity);
if (certificateCount == 0 && Provider.IsEnabled(TraceLevel.Error))
EventLogWriter.WriteError(this._logSource, "Certificate with name {0} doesn't exist", new object[1]
{
(object) sslCertIdentity
});
if (certificateCount > 1 && Provider.IsEnabled(TraceLevel.Error))
EventLogWriter.WriteError(this._logSource, "More than one certificate with the name {0} exist.", new object[1]
{
(object) sslCertIdentity
});
ServiceCredentials serviceCredentials = new ServiceCredentials();
serviceCredentials.ServiceCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectDistinguishedName, (object) sslCertIdentity);
this._listenerParameters.Add((object) serviceCredentials);
}
}

  • The cannel type the client uses is IDuplexSessionChannel – from WcfClientChannel ctor:
this._tcpFactory = this._tcpBinding.BuildChannelFactory<IDuplexSessionChannel>(new object[0]);

So I learn a lot but unfortunately I didn’t find a way to override the current binding and security methods.

The Solution

I was really frustrated at this point but then I remembered one of the new features of WCF 4 was Routing Service which one of his target scenarios are: Protocol bridging (“You receive messages over one transport protocol, and the destination endpoint uses a different protocol” – MSDN). the only thing you should know is exactly what is your binding to do the routing correctly and here is the full solution:

 

image

  • Host Routing Service in the client side that listen to App Fabric request meaning:
    • Implement the contract IDuplexSessionRouter
    • Has custom binding with: TcpTransport,BinaryMessageEncounding and windowsStreamSecurity
    • Route all communication using “MatchAll” Filter to the target Routing Service at the App Fabric Server  with the required binding/security (Transport security using certificate)
  • Host Routing Service In the AppFabric cache Server machine
    • Implement the contract IDuplexSessionRouter
    • Has matching binding to the target service of the other router
    • Route all communication using “MatchAll” Filter to the App Fabric Server using custom bindingbinding with: TcpTransport,BinaryMessageEncounding and windowsStreamSecurity

Well it works Open-mouthed smile the client is agnostic to the fact that the messages are going through the routing services – the only down side is the double hopes the messages does but it doesn’t open the soap message (doesn’t Serialize /Deserialize) so I guess this solution it’s not bad.

That’s all for now

Cheers Offir

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

2 comments

  1. deecytheoft21 בApril 2013 ב 1:10

    When I originally commented I clicked the -Notify me when new comments are added- checkbox and now each time a comment is added I get 4 emails using the very same comment. Is there any way you are able to remove me from that service? Thanks!

    [url=http://www.michaelkorsstore.com]michael kors bags cheap[/url]

    Reply
  2. GraliaExhaura9 בMay 2013 ב 14:08

    This honestly answered my issue, thank you!

    [URL=http://www.mkbagforcheap.com/messenger-c-50.html]bolsas michael kors[/URL]

    Reply