Delegation - WCF Gotcha #2
Welcome to the Jungle
If you've done a little web development or written a few
basic web services, you are probably familiar with the concept of
impersonation. When impersonation is turned on, .NET will make certain API
calls return the identity of the caller, instead of the account that the service
is actually running as (for example, DOMAIN\SmithJ instead of MACHINE\Network
Service). Impersonation, however, is mostly sugar coating. The web service doesn't
actually execute as if it was that user, it just makes your application think
that it is. When you connect to a database server on a remote machine or read a
file from a file share, you perform those operations as the service account.
The advantage of this approach is that security is easy to
configure. You don't have to worry about granting tons of permissions on your
servers. You simply trust the service to make the proper security checks on its
own and give the service all the permissions it might need... but that ease of
configuration comes at a cost. If you have a single account that the web
service is executing under, which tends to be happen, that account ends up with
permission to do everything. But what if the developers working on the web
service forget to put in a security check? These god mode services often open
up huge security holes [1].
How do we fix this problem? Enter delegation. Delegation
takes impersonation to the next level, allowing a remote machine to execute
with the same credentials as the caller. With delegation, you will be able to
take advantage of all of the wonderful security features of each of your systems
automatically. File system security will no longer be bypassed, database
security will no longer be bypassed, and you can hop from service to service to
your heart's content, without losing the caller identity or the ability to manage user
permissions and policies [2].
The downside of delegation is that it adds a significant
amount of complexity to the authentication process. For one, developers
generally do not have permission to turn on delegation. Delegation settings are
not in the IIS control panel or in the standard control panel. Delegation is
turned off by default, and since it is tied closely to Active Directory and
Kerberos, it can only be enabled by domain administrators. Additionally,
delegation can be difficult to troubleshoot. It is entirely possible for
firewalls, proxies, and a plethora of registry options to completely screw it
all up. That said, the benefits can really be worth all the hassle, but be prepared
for a potentially bumpy ride with the infrastructure team.
Here are the basics you should know.
1) Delegation
must be enabled on every machine that will be passing credentials around. If
you are talking from your web box to a SQL server, delegation must be turned on
in Active Directory for BOTH machines.
2) Turning
delegation on doesn't actually make it work. After turning it on, you have to
set up SPN for the service accounts that your services are running under [3].
Each SPN is specific to the service that is being called. For web services over
http or web sites, it should be host/machinename (and an additional for the
full domain name host/machine.mydomain.com). For SQL, you will want
MSSQLSVC/machinename:1433. This can be done in the active directory admin tools or with the
command line tool from Microsoft setspn. The nice thing about setspn is that
you can use "setspn -l machinename" to list the SPNs that are configured for a
machine, even if you don't have active directory admin rights, which can be
extremely useful if you want to troubleshoot things.
3) After
delegation is configured, you need to tell the WCF proxy on the client machine
to allow it. When credentials are passed to the server, they have an allowed
impersonation level associated. Unless you specifically set the client to allow
delegation (allowedImpersationLevel=delegation), credentials will not flow. This
can be done in the binding configuration for your service. Consider disabling
NTLM in the client configuration as well (allowNtlm=false). In the server
configuration, select windows authentication and use a binding that supports
delegation, like wsHttpBinding [4].
4) If
things still don't work, check your SPNs again with "setspn -l". You may need
to explicitly set the SPN in the client configuration with the <identity>
node.
If you follow the steps above, delegation should work with
WCF. Fortunately, WCF gives you control over a lot of parameters that you haven't
had an easy way to control in the past (like the SPN of the remote machine). But,
keep in mind that when you need to call your service from non-WCF applications,
you may not have all these options available.
As a final note, delegation does not work out of the box with
every binding. For example, the MSMQ binding does not currently support
delegation. However, that doesn't mean that you are out of luck if you want to
use other bindings. There is a feature of Windows called "protocol transition"
that gives your service the ability to act as whoever it needs to act on the fly without
the normal Kerberos ticket granting process or the need to call the LogonUser api with a password.
Because of the large amount of risk associated with giving a service the ability to run as anyone it chooses, protocol
only works when running "constrained delegation." Constrained delegation forces
you to choose exactly which services your service can communicate with. That
way, you don't have to worry about someone pulling down the CEO's email from
your web box. Still, since protocol transition bypasses the normal Kerberos processes, you
should use it carefully.[5]
[1] It is worth mentioning that security checks aren't
always the only reason you might need the original caller's identity. Sometimes
services expose different amounts or portions of data depending on the caller
and many times the application behind the service isn't something you have
source code for, making delegation the only way you can really call into the
application. Additionally, since most major vendors support Windows Authentication,
using delegation can give you a unified way to manage user credentials.
[2] Beware of approaches that explicitly pass the identity
of your user around. Developers are often tempted to solve the problem by
forwarding the caller's identity as a parameter to the web service, but this
can open up the same security holes as well, since it bypasses a lot of
security provided by the Kerberos protocol. Don't reinvent the wheel if you can
avoid it. If you spend the time to build, debug, and secure it you will probably
end up with something that looks a lot like Kerberos anyway.
[3] As an FYI, service accounts will make things easier to
setup due to some special permissions they have. By service account, I mean the
local machine service accounts, think Network Service. You can use domain
accounts, but that requires additional setup and the use of a UPN identity
inside your configuration files instead of an SPN.
[4] I have seen delegation working with basicHttpBinding
when hosted in IIS with asp.net compatibility and SSL enabled... so it is possible
to do without wsHttpBinding even if it's not explicitly supported.
[5] As a result, protocol transition with constrained
delegation is similar to using a service account with your own user store,
except that credentials can be managed in active directory and you get to use the
actual identity of the caller when you are granting permission to resources.