Trust Boundaries
A trust boundary is a location in your code that you assume that you can “trust” the calling code not to pass junk in the parameters or attempt something malicious. Within an single executable with no external interfaces pretty much all the code can trust it’s caller, but in a distributed application this is not the case. We have all seen distributed applications (which I’m defining in this context as any application that uses external code located in a separate location, for example local DLLs, web services, etc) that have code in the external resources that simply trusts the caller to provide all it needs and that it has a non malicious purpose with pretty much no security or validation whatsoever. These applications, in my own experience, are internal applications that run behind company firewalls and were built by people who had a “closed application” image in their minds. I have to admit I’ve written several of these myself (just a few of which against my will).
A good example is this: let’s say we have an application that manages orders of bagpipes. There is an external resource created by one developer that contains a class called PipeOrder. PipeOrder has a method called “Place” which takes some order information (buyer name, address, credit card, etc.) and makes the calls to update the back end database with the new order. If this class was designed with the thought (either consciously or unconsciously) it would be behind a trust boundary then it probably just takes the list of parameters, assuming the required ones are provided and the data is in the correct format, and passes it on to the data access layer to insert into the database. After all, isn’t that what the UI is for?
This was very much something you could count on in a stand alone executable where the UI was bound up in the same executable as the class and the only way the class could be called was from within that executable. Today though we have a different story. While developers have now at least heard the buzz-anagram SOA, they have definitely been working with distributed applications for quite a while. The UI being separated is nothing new, but I’ll bet the PipeOrder class, which now resides as a web service on some back end system or “business tier” server, still just assumes it is only getting called from the UI piece and still does nothing more than pass on the data handed it. This is fine if you can count on the fact that this object would never be called by some other code that may not be doing the data validation the companion UI is doing. This effectively means your trust boundary is the UI.
What if you want to expose PipeOrder as a web service to the internet so that some retailers can incorporate direct orders to you from their own web sites or ordering systems? Are you going to assume that the people calling your web service will validate all the data? Effectively you have to move your trust boundary to the PipeOrder class, or perhaps even lower to the data storage mechanisms.
I’ve had several discussions during design meetings about where this trust boundary should be. It hasn’t really been worded like this (except once) and so it usually comes down to a database person saying they shouldn’t have to check all this stuff since it’s the UI’s responsibility to validate the data. Plus the performance minded individuals say you need to validate at the highest level to save calls that go all the way down just to be thrown back due to invalid data.
A really paranoid architect will have validation at every level (UI, Mid tier and data), but this is very inefficient. You should select a trust boundary that, after a call reaches that point, is trusted to supply the correct information. Even if you select the trust boundary to be your data tier, you need to have it at the lowest point in which your code could be called externally. You can always have validation higher than that, for example, check these items in your UI, but you should also have it lower to validate and verify for code that will be calling your system by other means.
In the PipeOrder example I might recommend that we have the class do a check of all the required fields and validate the data provided and throw an exception if something doesn’t check out, which would move the trust boundary to that class, and not the caller. This would be very helpful if the system did a lot of processing on the order prior to it actually being handed to the database. If, however, some of our internal application could also be writing to the order table, not through the PipeOrder class, then perhaps it is time the Database implement some constraints and checks, or rethink why there are multiple entry points of the data.
Gone are the days you can assume that the delete method won’t be called on a class just because you don’t display that button to non-admin users. You better have a user id as a parameter and check it before you just delete that order. We keep designing broader and broader distributed applications yet we don’t always remember the repercussions of that.
Just remember that not having a trust boundary is a conscious decision that the trust boundary exists with the calling code, not within yours.