Why?
Great question! Here are a few lame excuses I was able to come with:
- I like to use command line. This is a lame excuse because Windows have powershell. But,
- I am more comfortable with Linux than Windows. Lame excuse since
- How many posts in this very blog I have made about using Windows?
- How many of said posts I have used the GUI when I could take care of business with Powershell?
- What is wrong with Powershell, at least of the applications I have used it for here so far?
- How many posts in this very blog I have made about using Windows?
- I like to be able to access the network resources from any machine in the network running any OS. If I have a Linux box in an Active Directory-controlled network, chances are I will need to authenticate the Linux box against Active Directory (AD so I can save some keytaps). AD is Kerberos + ldap + sprinkles, so I better be able to use the usual kerberos/ldap Linux tools as one day I will need to figure out why things are boink.
- It feels like I get more info using ldapsearch than the Windows tools, which is good when I do not know the name of an attribute, or how many instaces of said attribute are in use.
Using ldapsearch
Before we go mindlessly typing things, we need some data.- We need to know the name of the ldapserver.
- Yes, if you have it configured in your ldap.conf file, you should not need it. But I prefer to assume nothing. If the domain was setup properly, we can ask it directly by typing nslookup -type=srv _ldap._tcp.DOMAIN where DOMAIN is the Active Directory domain name, not the DNS one; that caught me off guard. So, if our DNS dmain is example.com and the AD domain (we are very original) is ad.example.com, we have
raub@desktop:/tmp$ nslookup -type=srv _ldap._tcp.ad.example.com Server: 192.168.0.10 Address: 192.168.0.10#53 Non-authoritative answer: _ldap._tcp.ad.example.com service = 0 100 389 ADDC0.ad.example.com. _ldap._tcp.ad.example.com service = 0 100 389 ADDC2.ad.example.com. _ldap._tcp.ad.example.com service = 0 100 389 ADDC1.ad.example.com. Authoritative answers can be found from: ad.example.com nameserver = addc1.ad.example.com. ad.example.com nameserver = ns.example.com. ad.example.com nameserver = ns2.example.com. ad.example.com nameserver = addc0.ad.example.com. ad.example.com nameserver = addc2.ad.example.com. ADDC0.ad.example.com internet address = 192.168.1.100 ADDC1.ad.example.com internet address = 192.168.1.102 ADDC2.ad.example.com internet address = 192.168.1.101 ns.example.com internet address = 192.168.0.10 ns2.example.com internet address = 192.168.0.10 raub@desktop:/tmp$
and we can use any of the ADDCN.ad.example.com (where N=0,1,2). Notice in my setup, just using ad.example.com also worked. I found out by using netcat to see if port 636 was open (I will leave the answer for "why port 636?" as an exercise to the reader)raub@desktop:~$ nc -v ad.example.com 636 Connection to ad.example.com 636 port [tcp/ldaps] succeeded! ^C raub@desktop:~$
- We need to be able to authenticate against AD somehow.
- For this discussion I will be using a username and password; we can also do it using a Kerberos TGT ticket.
Fun Fact: I have a user account, my normal one, which can look into some things in LDAP/AD but then I have another ("admin") account I can see more and edit stuff in AD. You will see later on me forgetting completely about that and how it affects me. But we are getting ahead of ourselves.
With that taken care of, we are going to begin by looking for some user: me
raub@desktop:~$ ldapsearch -H "ldaps://addc0.ad.example.com:636" -D "raub@ad.example.com" -W -b "dc=ad,dc=example,dc=com" -LLL -s sub "(CN=raub)" Enter LDAP Password: dn: CN=raub,OU=Users,OU=Identity,DC=ad,DC=example,DC=com objectClass: top objectClass: posixAccount objectClass: person objectClass: organizationalPerson objectClass: user cn: Wrong Droid sn: Droid title: Entropy Creators description: Orthodontics givenName: Wrong initials: B distinguishedName: CN=raub,OU=Users,OU=Identity,DC=ad,DC=example,DC=com instanceType: 4 whenCreated: 20080109142820.0Z whenChanged: 20180905201157.0Z displayName: Droid, Wrong uSNCreated: 243336 memberOf: CN=cookie_recipes,OU=Distribution Groups,OU=Special Users,DC=ad,DC=example,DC=com memberOf: CN=servers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com memberOf: CN=third_floor_printers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com [...] proxyAddresses: sip:raub@ad.example.com proxyAddresses: smtp:raub@ad.example.com proxyAddresses: X500:/o=UNC Exchange/ou=Exchange Administrative Group (FYDIBOH F23SPDLT)/cn=Recipients/cn=raub proxyAddresses: SMTP:raub@email.example.com displayNamePrintable: Wrong Droid name: Wrong Droid [...] sExchPoliciesExcluded: {26491cfc-9e50-4857-861b-0cb8df22b5d7} msExchUserAccountControl: 0 msExchELCMailboxFlags: 2 msRTCSIP-PrimaryHomeServer: [...] msExchOWAPolicy: CN=Default,CN=OWA Mailbox Policies,CN=Exchange,CN=Microsoft Exchange,CN=Services,CN=Configuration,DC=ad,DC=example,DC=com # refldaps://ForestDnsZones.ad.example.com/DC=ForestDnsZones,DC=ad,DC=example,DC=com # refldaps://DomainDnsZones.ad.example.com/DC=DomainDnsZones,DC=ad,DC=example,DC=com # refldaps://ad.example.com/CN=Configuration,DC=ad,DC=example,DC=com raub@desktop:~$
That is probably more info than you wanted to know about someone, but this has its applications. Since we now know every attribute associated with a given user (I should not be that special, at least as far as AD is concerned), we can build customized queries looking for only a specific bit of info. For instance, let's just get the groups I belong to or am a member of (hint hint):
raub@desktop:~$ ldapsearch -H "ldaps://addc0.ad.example.com:636" -D "raub@ad.example.com" -W -b "dc=ad,dc=example,dc=com" -LLL -s sub "(CN=raub)" memberOf dn: CN=raub,OU=Users,OU=Identity,DC=ad,DC=example,DC=com memberOf: CN=cookie_recipes,OU=Distribution Groups,OU=Special Users,DC=ad,DC=example,DC=com memberOf: CN=servers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com memberOf: CN=third_floor_printers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com
Fancy, huh? Now, if instead of doing (CN=raub) we did (CN=*raub*), it would return every entry that has a CN with raub in it. In my case that would mean two entries, raub and raub.admin (if you remember our Fun Fact you will know about it), but it could also have returned a device whose name matches that. And, they would have been printed one after the other (I do wonder if the order depends on the order they were added to LDAP/AD).
Using ldapmodify
Ok, we established we can probulate Active Directory using common household Linux/UNIX query tools. What if we want to change something? Let's say we have an AD group (specifically a distribution list) called moustache_operators (for those who own and operate moustaches) and want to add a member and delete another.
Why I do like ldapmodify to edit LDAP/AD
Main reason is because I can create a file (in the LDIF format) at my leisure (i.e. think about what I want to do) with pretty commands and comments describing what I want to do. If I like what I did, I can then document it and maybe even save the file in a wiki or somewhere that can be fed to Ansible/Puppet/Chef/Docker and reused.
Our little LDIF file, let's call it change.ldif, could look like this:
# Let's define the entity we will be fiddling with dn: CN=moustache_operators,OU=Distribution Lists,OU=Special Users,DC=ad,DC=example,DC=com # And then what we will be doing with it changetype: modify delete: member member: CN=baldone,OU=Users,OU=Identity,DC=ad,DC=example,DC=com # Separator because we will be doing another change - add: member member: CN=raub,OU=Users,OU=Identity,DC=ad,DC=example,DC=com
So let's try it:
raub@desktop:~$ ldapmodify -H "ldaps://addc0.ad.example.com:636" -D "raub@ad.example.com" -x -W -f change.ldif Enter LDAP Password: modifying entry "CN=moustache_operators,OU=Distribution Lists,OU=Special Users,DC=ad,DC=example,DC=com" ldap_modify: Insufficient access (50) additional info: 00002098: SecErr: DSID-03150F93, problem 4003 (INSUFF_ACCESS_RIGHTS), data 0 raub@desktop:~$
Why is it not working? Well, do you remember the Fun Fact I mentioned earlier in this article? This is how it shows it's ugly head. I should have used my raub.admin@ad.example.com account instead of raub@ad.example.com. If we do it right, it then works. I will not show the output of a successful connection here; what matters is verifying the deed is done, and we can do it using dear ol' ldapsearch:
raub@desktop:~$ ldapsearch -H "ldaps://addc0.ad.example.com:636" -D "raub@ad.example.com" -W -b "dc=ad,dc=example,dc=com" -LLL -s sub "(CN=raub) memberOf" dn: CN=raub,OU=Users,OU=Identity,DC=ad,DC=example,DC=com memberOf: CN=cookie_recipes,OU=Distribution Groups,OU=Special Users,DC=ad,DC=example,DC=com memberOf: CN=servers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com memberOf: CN=third_floor_printers,OU=Groups,OU=APE,OU=EXAMPLE,DC=ad,DC=example,DC=com memberOf: CN=moustache_operators,OU=Distribution Lists,OU=Special Users,DC=ad,DC=example,DC=com
What about Mac/OSX?
They too have ldapsearch; just use the terminal and off you go.