I want to bind to to a Directory Object on Active Directory using gMSA in a C# service.
What I am aware (… and able to do ):
If the C# service is running in the context of the gMSA, then following code works fine (this is password less bind):
DirectoryEntry entry = new DirectoryEntry("LDAP://172.5.25.5:389/CN=MyUser,OU=MyOU,DC=Example,DC=lab");
_log.info("Authentication Type: " + entry.AuthenticationType.GetTypeCode());
_log.info("Name is: " + entry.Name);
Question 1
(Before actually asking the question, I will try to give the background and provide the code for my attempts)
But what I actually want to do is to bind to the Directory Object like a normal User/ Service account!
Reasons:
- I don’t want to run the service within the context of a particular gMSA
- This is because, the C# service intends to connect with multiple DCs (Cross Domain and Cross Forests)
- If the C# service runs under the context of particular gMSA, then that particular gMSA needs to have permissions over all the DCs (cross domain as well as cross forest) which I want to avoid
- Hence, I want to use dedicated gMSA accounts for each DC.
Why gMSA:
- We want to utilise the gMSA feature wherein its password will be managed by the Active Directory.
- This way, the C# service doesn’t have to be updated with the passwords every now and then. This would be the case if we intend to use normal User accounts or Service Accounts (by the way, this is what we have to do now)
- We want to eliminate the password management in our C# service.
- Hence, use the gMSAs
Assumptions and Prerequisites
- C# service is running under the context of the Administrator of Domain A
- This Administrator will will be added to the
principalsAllowedToRetrivePassword
for all the local/cross domain/ cross forest gMSAs of the domains to which the service needs to bind for LDAP connection - Now the Administrator can retrieve the password for each gMSA. I want to use this password to bind to the respective domains using corresponding gMSA accounts!
For a normal User account, this is how we usually bind to a Directory Object using its username and password:
DirectoryEntry entry = new DirectoryEntry("LDAP://172.5.25.5:389/CN=MyUser,OU=MyOU,DC=Example,DC=lab",
"[email protected]", "P@ssw0rd!", AuthenticationTypes.Secure);
But when I do the same with gMSA it fails with error: Username or password is incorrect
Here are more details in to the code:
Retrieve the gMSA Password:
using (PowerShell ps = PowerShell.Create())
{
// This will succeed since the C# service is running under the context of
//Administrator and is been added to principalsAllowedToRetrievePassword for the concerned gMSA
ps.AddCommand("Get-ADServiceAccount")
.AddParameter("Identity", gMSA)
.AddParameter("Properties", "msds-ManagedPassword");
Collection<PSObject> results = ps.Invoke();
foreach (PSObject result in results)
{
Object managedPasswordBlob = result.Properties["msds-ManagedPassword"].Value;
ps.Commands.Clear();
// This is a DS-Internals hack to get the password for gMSA in clear text
// I have installed their package on my machine...
// This code is pretty straight forward can can directly incorporated in code
// without the need for the package installation...
ps.AddCommand("ConvertFrom-ADManagedPasswordBlob")
.AddParameter("Blob", managedPasswordBlob);
Collection<PSObject> passwordResults = ps.Invoke();
if (passwordResults.Count > 0)
{
string currentPassword = passwordResults[0].Properties["CurrentPassword"].Value.ToString();
}
}
}
This code perfectly works fine and I get the cleartext password for the gMSA. But when I use the same in following piece of code, I get an error saying: username or password is incorrect
The code that fails:
// note: currentPassword is what I got through the above snippet!
DirectoryEntry entry = new DirectoryEntry("LDAP://172.5.25.5:389/CN=MyUser,OU=MyOU,DC=Example,DC=lab",
"[email protected]", currentPassword, AuthenticationTypes.Secure);
Note: I tried this with following LDAP URLS but with no luck:
a. LDAP://example.lab:389/CN=MyUser,OU=MyOU,DC=Example,DC=lab
b. LDAP://hostname.example.lab:389/CN=MyUser,OU=MyOU,DC=Example,DC=lab
Now, getting back to the actual question: What am I doing wrong here? Is this even possible?
Question 2
What worked for me?
I was able to form the LdapConnection using the gMSA and its retrieved password:
LdapConnection connection = new LdapConnection(new LdapDirectoryIdentifier("hostname.example.lab", 389, false, false));
NetworkCredential cred = new NetworkCredential("gMSA", currentPassword, "example.lab");
connection.SessionOptions.Sealing = true;
connection.SessionOptions.SaslMethod = "GSSAPI";
connection.Credential = cred;
try
{
connection.Bind();
SearchRequest searchRequest = new SearchRequest();
searchRequest.DistinguishedName = "CN=My,OU=MyOU,DC=Example,DC=lab";
searchRequest.Scope = System.DirectoryServices.Protocols.SearchScope.Subtree;
searchRequest.Attributes.Add("SamAccountName");
DirectoryResponse response = connection.SendRequest(searchRequest);
}
catch(Exception e)
{
//....
}
This code works perfectly fine!
So:
- If I am able to correctly for the
LdapConnection
using the gMSA’s password, then why can’t I for theDirectoryEntry
using the same? - Will I be able to form the
DirectoryEntry
object, either from theLdapConnection
object/DirectoryResponse
object? - Will this solution be scalable, meaning will I be able to achieve the same for all the gMSA’s in question ? (cross domain and cross forest per se? )
Question 3: Security Considerations
- Is my approach secure? That is is it okay if the Administrator to be added to principalsAllowedToRetrievePassword for the respective gMSA’s. Will it be accepted, along with the intended use by wider audience/ customers?
- What security aspects would be of major concern over here?
- Since the problem I am solving is of password management, I will have to use some or the other workaround!
- Are there any concerns of adding the Administrator to the principalsAllowedToRetrievePassword list of the gMSAs?
- What do you feel about my approach of retrieving the password internally and then using it (I know gMSAs are not meant for this, but is there any alternative to what I am solving? )
Thanks in Advance!