C# LDAP AD Authentication - Separate Network - c#

I am working on a solution to use Active Directory to authenticate our applications, we are using LDAP protocol, we have several domain controllers that all sync together.
I have built out an application that works with Active Directory authentication using our internal domain controller which is on the same network I run the application, as soon as I point to the Domain Controller hosted on Amazon (separate network) I get an error response saying the server can not be contacted. When I open command prompt and ping the server using its domain name I can get a reply.
The AWS AD server is new and was spun up just a few weeks ago for this project, I can't tell if this is an issue with the AD setup on that machine or if I have to modify my code to access AD on a separate network. Here is the function I use to authenticate against AD - again this works on the domain controller hosted on the same network I'm executing but as soon as I update the config to use the AWS DC it fails. Sys admins are looking into the server to make sure everything is OK, but I'm expecting that they will say everything is configured correctly.
public bool IsAuthenticated(string username, string pwd)
{
var domainAndUsername = _domain + #"\" + username;
var entry = new DirectoryEntry(_ldapPath, domainAndUsername, pwd);
_username = username;
try
{
//Bind to the native AdsObject to force authentication.
var unused = entry.NativeObject;
var search = new DirectorySearcher(entry) {Filter = "(SAMAccountName=" + username + ")"};
search.PropertiesToLoad.Add("cn");
var result = search.FindOne();
if (result == null) return false;
//Update the new path to the user in the directory.
_ldapPath = result.Path;
_filterAttribute = (string) result.Properties["cn"][0];
}
catch (Exception ex)
{
// Exception Handling
// throw
}
return true;
}

When I open command prompt and ping the server using its domain name I
can get a reply.
Ping is not a good test. Just because ping works, doesn't mean that anything else will or will not work.
A better test is using the telnet client. It's a quick test that opens a TCP connection on whatever port you specify. That will tell you if there are any firewalls in the way preventing the connection.
It uses port 389 by default, so try that. From the command prompt:
telnet domain.com 389
A blank screen means the connection succeeded. If it fails, it will say so.
Here are the ports that Active Directory uses. You can specify a different port if you want to.
389: LDAP - single domain only (the default if you don't specify anything)
3268: Global Catalog - your whole forest
636: LDAP over SSL
3269: Global Catalog over SSL

Related

C# Remove AD Computer Account Running on Windows Service

I want to write a function in a windows service application to remove a given computer name from Active Directory.
The Windows service is running on a machine which is domain-joined to the DC. Currently I have logged in to this machine with domain admin account.
The Windows service is running under the security context of "NT AUTHORITY/SYSTEM" and this should not be changed, as there shouldn't be any user interaction after installing the application, meaning that admin shouldn't enter their credentials in services.
When I run the application with my newly added code to delete the computer account, it doesn't work. However, when I change the logon info on the Windows Service and update that with domain admin credentials, it's able to successfully remove the computer account from AD.
Below is [a shortened version of] the function used to delete computer accounts.
Is there any way I can modify the code to be able to remove Computer Accounts using the same security context (NT AUTHORITY/SYSTEM)?
private void DeleteComputerAccount(string CompName, DirectoryEntry DirEntry)
{
try
{
//Delete computer account
DirectorySearcher search = new DirectorySearcher(DirEntry, "(name=" + CompName + ")");
SearchResult res = search.FindOne();
res.GetDirectoryEntry().DeleteTree();
}
catch (Exception)
{
Throw();
}
}
Where DeleteComputerAccount is called:
DirectoryEntry dirEntry = new DirectoryEntry("LDAP://domain.contoso.com");
string compName = "MyWorkstation01";
DeleteComputerAccount(compName, dirEntry);
When a service runs as local system, it will access the network (and thus AD) in the security context of the host's computer account. You can delegate the computer account (or better, a group which the computer is a member of) the ability to delete objects from AD. This link has accurate advice on how to complete that task - http://sigkillit.com/2013/06/12/delegate-adddelete-computer-objects-in-ad/
While not what you asked, a couple other things stand out to me:
You're not filtering your search very well. You might get something other than what you want back. I'd suggest a filter like this instead: (&(objectClass=computer)(sAMAccountName=<PCName>$))
Local system is a lot of access. Could you run this as network service instead?

Access denied when reading / writing to network location as a remote process

I'm currently trying to launch a process on a remote machine using WMI in C#. The process reads and writes to a file that is stored on a separate server.
When I manually login to the remote machine, I can run the process and it all works fine.
However, when I try to launch the process on the remote from my local machine using WMI, I get the following error:
System.UnauthorizedAccessException: Access to the path '\\server\path\input.txt' is denied.
I've tried multiple connection options, but I'm not sure how to re-create the permissions that I seem to have when I login manually... What do I need to do?
Local machine code
static void LaunchRemoteProcess(string remoteMachine, string command)
{
ConnectionOptions connectionOptions = new ConnectionOptions
{
Impersonation = ImpersonationLevel.Impersonate,
EnablePrivileges = true
};
var managementScope = new ManagementScope(string.Format(#"\\{0}\root\cimv2", remoteMachine), connectionOptions);
managementScope.Connect();
var managementPath = new ManagementPath("Win32_Process");
var objectGetOptions = new ObjectGetOptions();
var managementClass = new ManagementClass(managementScope, managementPath, objectGetOptions);
// Launch the command asynchronously
var inParams = managementClass.GetMethodParameters("Create");
inParams["CommandLine"] = command;
var outParams = managementClass.InvokeMethod("Create", inParams, null);
}
Remote machine code
string networkPath = #"\\server\path";
string inputFile = "input.txt";
string inputText = File.ReadAllText(Path.Combine(networkPath, inputFile));
string outputFile = "output.txt";
File.WriteAllText(Path.Combine(networkPath, outputFile), inputText);
Edit 1
I have already tried using the credentials of the user for which the process works if I log on to the remote machine manually and the process still fails with the same error:
ConnectionOptions connectionOptions = new ConnectionOptions
{
Username = "username",
Password = "password",
Authority = "ntlmdomain:COMPANYNAME.CO.UK,
EnablePrivileges = true
};
Am I missing something with regards to the Authority, Authentication, or Impersonation attributes?
Impersonation vs Delegation
Your WMI code uses impersonation, so the server side runs in the security context of the user who calls the code on the client. But this is only valid on the server itself, not for accessing e.g. a remote CIFS share (as in your case).
You have to use delegation.
First, change
Impersonation = ImpersonationLevel.Impersonate,
to
Impersonation = ImpersonationLevel.Delegate,
If you get an exception then, delegation does not yet work in your environment.
Check:
Calling user account: "Account is sensitive and cannot be delegated" must not be checked in the user properties (Active Directory Users and Computers)
server machine account: "Trust this computer for delegation to any service..." must be checked
local security policy on the server: "Enable computer and user accounts to be trusted for delegation" must include the calling user.
See
https://msdn.microsoft.com/en-us/library/aa389288%28VS.85%29.aspx
for further information on this topic.
Added: (see the comments below):
If Delegate is not an option in your environment (e.g. group policies do not allow for this, and you do not have the rights to change them), you may check some alternative ways.
You probably heard of psexec.
Or, what I did some years ago, and which runs in production in a enterprise environment on a few servers for many years very successfull:
I created a scheduled task which starts a program and set the technical user + password for this task. The task was configured for "run once in year 2200 :-)".
Then I wrote commands in a queue (I used a simple command file) and started the task from a remote machine.
Doing it this way, delegation is not required, since the scheduled task itself logs on as the technical user account ("logon as batch" privs are required).
As the reason states, the user id you are using on your PC does not seem to have access for to another computer's location (though it is a server, it is some other computer).
You may get access for your user id or use Impersonation to use an user id that already has access to the location.
Find more details here: https://msdn.microsoft.com/en-us/library/w070t6ka%28v=vs.110%29.aspx
Edited: Add user name password too. That may help.

Connecting to Active Directory via .NET

I have a virtual machine with Active Directory that I want to connect to using .NET, I've already connected to an ubuntu machine running OpenLDAP but when connecting to AD it's not working smoothly.
The code I'm attempting to connect with is as follows:
var directoryEntry =
new DirectoryEntry("LDAP://192.168.1.1", #"EXAMPLE\Administrator", "Abc1234");
try
{
var test = directoryEntry.NativeObject;
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
Watching the locals window the variable directoryEntry's Guid, name etc says "Function evaluation timed out".
Then when it arrives at the try block it simply says "The server is not operational".
I've also tried this code, and it fails at the "ldap.bind" telling me that "the ldap-server is unavailable".
using (var ldap = new LdapConnection("192.168.1.1:389"))
{
ldap.AuthType = AuthType.Basic;
ldap.SessionOptions.ProtocolVersion = 3;
ldap.Bind(new NetworkCredential(#"EXAMPLE\Administrator", "Abc1234"));
}
I know the server is up and running, I know that they have a connection (machines can ping each other) but I can't figure out why it isn't working. Can any of you see if there are any flaws in the code? (and yes I've googled all of the errors and various questions about connecting to AD before asking this question but none of the solutions have worked).
If you domain name is 'example.com' and let say you have an organization unit (OU) called 'users'. This works perfectly fine for me.
However the machine where this code runs, is added to the AD domain and it runs with an AD user account. If you do not have a machine added to the same domain which you are querying, you may try "Run as" option (Shift + Right Click) to launch the program or visual studio.
public static List<string> GetAllUsers()
{
List<string> users = new List<string>();
using (DirectoryEntry de = new DirectoryEntry("LDAP://OU=Users,DC=example,DC=local"))
{
using (DirectorySearcher ds = new DirectorySearcher(de))
{
ds.Filter = "objectClass=user";
SearchResultCollection src = ds.FindAll();
foreach (SearchResult sr in src)
{
using (DirectoryEntry user = new DirectoryEntry(sr.Path))
{
users.Add(new string(user.Properties["sAMAccountName"][0].ToString()));
}
}
}
}
return users;
}
I tested your code (with changed domain / password...) in my own Active Directory test environment, it works. If I use a wrong password intentionally for testing purpose, I get "invalid credentials". Fine.
"The server is not operational" will be returned if LDAP is completely unavailable. So it seems that e.g. port 389 is not reachable.
Firewall ?
LDAP SSL (port 636) ??
If you do not have a machine added to the same domain which you are querying, you may try "Run as" option (Shift + Right Click) to launch the program or visual studio.
Yes, this was my first idea too as I saw this question. However, it seems that you will get another error in this case.
What I suggest: install WireShark, the network analyzer and check what is sent over the wire (assuming your AD is running on another machine).
WireShark helped me more than often to diagnose errors with AD, login, SMB or other protocols.
PS:
The answer from Ravi M Patel is a nice example of searching, I almost do the same in my own code.
Problem solved. I had two network adapters and adapter 1 had dhcp and the static ip I was attempting to connect was running on adapter 2. I simply gave adapter 1 the static adress and was able to connect that way, seemingly the code connects via adapter 1 per default. And Thanks for all the answers guys :)

How to run application in an authenticated manner

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.

How to RDP into a Terminal Server THEN access Active Directory on an internal-only DC

I have a dev network setup modeled after a production setup at work, and I've been able to successfully query Active Directory when I'm in the same subnet and can resolve the server. Now, I'm trying to set this up where I am required to remote into a terminal server, which is the only internet accessible server, and use that connection to gain access to an internal-only domain controller where I can then run my queries (all in C#). Maybe a better way of explaining this would be I'm trying to turn this RDP connection into a network bridge of sorts, where I can use the internal address of the DC (such as 192.168.1.1) from across the internet when I create my LDAP path.
Is RDP the right thing to use? I found this off of the code project, but it appears to be for setting up a remote desktop as opposed to allowing me access to the internal DC:
// RDP test
rdp.Server = "firewall";
rdp.UserName = "Administrator";
IMsTscNonScriptable secured = (IMsTscNonScriptable)rdp.GetOcx();
secured.ClearTextPassword = "mypassword";
rdp.Connect();
string moo2 = rdp.UserName;
string moo = rdp.ProductName;
rdp.Disconnect();
My dev TS is called "firewall", and from there I want to be able to execute the code below against the DC:
// Fire up the directory
DirectoryEntry ADRoot = new DirectoryEntry();
ADRoot.Username = "myusername";
ADRoot.Password = "mypassword";
ADRoot.Path = "LDAP://192.168.1.11";
// Search for all the computer objects
DirectorySearcher searcher = new DirectorySearcher();
searcher.Filter = "(&ObjectCategory=computer)";
searcher.SearchRoot = ADRoot;
SearchResultCollection results = searcher.FindAll();
I'm all for just about any approach that will let me hit this internal DC from over the internet.
After much google research, it looks like the best approach will require a VPN connection. Once I have an established VPN connection (doesn't matter where or what server in the network lets me in), I can then use the LDAP protocol.
Yes VPN basically extends the network to your machine as well. in some cases for security reasons you wont get this access. RDP is only a screen on a machine running there and does not fit your needs.

Categories