I have the following code snippet to test a plain-text username/password against the AD, which works fine if I hit F5 in visual studio and try it via the WCFTestClient, but once I deploy to IIS and try the same function, it will never return true for ValidCredentials; is there something security wise that needs to be set for the Identity that the Application Pool runs under?
I tried setting the App Pool identity to my own account (domain admin) just to test if this was the problem, but that didn't help either, so I'm a bit lost as to how to fix this.
The site (Custom API) has anonymous access set up.
try
{
// create a "principal context" - e.g. your domain (could be machine, too)
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, DomainName))
{
// validate the credentials
if (pc.ValidateCredentials(UserName, Password))
{
IsValid = true;
break;
}
}
}
catch (Exception)
{
LoggingControler.LogWarning(null, "Unreachable Domain: " + Domain);
}
I've gone over things again, and it's all due to Rights in Windows. Network Service somehow doesn't have enough rights to execute PrincipalContext.ValidateCredentials. If I change the Application Pool Identity to the identity of a Domain Administrator, the code works.
If someone can tell me how to set up a Limited User account with the proper rights to execute PrincipalContext.ValidateCredentials, I can finish this.
Ok, I've finally found my own answer via https://stackoverflow.com/questions/5140377/query-activedirectory-sometimes-not-working-asp-net-c and https://elgg.leeds.ac.uk/webteam/weblog/15385.html
As I've discovered, "Network Service" application pool identity holds the key to this problem...
Adding the read rights didn't work; so there's still something else wrong.
Did you enable Windows Authentication in IIS
Related
I've created a small application which attempts to authenticate a user based on their username and password. This application works correctly when run on the same domain which Active Directory resides on.
I must now extend the application to also work on domains which are "closed" in terms of security and permissions. In other words, is there a way to run the application based on an administrator account, or an account which has the necessary permissions to access the Active Directory?
This is the code I have used to authenticate a user:
using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, server + ":" + port))
{
if (pc.ValidateCredentials(username, password))
{
valid = true;
}
else
{
valid = false;
}
}
The above code works perfectly, however I would like to modify it so that it can communicate with the Active Directory database in an authenticated manner.
I have read numerous documentation and resources, but have not found anything. The closes I found was an article mentioning that IIS has to be set up and configured in a specific manner. However, my application is a simple C# application, and IIS is not being used.
If I understand the question properly, you want to execute ValidateCredentials using a different user than the current process' user.
I may be missing something, but have you tried modifying your code this way?
using (PrincipalContext pc =
new PrincipalContext(ContextType.Domain,
server + ":" + port,
specialAccountUsername,
specialAccountPassword))
{
return pc.ValidateCredentials(username, password);
}
It simply uses a constructor that takes the special account you are using for accessing the domain.
I have the following block of code that queries Active Directory for users by Group Name using System.DirectoryServices.AccountManagement:
var domainContext = new PrincipalContext(ContextType.Domain, "company.container.internal");
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Name, "Lvl1Users");
if (groupPrincipal != null)
{
//Read the values
}
Now the site uses the following:
IIS7 on Win2k8
Windows Authentication
Impersonation = True
App Pool on .NET 4.0 using 'NETWORK SERVICE' as the account
On my local machine (you know how this goes) it all works great. My peers that try it locally also it works well. However once deployed to the server it shows the following:
An operations error occurred.
Everything I research says it's a permissions issue. 1 thing to note, on my local machine I'm on the MainNetwork domain which is the parent to company.container.internal domain which I am querying. The IIS machine is on company.container.internal and is querying the same domain. So honestly, I would think the more challenging situation is reading AD on my local machine which is on a different domain, but it works. On the server which is querying the same domain, it fails.
Here is what I've tried, and none of these has worked:
Change AppPool to 'LocalSystem'
Change AppPool to use a static super-duper Admin account
Used Impersonation in code to manipulate the context of the calls in a local block with an admin user on the MainNetwork domain.
Used Impersonation in code to manipulate the context of the calls in a local block with an admin user on the company.container.internal domain.
Adding in using (HostingEnvironment.Impersonate())
What gives here? I have tried impersonating every type of power admin on both domains, and used multiple AppPool settings, and I keep getting the same error. Is there anything that needs to change in the code with the declaration of the domains, or is there a permissions issue I'm missing?
I figured this out and it turned out that using HostingEnvironment.Impersonate() was still at the root to solve the problem. I had already tried this, but there was another issue with my code.
The issue is often that the context for which the Active Directory calls is made is under a user that does not have permissions (also can happen when identity impersonate="true" in ASP.NET, due to the fact that the users token is a "secondary token" that cannot be used when authenticating against another server from: http://bit.ly/1753RjA).
The following code will ensures that the block of code running, is run under the context of say the AppPool (i.e. NETWORKSERVICE) that the ASP.NET site is running under.
using (HostingEnvironment.Impersonate())
{
var domainContext = new PrincipalContext(ContextType.Domain, "myDomain.com");
var groupPrincipal = GroupPrincipal.FindByIdentity(domainContext, IdentityType.Name, "PowerUsers");
if (groupPrincipal != null)
{
//code to get the infomation
}
}
However, one super important detail is that all the code calling Active Directory must be in that block. I had used some code a team member of mine wrote that was returning a LINQ query results of type Users (custom class), but not evaluating the expression (bad practice). Therefore the expression tree was returned instead of the results.
What ended up happening is the calling code eventually evaluated the results and the An operations error occurred message still appeared. I though the code fix above didn't work. When in fact it did, but there was code evaluating the results outside the block.
In a nutshell, make sure all code to access Active Directory is inside that using block and the exception should be fixed one the service/app is deployed to the server.
I try change identity for application pool on Windows Azure. My project uses this application pool when works on Windows Azure. By default application pool uses NetworkService identity, but I must use another identity. I try change it in OnStart() event of WebRole by this way:
using (ServerManager serverManager = new ServerManager())
{
string appPoolName =
serverManager.Sites[RoleEnvironment.CurrentRoleInstance.Id + "_Web"]
.Applications.First().ApplicationPoolName;
var appPool = serverManager.ApplicationPools[appPoolName];
appPool.ProcessModel.UserName = Environment.MachineName + "\\UserName";
appPool.ProcessModel.IdentityType = ProcessModelIdentityType.SpecificUser;
appPool.ProcessModel.Password = "UserPassword";
serverManager.CommitChanges();
}
But I get exception with next message:
System.Runtime.InteropServices.COMException (0x80090016):
Keyset does not exist (Exception from HRESULT: 0x80090016)
at Microsoft.Web.Administration.Interop.AppHostWritableAdminManager.CommitChanges()
at Microsoft.Web.Administration.Configuration.CommitChanges()
at Microsoft.Web.Administration.ConfigurationManager.CommitChanges()
at Microsoft.Web.Administration.ServerManager.CommitChanges()
at Project.Web.WebRole.OnStart() in E:\Projects\...\Web\WebRole.cs:line 57
If I change identity in IIS manager I don't get any error. What is wrong with my code and why do I get this error?
Updates to the applicationHost.config require administrative privileges. When you run locally, you are an administrator. In the cloud, your RoleEntryPoint runs as a normal user unless you elevate the role. Have you done so?
Check to see if you have <Runtime executionContext="elevated"/> specified inside your role declaration in ServiceDefinition.csdef.
Edit: Wade also showed how to do this using a slightly different method (check the comments). Try this as well
ok, here is my answer. This error occurs because NetworkService identity doesn't have Read access on the iisWasKey key. More information and how to resolve this problem I found here: "Keyset does not exist" error message when you try to change the identity of an application pool
I have a small C# solution used to check users credentials. It works fine for two of my teammates, but on my PC I get an exception.
The relevant code:
PrincipalContext context = new PrincipalContext(ContextType.Domain);
if (context.ValidateCredentials(System.Environment.UserDomainName + "\\" + usr, pwd))
return true;
else
return false;
And the exception is:
DirectoryOperationException, "The server cannot handle directory requests.".
I tried creating context with the explicit server name and the 636 port number, but this didn't help as well.
Any ideas?
I had this problem too using IIS Express and VS 2010. What fixed it for me was a comment on another thread.
Validate a username and password against Active Directory?
but i'll save you the click and search... :) Just add ContextOpations.Negotiate to you Validate Credentials call like below.
bool valid = context.ValidateCredentials(user, pass, ***ContextOptions.Negotiate***);
I had this issue: things were working on my dev machine but didn't work on the server. Turned out that IIS on the server was set up to run as LocalMachine. I changed it to NetworkService (the default) and things started working.
So basically check the user of the app pool if this is running on IIS.
I had to just create a new app pool and assign it .NET 2.0, then assign the new app pool to our web app, and it started working. We had .NET 3.5 SP2, so the hotfix wasn't ideal for us. Since the WWW service is usually Local System, I questioned that too. But since it was .NET and security related, I gave a shot at the app pool first and it worked.
Perhaps you need the hotfix?
FIX: DirectoryOperationException exception
And you are an Admin or the id that your service is running under is an Admin on your PC right?
I take it you already looked into this:
System.DirectoryServices.Protocols
"You may receive a less than helpful DirectoryOperationException(“The server cannot handle directory requests.”) what isn’t quite so amusing about this is that it didn’t even try to communicate with the server. The solution was to add the port number to the server. So instead of passing “Server” to open the LdapConnection, I passed “server:636”. By the way, LDAPS is port 636 – rather than the 389 port used by LDAP."
Good point, I wouldn't expect that Win7/.NET 3.5 would need that patch. How about the info provided in this question:
Setting user's password via System.DirectoryServices.Protocols in AD 2008 R2
I receive an "Access Deined" COMException when I try to connect to a remote IIS 6 server from my C# application that is running under IIS 5.1.
Any ideas? I am experiencing all the same issues with the original questions.
Update - 4/1/09
I found this solution (http://www.codeproject.com/KB/cs/Start_Stop_IIS_Website.aspx) that consists of a window application connecting to an IIS server to start and stop web sites. I am able to run it on my workstation and connect to the IIS server.
Ugh....why can I run this stand alone application but not my ASP.NET application?
Original
I receive an "Access Denied" COMException when I try to connect to IIS from a remote machine using the DirectoryEntry.Exist method to check to see if the IIS server is valid.
string path = string.Format("IIS://{0}/W3SVC", server);
if(DirectoryEntry.Exist(path))
{
//do something is valid....
}
I am a member of an active directory group that has been added to the Administrators groups to the IIS server I am trying to connect to.
Has anyone experience this issue and know how to resolve it?
UPDATE:
#Kev - It is an ASP.NET application. Also, I can connect without an username and password to the remote server through IIS6 Manager.
#Chris - I am trying to connect to the remote server to display the number of virtual directorys and determine the .NET framework version of each directory. See this SO question.
#dautzenb - My ASP.NET application is running under IIS 5.1 trying to connect to an IIS 6 server. I can see fault audits in the security log for my local ASPNET account on the remote server. When I try to debug the application, I am running under my domain account and still get the Access is denied.
UPDATE 2:
#Kev - I was able to establish to create a DirectoryEntry object using the following overload:
public DirectoryEntry
(
string path,
string username,
string password
)
But, all of the properties contain a " threw an exception of type 'System.Runtime.InteropServices.COMException'" while I debug the app.
Also, the AuthenticationType property is set to Secure.
UPDATE 3:
The following two failure audit entries were in the remote IIS server's security event log every time I tried to establish a connection:
First event:
Event Category: Account Logon
Event ID: 680
Log attempt by: MICROSOFT_AUTHENTICATION_PACKAGE_V1_0
Logon account: ASPNET
Source Workstation:
Error Code: 0xC0000234
Second event:
Event Category: Logon/Logoff
Event ID: 529
Logon Failure:
Reason: Unknown user name or bad password
User Name: ASPNET
Domain: (MyDomain)
Logon Type: 3
Logon Process: NtLmSsp
Authentication Package: NTLM
Workstation Name: (MyWorkstationId)
Caller User Name: -
Caller Domain: -
Caller Logon ID: -
Caller Process ID: -
Transited Services: -
Source Network Address: 10.12.13.35
Source Port: 1708
Impersonation is set to true and the username and password are blank. It is using the ASPNET account on the remote IIS server.
If it is an identity problem, you could try setting your IIS 5.1 application to use Integrated Windows Authentication, and then add the following to you web.config on your IIS5.1 web site under system.web to enable impersonation.
<identity impersonate="true"/>
<authentication mode="Windows" />
Since this is an ASP.NET application, it runs in an Application Pool of IIS. This Application Pool runs using a specific user("Local System", "Network Service" or another user).
Does this user have enough rights to connect to a remote server ?
See MSDN for more info.
This looks like it may be a double-hop issue. If you are impersonating the current user of a website using NTLM, that impersonation is only valid on that server (your IIS 5.1 server in this case). If you try to connect to another server using the web site, you are actually going to have issues as it cannot pass the token to another server that was used during impersonation. The same is true if you are debugging your site through your machine, going to another box. Your local machine is authenticating you, but it cannot impersonate you to another server.
All of the solutions I have used in the past require you to hard code the app pool to use an account that has permissions, set the annony. account to a domain account with permissions on the other machine, or use a windows service running on the IIS 5.1 machine, under a domain account, to connect to the other server.
If you are using Kerberos, this wouldn't apply, but AD uses NTLM by default.
Where exactly are you trying to read too? Is it in under the same path as your application?
When I had this problem, I found that simply authenticating my self on a Windows file share solved the problem. From experience, I think that WMI/ADSI/COM doesn't have great support for not-already-authenticated users. I believe this issue occurs when you're not associated with a Windows domain.
If it is indeed a NTLM doublehop issue you could use the SETSPN utility to create service principal named instances for your target IIS servers.
Then you could go into Active Directory, and then allow the computer object (basically the NETWORK SERVICE or LOCAL SERVICE principals) to delegate its credentials to a correctly registered SPN.
Then you could hop-hop-hop all over the place! But, be warned! People can hurt themselves on sharp pointy things when you enable double-hop!
Good KB articles to read:
http://support.microsoft.com/kb/929650
I believe that DirectoryEntry.Exists silently ignores any credentials supplied and uses the creds of the authenticated user. This seems to match the behaviour you've described. For AD work, we never use it for this reason.
I'm sort of stumped at the moment as to why you can't get this working. There is a temporary work around you could try. When instantiating the DirectoryEntry object you could use one of the following constructor overloads:
public DirectoryEntry(
string path,
string username,
string password
)
Documented at: MSDN: DirectoryEntry Constructor (String, String, String)
...or...
public DirectoryEntry(
string path,
string username,
string password,
AuthenticationTypes authenticationType
)
Documented at: MSDN: DirectoryEntry Constructor (String, String, String, AuthenticationTypes)
As it happens I'm building a test AD environment on my virtual server box for a new project to do similar stuff. When I get it up and running I'll have a play around to see if I can reproduce the problem you're encountering. In the meantime let us know what happens if you try these constructor overloads referenced above.
Update (In answer to Michaels comment):
For reasons that evade me just now, we couldn't use DirectoryEntry.Exists() in a particular scenario, there is this snippet of code that gets called now and again in one of our apps:
public static bool MetabasePathExists(string metabasePath)
{
try
{
using(DirectoryEntry site = new DirectoryEntry(metabasePath))
{
if(site.Name != String.Empty)
{
return true;
}
return false;
}
}
catch(COMException ex)
{
if(ex.Message.StartsWith("The system cannot find the path specified"))
{
return false;
}
LogError(ex, String.Format("metabasePath={0}", metabasePath));
throw;
}
catch(Exception ex)
{
LogError(ex, String.Format("metabasePath={0}", metabasePath));
throw;
}
}
You could replace the constructor with one of the ones from above. Admittedly it's a stab in the dark :).