Implementing a WCF service firewall part II of N
Join the DZone community and get the full member experience.
Join For Freethe previous installment provided some context as to why I want to implement this pattern. This installment will look at some of the implementation options.
As
I noted before, WCF provides quite a lot of extension points on the
route the message pass from arriving on the service to the point WCF calls
the actual method in the service instance. Several of those extension
points are possible candidates for the Service Firewall for instance
- Contract Filter-The contract filter is responsible to route messages to the appropriate contract. It needs to be a subclass of a MessageFilter. It looks that the contract filter is a good option since it intercepts the call rather early so it means it would probably be the fastest option. Also its name (filter..) implies it is a good option
- Message Inspector - The Message inspector is responsible for looking at or modifying messages when they enter a service and looks like a natural candidate for the job. There are two kinds of Message Inspectors: Those who look at messages on the client side (implement the IClientMessageInspector interface) and those that look at the server side (implement the IDispatchMessageInspector). It seems that the latter is the type of inspector we need here.
- Service Authorization Manager - responsible for evaluating policies, claims etc. of the client to make sure that a call is valid from the security perspective. This looks like it would be a good class to use for a real service firewall. It seems it won't be a good fit for the purpose of what we need here.
When I need to choose between several technical options that seem
to be similar I usually do a POC - proof of concept. A piece of
throwaway code to get a feel of the different options and better
understand their strengths and weaknesses (in the context of the
solution I seek).
What I did was to take a class I prepared for some of the integration tests of the EventBroker and build a few extensions that interact with them. Here is some of the setup code of the environment:
testServer = new Tester();
service1 = new ServiceHost(testServer, new Uri(string.Format("http://localhost:{0}", TestServerPort)));
var binding = new WebHttpBinding
{
ReaderQuotas = { MaxArrayLength = 600000 },
MaxReceivedMessageSize = 800000,
MaxBufferSize = 800000
};
var ep = service1.AddServiceEndpoint(typeof(TestingContract), binding, string.Format("http://localhost:{0}/S1", TestServerPort));
ep.Behaviors.Add(new WebHttpBehavior());
ep.Behaviors.Add(new InspectorBehavior());
service1.Authorization.ServiceAuthorizationManager = new TestAuthorizer();
var cp = service1.AddServiceEndpoint(typeof(ImContract), binding, string.Format("http://localhost:{0}/Control", TestServerPort));
cp.Behaviors.Add(new WebHttpBehavior());
The two redlines above are the ones responsible for injecting the POCs the InspectorBehavior is reponsible for inserting the ContractFilter and the MessageInspector and the TestAuthorizer is the Authorization Manager test implementation.
We also need some code to raise an event:
public void SendMessage()
{
var evnt = new TestingEvent { sagaId = Guid.NewGuid() };
moqRA.Expect(x => x.GetChannel<TestingContract>(evnt.sagaId, true)).Returns(channel1);
moqRA.Expect(x => x.GetChannel<TestingContract2>(evnt.sagaId, true)).Returns(channel3);
eb.BeginNewSagaEvent(evnt.sagaId, evnt);
eb.CloseSaga(evnt.sagaId);
}
And now we can look at the different options. The InspectorBehavior is just a helper class to wite the filter and/or inspector to the endpont. (The Authorization Manager is setup at the service level (i.e. for all endpoints))
public class InspectorBehavior : IEndpointBehavior
{
public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
{
}
public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
{
throw new NotImplementedException();
}
public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
{
var inspector = new TestInspector();
endpointDispatcher.DispatchRuntime.MessageInspectors.Add(inspector);
endpointDispatcher.ContractFilter = new TestFilter(endpointDispatcher.ContractFilter);
}
public void Validate(ServiceEndpoint endpoint)
{
}
The first thing I tried was the "ContractFilter". It is actually very simple to use. You inherit from MessageFilter and there are two "Match" method you need to override. One that accepts a buffer and one that accepts a (WCF) Message. WCF calls the Match method which accepts a Message.
WCF's Message class is interesting in the sense that it has a one-time touch feature. i.e. only one piece of code can read/copy it and the next piece of code which will try to do the same will fail
So the match method you can do something like the following:
public override bool Match(Message message)
{
var buffer = message.CreateBufferedCopy(Int32.MaxValue);
message = buffer.CreateMessage();
var r = buffer.CreateMessage().GetReaderAtBodyContents();
.
.
.
}
Which basically means get a buffer of the message, create one copy to preserve and the get another copy for internal use and work with that to parse and verify the actual message. Unfortunetly, this doesn't really work - the message parameter is not passed as ref so the original message is lost on the first line of the method and that's it. Note that you can access the header part of the message without problem, however that's not a good fit for what I am trying to do.
The next thing I looked at the MessageInspector. Again implementing it is rather simple, you just need to implement the IDispatchMessageInspector interface. This interface has two methods BeforeSendReply and AfterReceiveRequest. We'll look at the AfterReceiveRequest method. Again we try the message copy trick:
public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext)
{
var buffer = request.CreateBufferedCopy(Int32.MaxValue);
request = buffer.CreateMessage();
var temp = buffer.CreateMessage().GetReaderAtBodyContents();
.
.
.
}
This time it works since we get the request parameter as ref. At first it seemed to me that while you can inspect and alter the message as your heart wishes there is no way to say that the message is bad. One option is to alter the message to a faulty message and let the application handle it - but that means too much coupling between infrastructure and application. Another, better, option is to throw an exception.
So using the MessageInspector is a usable option. It is very good if you want to alter the incoming message but throwing an exception when the message is bad is not very clean
Which brings us to our third option Authorization Manager which, surprisingly turned out to be the best option
public class TestAuthorizer :ServiceAuthorizationManager
{
public override bool CheckAccess(OperationContext operationContext, ref Message message)
{
var autorized= base.CheckAccess(operationContext, ref message);
var buffer = message.CreateBufferedCopy(Int32.MaxValue);
message = buffer.CreateMessage();
var testMessage = buffer.CreateMessage();
.
.
.
return autorized;
}
}
Like the message inspector it receives the message as ref and like the filter it allows a single yes/no answer to decide if a message should continue or be discarded. Additionally it notifies the client that the message was rejected if that is what you choose to do (in the WebHttpBinding I used that means a 400 bad request return code)
Ok, so we've seen some of the options for implementing the Service Firewall and briefly went over thier different behaviors. The next part in this series will take a look at some of the actual implementation I didPublished at DZone with permission of Arnon Rotem-gal-oz, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Trending
-
A Comprehensive Guide To Testing and Debugging AWS Lambda Functions
-
How To Use Geo-Partitioning to Comply With Data Regulations and Deliver Low Latency Globally
-
Manifold vs. Lombok: Enhancing Java With Property Support
-
Docker Compose vs. Kubernetes: The Top 4 Main Differences
Comments