Accessing SelfHosted WCF Services outside a domain - c#

We have WCF services being self-hosted by a Windows Service inside our domain, using NetTCP with the following settings.
// Set Binding Security.
netTcpBinding.Security.Mode = SecurityMode.Transport;
netTcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
netTcpBinding.Security.Transport.ProtectionLevel = System.Net.Security.ProtectionLevel.EncryptAndSign;
We now have a requirement to allow people outside the domain to access these Services (as long as they can provide proper domain credentials). Our goal isn't to host the services via IIS, just allow those outside folks into our services. In my testing I was able to connect to a service from outside by "impersonating" the client proxy credentials during the WCF call as such.
proxy.ClientCredentials.Windows.ClientCredential.Domain = "MyDomainName";
proxy.ClientCredentials.Windows.ClientCredential.UserName = "MyUserName";
proxy.ClientCredentials.Windows.ClientCredential.Password = "MyPassword";
My question is: Is this the correct way? Is there a better way? Any advice would be greatly appreciated.

This route is perfectly valid if you need to imperatively (in code, e.g. a credential popup, or read from a configuration file) set the credentials. A more secure option is to use the windows credential cache. Firstly you would set it up to use the cache:
proxy.ChannelFactory.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
Next you would set up the credentials in the credential cache. In Windows XP/2003 this is under "Stored Usernames and Passwords," (in the control panel) in Vista/7/2008 this is under "User Account > Credential Manager" (in the control panel).
As said, your way is perfectly valid - the cache is just more secure.

Related

Does Connecting to Remote WMI from an ASP.NET Page using Constrained Delegation Require Protocol Transition?

I'm working on an older web app that I did not originally build, but now maintain. It is a classic ASP app with ASPX pages mixed in.
The site is setup for Kerberos authentication and delegation, and that is working properly to other boxes (e.g. I can run a SQL query against a back-end server from an ASP page in the site and it connects using the front-end client's credentials properly). So SPNs are registered, delegation privileges are configured in AD, etc.
Now, the part I'm having trouble with is an ASPX page which invokes a remote WMI call to check on the status of an IIS website, using the \root\WebAdministration WMI namespace. The ASPX page is itself invoked by way of an XHR which resides in the client-side code of a different ASP page. The ASPX, when invoked, makes the WMI call, then Response.Write's back the necessary data, which the originating ASP page then utilizes to populate the page that the user sees. The problem is, I cannot get the IIS box to properly delegate the user's credentials to the back-end machine that its making the WMI call against.
This all works properly (including the constrained delegation), but only if I enable protocol transition. If I set the delegation on the middle-tier (IIS) box to use only Kerberos authentication, it fails (I get an anonymous logon attempt on the back-end box).
Now, I've done numerous packet captures on both the front-end client and the IIS box to see exactly what is going on here, and I can see several things:
The front-end client is properly getting its Kerberos ticket, and presenting it to the IIS box for authentication.
The IIS box is accepting the Kerberos ticket from the client.
However, the IIS box is not using the ticket received from the client as the "evidence ticket" that it should be presenting to the KDC in order to obtain a service ticket to access the back-end service on behalf of the front-end user. Instead, the IIS box is using a S4U2Self call to the KDC to obtain a ticket on the front-end user's behalf for itself, then using that ticket in the subsequent S4U2Proxy call to try and obtain the ticket to the back-end. This is where the problem lies.
The behavior noted above is why this works when protocol transition is enabled, and does not work when it is not.
I cannot figure out for the life of me, why the IIS box feels the need to obtain a TGS for itself to use as the "evidence ticket" to get the ticket for accessing the back-end, instead of simply using the ticket presented by the client. There is nothing invalid about the client's ticket from what I can tell, and the client is establishing a Kerberos-authenticated connection with the web server just fine, so there should be no need for protocol transition here. I could enable it if really needed, but I really just want to know why its necessary (if there is valid reason and this is by design then so be it).
The IIS app pool is running as the built-in app pool identity, and the delegation settings are thus configured on the IIS machine account in AD. SPNs are registered for the site against the IIS machine account, and for the back-end services against those service and/or machine accounts, and the "allowedToDelegateTo" list is configured on the IIS machine account, allowing constrained delegation to the necessary services. The specific SPN we are trying to delegate creds to in this scenario is RPCSS/[machine] for the WMI call. I've verified via the packet capture that the SPN in the request matches the SPN in the A2D2 list exactly (of course, if it didn't, then it wouldn't be working when protocol transition was enabled anyway).
As for the actual WMI connection code, I've tried a few ways. One was something like this:
ConnectionOptions co = new ConnectionOptions();
// I did try ImpersonationLevel set to both Impersonate and Delegate, but I don't think I need
// Delegate here because I'm not delegating from the remote WMI machine to a different box; instead,
// I'm delegating from the IIS box to the remote WMI machine.
co.Impersonation = ImpersonationLevel.Impersonate;
co.Authentication = AuthenticationLevel.PacketPrivacy;
co.EnablePrivileges = true;
// Tried this for the Authority line because I noticed in the packet captures that the principal
// specified here becomes the SPN that is used in the S4U2Proxy request.
co.Authority = "kerberos:RPCSS/machine.domain.com";
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration", co);
Then I also tried this:
ConnectionOptions co = new ConnectionOptions();
co.Impersonation = ImpersonationLevel.Impersonate;
co.Authentication = AuthenticationLevel.PacketPrivacy;
co.EnablePrivileges = true;
// I also tried this for the Authority line based on various other code examples for similar
// issues, but this resulted in an incorrect SPN being used in the request.
co.Authority = #"kerberos:DOMAIN\machine";
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration", co);
I also tried the same as above, but without an Authority line, and the correct SPN was used in the request but it still didn't work.
Finally, I also tried this, with no ConnectionOptions object at all, hoping it would just pass on the default creds:
ManagementScope ms = new ManagementScope(#"\\machine.domain.com\root\WebAdministration");
Any help here on either how I can get this working without enabling protocol transition, or info on why this setup would require protocol transition, would be very much appreciated!

How to pass default credentials in Windows Authentication

I'm developing UWP application using C#.net and it has WCF service with Windows Authentication enabled. I struggling to pass default NetworkCredential after consume a service call using Add service reference option.
Please find below my examinations.
When I pass correct windows authentication credentials, it is working as expected.
var service = new ServiceReference.Service1Client();
service.ClientCredentials.Windows.ClientCredential =new NetworkCredential("pradeep","****");
var test = await service.GetDataAsync(1);
but, I wanted pass default network credentials while using my service methis
var service = new ServiceReference.Service1Client();
service.ClientCredentials.Windows.ClientCredential = System.Net.CredentialCache.DefaultNetworkCredentials;
var test = await service.GetDataAsync(1);
I also tried below option.
service.ClientCredentials.Windows.ClientCredential = (NetworkCredential)CredentialCache.DefaultCredentials;
When I pass default credentials. I'm getting below exception.
The HTTP request is unauthorized with client authentication scheme
'Negotiate'. The authentication header received from the server was
'Negotiate, NTLM'.
I tested same service call with default NetworkCredential in WPF application which is working as expected.
In order to pass the default credentials for the WCF Windows Authentication in UWP by using the System.Net.CredentialCache.DefaultNetworkCredentials, first please make sure that you have added the Enterprise Authentication and Private Networks(Client & Server) capabilities as following:
For the Enterprise Authentication capability, it is because that Windows domain credentials enable a user to log into remote resources using their credentials, and act as if a user provided their user name and password. The enterprise Authentication special capability is typically used in line-of-business apps that connect to servers within an enterprise.
For the Private Networks(Client & Server) capability, it is because that currently in Windows Runtime we can only pass the default credential in the Intranet. For the Internet we have to use the Username and Password as credential.
For more information about the Capabilities, please check:
https://msdn.microsoft.com/en-us/library/windows/apps/hh464936.aspx .
After that please try to use your Computer name or Fully Qualified Computer name instead of the IP address for your WCF Services like this: http://YourComputerName:YourPortNumber/Service1.svc.
At last please use another computer as client to test the WCF Windows Authentication in UWP with the System.Net.CredentialCache.DefaultNetworkCredentials, then it should work fine.
Thanks.

WCF same Identity as on client?

For an experimenting project I'am struggling with a service. The client is an ASP.NET MVC 4 and the service will be build with WCF. For now all the systems are in a trusted subsystem so SSL/certificates is not necessarily.
The problem I'am currently dealing with is: Is het possible when I create a new GenericIdentity like:
IIdentity newIdentity = new GenericIdentity("Test", "Custom authentication");
string[] newRoles = { "TestRole" };
IPrincipal testPrincipal = new GenericPrincipal(newIdentity, newRoles);
Thread.CurrentPrincipal = testPrincipal;
It is possible to have the created user when I'am on the WCF service calling the following code:
ServiceSecurityContext.Current.WindowsIdentity;
Or
ServiceSecurityContext.Current.PrimaryIdentity;
Or
Thread.CurrentPrinicpal;
I get the user which I created in the client? Or do I have to write a WCF extensibility for this?
I'm currently using a WsHttpBinding and security mode Transport and clientCredentialType: Windows. Maybe something wrong about the configuration?
On server side (WCF), the security context will have the username value you have used to authenticate to the service.
This means, that if you have defined on client side windows authentication, WCF will find the security context filled with the username of the windows identity you used.
In order for you to authenticate to the servive with windows account, you can use the following code
on client side :
channelFactory.Credentials.Windows.ClientCredential =
new NetworkCredential(username, password, domain);
Having used this code, you can access the identity on server side with the below code :
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity
In general, try to be explicit on client side authentication info. Currently, you seem to
be simply using the default behavior of windows authentication schema, by setting identity
on current thread.
I hope this helps.
I discovered to achieve this automatically in WCF that you must specify a certificate...
In the messages that are sent to the WCF service I created a few properties with the information in the datacontracts that is necessary to create a GenericPrinicpal (in this the username en the roles). Because I have an trusted subsystem I don't want to authenticate the user on each WCF-service (to much overhead for my scenario). See also Trusted subsystem
With this I created a WCF extension that implemented a parameter inspector and with reflection I set the credentials from the CurrentPrincipal in the specified properties of the contract. For more information about parameter inspectors and how to apply it

WCF windows credentials

my client on server A calls a service on B which calls a service on C.
In order to get the call working from B->C I have to do this:
channel.Credentials.Windows.ClientCredential =
new System.Net.NetworkCredential("WndowsUserName", "WindowsPassWord");
IService1 service = channel.CreateChannel();
etc...
the user name and password are the windows credentials used from A->B
Of course I do not want to hardcode this so how can I do this without hardcoding?
I tried, with no luck:
WindowsIdentity callerWindowsIdentity =
ServiceSecurityContext.Current.WindowsIdentity;
using (callerWindowsIdentity.Impersonate())
Use
System.Net.CredentialCache.DefaultNetworkCredentials
property. It represents the authentication credentials for the current security context in which the application is running. Details can be found here.
It seems to be a "double hop" authentication problem.
In short, NTLM doesn't alllow more than one "hop" with it's credentials (token). So user authenticates on server 1 with it's token, and in turn, server 1 tries to send the token to server 2. This won't work, unless Kerberos deleguation is allowed between server 1 and 2.
More details here : http://weblogs.asp.net/owscott/archive/2008/08/22/iis-windows-authentication-and-the-double-hop-issue.aspx
And here : http://blogs.msdn.com/nunos/archive/2004/03/12/88468.aspx
Perhaps the class
System.Net.CredentialCache
could be helpfull ...
It has the DefaultCredentials and DefaultNetworkCredentials properties that you can use.
Offcourse, you will have to make sure that your application runs under the credentials that you want (that is , the credentials of the current user).
This can be done by calling
AppDomain.CurrentDomain.SetPrincipalPolicy (PrincipalPolicy.WindowsPrincipal);
At the start of your program.
Then, when you initialize the WCF service, you can use the DefaultNetworkCredentials provided by the CredentialCache.
channel.Credentials.Windows.ClientCredential = CredentialCache.DefaultNetworkCredentials;
IService1 service = channel.CreateChannel();

Domain.GetDomain(...) fails when called from a web service

I have the following code in a class that is called from a web service:
NetworkCredential credentials = new NetworkCredential("user", "password");
connection = new LdapConnection("domain");
connection.Bind(credentials);
DirectoryContext directoryContext =
new DirectoryContext(DirectoryContextType.Domain, "domain");
// This call returns a domain object with unreadable properties
Domain domain = Domain.GetDomain(directoryContext);
If I instantiate the class directly, all is well, I have a valid domain object that I can work with. If I go through the web service, the domain object is created, but most of the properties throw exceptions, e.g.:
'domain.Children' threw an exception of type ActiveDirectoryOperationException
I have impersonation enabled and am explicitly setting the credentials before calling the web service. Examining Thread.CurrentPrincipal.Identity.Name on the web service side shows the username of the credentials I've explicitly set.
If I look at Request.LogonUserIdentity, I have the following properties:
Name: "domain\\username" (is correct)
ImpersonationLevel: Impersonation
IsAnonymous: false
IsAuthenticated: true
AuthenticationType: NTLM
Anonymous access is disabled (enabling it makes no difference), and 'Basic Authentication' and 'Integrated Windows Authentication' are both checked. The web service is running under IIS 5.1 on my development box.
The code that calls the web service, resulting in a failed call to Domain.GetDomain():
MyServiceProxy proxy = new MyServiceProxy ();
CredentialCache credCache = new CredentialCache();
NetworkCredential netCred = new NetworkCredential(user, password, domain);
credCache.Add(new Uri(proxy.Url), "Ntlm", netCred);
proxy.Credentials = credCache;
proxy.MethodCall();
The code that calls directly and succeeds:
MyService myService = new MyService();
myService.MethodCall();
Any ideas why calls to Active Directory would fail when made in the context of a web service? And again, the call doesn't fail per se... it returns a domain object with unreadable properties.
Thanks in advance!
When you do...
NetworkCredential credentials = new NetworkCredential("user", "password");
connection = new LdapConnection("domain");
connection.Bind(credentials);
DirectoryContext directoryContext =
new DirectoryContext(DirectoryContextType.Domain, "domain");
// This call returns a domain object with unreadable properties
Domain domain = Domain.GetDomain(directoryContext);
...you're in fact just creating a (System.DirectoryServices.Protocols).LdapConnection with specific NetworkCredentials and you validate your "user" and "password" credentials against that. But then you don't use the connection-object anymore; instead you create a new entirely unrelated (System.DirectoryServices.ActiveDirectory).DirectoryContext-object.
And because you're not using a constructor where you're explicitly specifying a username and a password the DirectoryContext-object will get using the credentials of the user running the Application Pool (in IIS 6+, in IIS 5.1 the application will, if memory serves me right, always be a/the local system account - IUSR_XXX - which won't be able to access Active Directory because it's not a domain account).
The different credentials that are used when you're running your code in an IIS-environment versus just testing using a console application (where you're running code as the logged in/interactive user) is a common cause of problems in programming Directory Services.
Try using the constructor where you specify a username and password for the DirectoryContext-object.
As far as impersonation is concerned you might have better luck by using this code-snippet...
System.Security.Principal.WindowsImpersonationContext impersonationContext;
impersonationContext =
((System.Security.Principal.WindowsIdentity)User.Identity).Impersonate();
//Insert your code that runs under the security context of the authenticating user here.
impersonationContext.Undo();
...taken from KB306158: How to implement impersonation in an ASP.NET application.
In the end, I converted this to a WCF service hosted in IIS, and the impersonation is fine. I'd love to know what the problem was, but I had to move forward, and a WCF service is a better solution overall, anyway. Thanks for the help, though!

Categories