Mike Woodring
http://staff.develop.com/woodring
This piece of code is intended to serve two purposes:
Before proceeding, you must understand that the solution presented here does not provide any level of security. Instead, these channel sinks merely provide additional information by serializing and transmitting the client thread's security principal to the server, where it's deserialized and associated with the server-side thread that's carrying out the remote method invocation. Before using this code in a production system, you must understand that transmiting this information over an unauthenticated and unencrypted channel, and relying on it in the server to perform role-based security access checks, creates a security vulnerability in your server.
The reason the stock .NET remoting channels (HttpChannel and TcpChannel) do not transmit the client thread's principal already is due to the fact that those channels do not setup an authenticated and secure connection between the client and server. This means that the role information for a client's principal would be transmitted in the clear (unless SSL was being used) and potentially snooped by a malicious attacker. Furthermore, the lack of authentication means the server has no way of knowing if a remote method invocation message was sent from the "real" client or an imposter. The cleartext disclosure by itself might be a problem, but more typically, that information (and the lack of authentication) means that an attacker could synthesize their own method requests against your server that included arbitrarily important role membership information. If this is done, the server would have no way of distinguishing such an attacker from a legitimate client.
So it's important to understand that this piece of sample code does not solve the problems surrounding the lack of an authenticated channel. In fact, careless use of these channel sinks would in fact reduce the security of your system. Instead, the point of this code is to provide for the seemless capture and restoration of client role membership if you happen to be using the HttpChannel over an SSL connection (in which case SSL is providing the security). Alternatively, these channel sinks would be useful if your remoting design has the remoting client and server interacting on the "same side" of a secure LAN environment where you're not worried about malicious snooping of wire packets.
At some point in the future, someone (me, Microsoft, or maybe you) will provide a custom channel that performs network authentication and supports setting up an authenticated, encrypted channel between remoting clients and servers. The enclosed channel sinks are no substitute for such a secure infrastructure.
If you've signed the waiver of understanding and realize what the limitations of this code are, we can talk about the code that's enclosed.
The remprincipal.sln VS.NET solution file contains four projects:
The sample code being demonstrated is in the IdentitySink library, while the client and server applications each have a configuration file that shows how the channel sinks are stitched into a running system.
Conceptually, the enclosed channel sinks are fairly simple. When a remoting client makes a method call to a server, the IdentityClientSink has a chance to inspect and modify or augment the method message as it makes its way toward the channel that will eventually ship the message across to the server. At this point, the client sink grabs the calling thread's IPrincipal reference, serializes it, and stores the serialized principal object in the call context collection that will be transmitted to the server as part of the remote method message. When the method message arrives at the server, the server-side sink grabs the serialized principal out of call context, deserializes it, makes a note of the current thread principal, and then changes the current thread principal to refer to the transmitted principal before passing the method message onto the downstream sinks, where the method is eventually executed on the target object. This means that the server-side code can perform declarative or imperative tests against the calling thread's principal (IPrincipal.IsInRole). Once the method call returns from the object and downstream sinks to the server-side identity sink, the current thread principal is restored to its original state.
You'll find the above sequence of events implemented in the SyncProcessMessage method of the IdentityClientSink class and the ProcessMessage method of the IdentityServerSink class.
The description of the sample code above was a little oversimplified in one regard. If the client's thread has an instance of GenericPrincipal associated with it, then that principal object is serialized, transmitted, deserialized, and attached to the server-side thread exactly as described above. Subsequently, the server-side code can perform standard principal-based security demands, or call IPrincipal.IsInRole to test the client's role membership. However, if the client's thread has an instance of WindowsPrincipal associated with it, then things are a little more complicated.
The complication arises because the WindowsPrincipal object cannot be serialized and deserialized across process boundaries. An attempt to do so results in an exception being raised when the principal is deserialized in a different process. This is because the WindowsPrincipal object internally holds a process-relative HANDLE to an access token representing the calling thread's identity and credentials. This access token is produced as a result of a Windows authentication operation (such as logging into your PC interactively). Simply transmitting this handle to a separate process would not do any good, as the handle would be invalid in the target process. And a new WindowsPrincipal object cannot be constructed in the target process without performing a logon, or network authentication, operation that results in a logon session locally on the server machine. Since neither of the stock remoting channels available to us provide an authenticated connection, we're out of luck here.
For these reasons, WindowsPrincipals cannot be handled in the same way that
GenericPrincipals are. Instead, when the client-side sink discovers a WindowsPrincipal on
the calling thread, it constructs a new GenericPrincipal
that specifies the
same user name and role membership as follows:
When you look at the code, you'll notice that, when handling a WindowsPrincipal, the client sink makes what looks to be an extraneous call to IsInRole with an empty string. This call is important, because the m_roles member variable of the WindowsPrincipal object is not initialized with valid data until the first call to IsInRole is made. So calling IsInRole with an empty string simply prods the WindowsPrincipal implementation to wander through the operating system's user database to determine the calling thread's role membership in terms of NT groups.
Finally, one last operation is performed to prepare the new GenericPrincipal for transmittal. In the case of a WindowsPrincipal, it's likely that certain groups the calling thread belongs to are local groups. These group (role) names will have the form machineName\groupName. Any role of this format that's discovered is altered to use a . (period) in place of the machine name. So, for example, the server will perceive a client-side group of BEARTOOTH\SuperDudes as a role of .\SuperDudes.
If anything fails along the way, the client-side sink will fail gracefully (i.e., without crashing) and nothing will be captured and transmitted to the server regarding the client thread's identity. This might happen if the sink assembly doesn't have the necessary code access permissions to use reflection to access private member variables, or if the WindowsPrincipal implementation changes so that there is no longer a member variable named m_roles that is an array of strings.
The source code for detecting, transforming (if necessary), serializing, and deserializing the calling thread's principal is in Util.cs.
As long as you realize that the sink provided in this sample doesn't provide any extra measure of security; and that writing your server-side code to perform role-based checks against the information being transmitted will create a vulnerability if you are not using SSL between the client and server (or isolating the client and server in a secure LAN), then the enclosed code can be useful. At the very least, it's yet another sample of how to write a custom channel sink.
Happy remoting.