WCF Security Spike – Day 2
We learn what he have to learn.
Does that surprise anyone? Some people have reacted with shock when I said that I don’t know WCF Security very well. Others said that I was implying that evangelists are somehow inferior to their colleagues on the product teams. Of course there are people with varying skills in different roles as a part of any organization. Technical Evangelists must have deep technical skills just like any other developer. However, the truth remains that when faced with an overwhelming amount of new technology every day we make choices and learn what we have to learn.
There are a few people who have taken the time to decipher WCF security. To this point, I have not been one of them because I have not had to learn it. The evangelist in me wants to focus on new product features and in the area of security there is nothing new in WCF. Yet because many customers are struggling with it, I felt that it was time… I must take on the beast so here we go for day 2. I’m writing these notes as I go so who knows what we will find.
Service Principal Name
Jesse mentioned to me that I needed to use setspn.exe to create a service principal name. I immediately went to Bing :-) and searched for WCF setspn which lead me to the WCF Security Guidance wiki page on the application scenario Intranet – Web to Remote WCF Using Transport Security (Trusted Subsystem, HTTP)
This article discusses using setspn when hosting the WCF service in an ASP.NET app pool running under a custom domain account. In my case the app pool is running under Network Service so I wasn’t sure if I needed it or not but I decided to give it a go so I ran the command
C:\>setspn -A http/rojacobs-vid rojacobs-vid
Registering ServicePrincipalNames for CN=ROJACOBS-VID,OU=Workstations,OU=Machines,DC=redmond,DC=corp,DC=microsoft,DC=com
http/rojacobs-vid
Updated object
Test It
I’ve created a local group on my machine called “ServiceUsers”. The plan is to add accounts to the “ServiceUsers” group and then use IsInRole to determine if the user is a member of the group.
The test will be to simply invoke the service with the WCF Test Client using two different accounts. One test will run it under my domain account REDMOND\rojacobs and the other will run it under a local admin account ROJACOBS-VID\Ron. Both of these accounts are members of ServiceUsers.
Here is the code for testing the user accounts. For now I’m commenting out the PrincipalPermission attribute
//[PrincipalPermission(SecurityAction.Demand, Role = "BUILTIN\\Administrators")]
public string AdminOperation()
{
return string.Format("IsInRole(\"ServiceUsers\")={0}",
Thread.CurrentPrincipal.IsInRole("ServiceUsers"));
}
I’ve setup my project in Visual Studio to host the site in my local IIS as well.
Test #1 – using domain account
I launch the WCF Test Client and add a service reference to my service by giving it the address of my service.
After clicking Invoke I see that it works returning the following
<s:Body u:Id="_0">
<AdminOperationResponse xmlns="https://tempuri.org/">
<AdminOperationResult>IsInRole("ServiceUsers")=True</AdminOperationResult>
</AdminOperationResponse>
</s:Body>
This didn’t work yesterday so I think it was the setspn trick that fixed it. Just to be sure I delete the spn and try it again
C:\>setspn -d http/rojacobs-vid rojacobs-vid
Unregistering ServicePrincipalNames for CN=ROJACOBS-VID,OU=Workstations,OU=Machines,DC=redmond,DC=corp,DC=microsoft,DC=com
http/rojacobs-vid
Updated object
Well guess what – it still worked. Strange, I’m sure this didn’t work yesterday… Perhaps the reboot overnight did it? Maybe the effects of setspn are still lingering in the worker process?
Just to be sure, I do an iisreset command
C:\>iisreset
Attempting stop...
Internet services successfully stopped
Attempting start...
Internet services successfully restarted
Now to test it again… still works.
Test #2 – using local admin account
Yesterday no matter what I did, the local admin account would never return true for IsInRole. Wonder what will happen today?
<s:Body u:Id="_0">
<AdminOperationResponse xmlns="https://tempuri.org/">
<AdminOperationResult>IsInRole("ServiceUsers")=True</AdminOperationResult>
</AdminOperationResponse>
</s:Body>
Doh! I can’t believe it…
Test #3 – using PrincipalPermission
Now I’m going to put PrincipalPermission back and try to get it to allow only users in the ServiceUsers group.
[PrincipalPermission(SecurityAction.Demand, Role = "ServiceUsers")]
public string AdminOperation()
{
return string.Format("IsInRole(\"ServiceUsers\")={0}",
Thread.CurrentPrincipal.IsInRole("ServiceUsers"));
}
I test it from both WCF Test Clients (one under REDMOND\rojacobs and the other running as ROJACOBS-VID\Ron) they both succeed.
Now I test it from a third local account (ROJACOBS-VID\Andrew) which is not a member of ServiceUsers – this one should fail.
Yes! Success – it fails.
I’m still not sure about this – with things not working yesterday, then working today. I think I need to reboot and run all the tests again just to be sure.
Test #4 – After Reboot
WCF Test client on the same machine running under domain account works
WCF Test client on the same machine running under local admin account works
Test #5 – Multiple Machines
Ok great, but I wonder will this work across machines?
In order to test this using HTTP I need to enable the firewall rule World Wide Web Services (HTTP Traffic-In)
Once I enabled all the machines in my home office to see each other (by turning on Network Discovery) I could browse to the service and see the WSDL but then I ran into a problem.
The WSDL used the full name of my machine rojacobs-vid.redmond.corp.microsoft.com in the URIs. My other machines in my home office could not resolve this address so the WCF Test Client failed.
Strange that I can navigate to https://rojacobs-vid but not https://rojacobs-vid.redmond.corp.microsoft.com
This is probably an issue because I’m testing this in my home office. If I had all these machines physically plugged into the corporate network (rather than via DirectAccess) it might work.
Day 2 Spike Summary
What have we learned so far?
- The easy way to restrict access by roles is with Windows Groups with PrincipalPermission demands
- You can do the PrincipalPermission demand with an attribute or in code
- You can use a local machine groups
- You do have to use a binding that supports this Intranet scenario (wsHttpBinding or netTcpBinding. basicHttpBinding wont’ work)
- You must enable the firewall ports for inbound and outbound traffic when testing
- Sometimes things just don’t work until you go home for the day and try again the next day :-)
What have we not figured out?
- How to test from another machine using the WCF Test Client when it cannot access the machine using the full DNS name (for some strange reason)
- or how to generate WSDL that does not use the full DNS name of the machine in the URI
Some other things to investigate
- What about Windows Identity Foundation? Does this make it easier or harder? Is it appropriate for this scenario?
Next Steps
- Review Windows Identity Foundation Whitepaper
- Do the Windows Identity Foundation WCF Services hands on lab