Creating User Accounts Using LDAP

May 23rd, 2009

I’ve expanded my RADUM sync() method to create Windows user accounts using LDAP alone. This means I won’t have to resort to using the Windows command line. I thought this would be possible, especially after successfully creating Windows groups. I found out that I was in for a world of pain (at least initially). I kept getting error code 53: “Unwilling to perform”. I figured that I was violating some Windows security rules somewhere. I searched online like mad, and I was able to get some hints on what the problems might be, though none of those solutions were Ruby related in any way :-)

So, how does one create a Windows user account using only LDAP? There are a couple of potential problems to deal with which I will discuss inline. The basic steps I am using are:

  1. Add the user account as a normal account, disabled, and with no password required.
  2. Replace the unicodePwd and userAccountControl AD attributes to at least remove the no password required attribute (and enable the account if it should be, which it should most of the time).
  3. Set the pwdLastSet AD attribute to 0 if the user must change his password on the first login.
  4. Fix the primaryGroupGID AD attribute if the primary Windows group is not Domain Users. Note that in the first step, the account was created with Domain Users as the primary Windows group. That seems like the easiest way to accomplish this because there is additional group logic needed if the primary Windows group is not the default Domain Users group. Most of the time this would not be required, but I want to make sure it is possible.

I am not going to post all the code from my create_user() method. It’s in the RADUM Git repository, and it is not production ready yet. It does work though. I will outline one sticky problem it took me a while to figure out though. The unicodePwd AD attribute can be set using LDAP to set the user’s Windows password. This attribute expects a UTF-16LE encoded value. This is actually Base64 encoded when it is handled over LDAP as well, but when specifying the value in the LDAP method you can just give it the UTF-16LE value. There are numerous sources online that point out the UTF-16LE format for this attribute. Microsoft has some documentation here. The first thing to note about the password value is that it has to be surrounded by explicit double quotes. Ruby has an NKF class that can easily encode values as UTF-16BE:

NKF.nkf('-w16m0', '"foo"')

But I could not get anything to work with respect to getting UTF-16LE using the NKF class. I did some digging and figured out that UTF-16LE for ASCII characters is simply the character itself followed by a NULL byte. That’s very convenient since I only care about handling ASCII characters for the password (sorry everyone who wants to use more characters :-). More information about this can be found on the UTF-16/UCS-2 Wikipedia page and in this extremely helpful email. This is how I encode the user’s password for the unicodePwd AD attribute:

# Convert a string to UTF-16LE. For ASCII characters, the result should be
# each character followed by a NULL, so this is very easy. Windows expects
# a UTF-16LE string for the unicodePwd attribute. Note that the password
# Active Directory is expecting for the unicodePwd attribute has to be
# explicitly quoted.
def str2utf16le(str)
  ('"' + str + '"').gsub(/./) { |c| "#{c}\0" }
end

Note that setting the unicodePwd AD attribute also requires using TLS as well or else error code 53 will be returned. I was able to do everything else except set the password unless I switched to TLS, so for now I’ve made my code require TLS. This means that a domain must have a certificate server. On my test server I added the role. Without a certificate server, connections to port 636 on my server would simply disconnect, even though the system was “listening”.

The second pain was dealing with setting the primaryGroupID AD attribute. Most of the time this would not be necessary. Users normally have Domain Users as their primaryGroupID, but as I’ve mentioned before, I want to be able to set this to whatever (there are restrictions that I check in the code). You can’t just set the primaryGroupID AD attribute to the right group. That also returned the dreaded error code 53. When using the GUI tools in Windows, I noted that you have to first add the user as a member of the target group, then you can set their primary Windows group to be that group. This also handles the magic where the user is removed from the member AD attribute for the target group and added to the member AD attribute for the previous primary Windows group. Recall that users are members of their primary Windows group by way of their primaryGroupID AD attribute alone. So, I simply did the same thing:

# The user already has the primary Windows group as Domain Users
# based on the default actions above. If the user has a different
# primary Windows group, it is necessary to add the user to that
# group first (as a member in the member attribute for the group)
# before attempting to set their primaryGroupID attribute or Active
# Directory will refuse to do it.
unless rid == find_group("Domain Users").rid
  ops = [
    [:add, :member, user.distinguished_name]
  ]

  puts ops.to_yaml
  @ldap.modify :dn => user.primary_group.distinguished_name,
                :operations => ops
  check_ldap_result

  ops = [
    [:replace, :primaryGroupID, rid.to_s]
  ]

  puts ops.to_yaml
  @ldap.modify :dn => user.distinguished_name, :operations => ops
  check_ldap_result
end

I am outputting the ops array to YAML format for now. That will be removed eventually of course, but it is useful for now.

My general algorithm for synchronizing with Active Directory seems to be something along the lines of: create all the groups that do not already exist (sans any user memberships since those users might not exist yet), create all user accounts that don’t already exist yet, then deal with group membership issues. I’ve not gotten to the last part yet, but at least I can now create a Windows user and set their password using LDAP alone. That’s great!

Oh yeah, I forgot that I also have to deal with any modified attributes for accounts that were not created by the first part of the algorithm. I am still working on this, but first I had to figure out if I could create users and groups using only LDAP.

Active Directory, Ruby

First Steps Toward RADUM sync() Method

May 19th, 2009

Since I bought the D90 and Aperture 2, I’ve not done much with my RADUM module. I decided I better get it up to speed and accomplish something. All along I thought that I’d need to use Windows commands (dsadd, dsmod, etc.) to create objects in Active Directory. I tried using the add() method of the Net::LDAP module to create a group and it worked! I am so happy. I might be able to do this as a pure Ruby module. I did not know if it would work since I can’t necessarily specify all the attributes, but I was hoping Active Directory would take care of the rest. So far it does. I couldn’t be happier about that.

I did have a little trouble at first because I did not catch that the attributes hash used with the add() method needs to contain string values. When creating a group so far, the code looks like this:

dn = group.distinguished_name

# Note that all the attributes need to be strings in this hash.
attr = {
  :cn => group.name,
  :groupType => group.type.to_s,
  :name => group.name,
  # All groups are of the objectclasses "top" and "group".
  :objectclass => ["top", "group"],
  :sAMAccountName => group.name
}

@ldap.add(:dn => dn, :attributes => attr)

unless @ldap.get_operation_result.code == 0
  puts "SYNC ERROR: " + @ldap.get_operation_result.message
end

That’s just the code I’ve checked in right now. Obviously it needs a lot of work. The group types I defined in the RADUM module have to be strings here. The numbers should be Bignums, but I can only pull them as Fixnums, so I was a little worried. Note that if you view these using GUI tools in Windows, they are represented as Fixnum types (large numbers are negative). I used those values here, and it works! Again, I am happy about that. So now I can create a group from nothing like this:

RADUM::UNIXGroup.new("newgroup", cn, ad.load_next_gid,
                     RADUM::GROUP_UNIVERSAL_SECURITY, "vmware")
puts "Looking for next UID: " + ad.load_next_uid.to_s
puts "Looking for next GID: " + ad.load_next_gid.to_s
ad.sync

and it actually creates the group:

Looking for next UID: 1010
Looking for next GID: 1004
newgroup not found - creating...

I also added the load_next_uid() and load_next_gid() methods to find the next usable UID and GID values by searching the entire Active Directory. This could be useful when creating UNIXUser and UNIXGroup objects if you do not already know the next available UID or GID. If all of your users and groups are in all the containers you load into the AD object, you can simply rely on the uids and gids attribute of the AD object itself, but I am trying to cover all the bases. Those methods truly do find the next UID and GID, not just the highest one not used. Of course, by the code above, I am not handling all the possible attributes. I just wanted to see if it could create the group in the first place. Now I have something to work with.

I hope this works for user accounts too. If so, that means I don’t have to make this Windows specific for actually creating new entities. That’d be great! I am excited. I never thought I’d be excited about Active Directory :-)

Active Directory, Ruby

We’ll Just Call It a Flower, With Raindrops…

May 13th, 2009

It rained all day. I got home late and the lighting wasn’t good, but I had to pull out the D90 and shoot the same mysterious flower which I cannot name. I am sure there are thousands of people who aren’t reading this who can name it easily. I probably could figure it out, but I am busy. Said flower again, now with RAIN™:

Flower with raindrops in the front yard.

Flower with raindrops in the front yard.

and:

Flower with raindrops in the front yard.

Flower with raindrops in the front yard.

I thought it was neat. I need to get out more :-)

Photographs

New Nikon D90

May 11th, 2009

I blame this on Apple :-) I downloaded the Aperture 2 demo about a week ago. I have 1069 photographs from my D70. I reasonably processed them, but they could use some work. But I wasn’t going to buy Aperture 2 just for that… but it is such an awesome program! I have to say, I like Photoshop, but that’s about all I am interested in with respect to Adobe. I can do almost everything I want in Aperture 2, and I don’t know what it is about Apple software, but they know how to make a user interface. Actually, I do know what it is about Apple software, but I just don’t want to write an entire book about their glorious design principles. I was thinking about getting an HD camera. Then I thought, “You know you aren’t going to shoot any video.” I have a few expensive lenses and a D70, but the D70 needed a sensor cleaning (sensor cleaning is a personal hell of mine – I spent more than an hour at last night). I made up my mind. My illogical joke reason was that I needed a point and shoot camera (this is what the D70 is now – hey, you can put it in automatic to point and shoot). Other reasons include that I needed to do more photography to justify buying Aperture 2, and when I got to Microcenter with the sole purpose of getting a D90 kit and various accessories, someone came in and asked about it after I got there, there was one left, and I wasn’t about to let that guy get it. That guy knew I wanted it too because I said something about them only having one left in inventory, he looked at me a little oddly, but then went to look at a computer with the sales guy. I got another sales guy and just got what I came for. I got there first!

I shot some HD video with it. I was not impressed, but it is nice I can do something like that in a pinch. I didn’t buy it to shoot HD video though. I took a couple of pictures very quickly. I’ve not used the D70 for a really long time (a lot of that was because I avoided sensor cleaning), so these are not that good. I am not all that familiar with Aperture 2 yet, but I made some adjustments. Anyway, here is a flower. I never know what the names of various flowers are, so we’ll just call it a flower:

Flower in the front yard.

Flower in the front yard.

Here are some more flowers:

Flower in the front yard.

Flower in the front yard.

This is my wonderful GM product (Trailblazer, actually, I do like it):

My Trailblazer.

My Trailblazer.

Rick thinks his fence is defective:

Rick's broken fence.

Rick's broken fence.

What do you think? This is the yard they trick you into believing is a park of some sort:

Front yard.

Front yard.

I need to spend some time going through the Aperture 2 manual. It’s not long, but I want to make sure I think about what I am doing. I am going to adjust all my photographs again over time and import them into projects that make more sense, etc.

Photographs

Powered by Media Wiki

April 26th, 2009

I am now powered by Media Wiki. This page was previously titled “MoinMoin Powered”. I do like MoinMoin and Python, but I chose to convert to Media Wiki instead.

I plan on putting details about software projects, software development, and system administration in my new wiki, but I’ll be sure to write plenty here too. All of my current software projects are there now, but I have a lot of work to do with adding more content.

General

Ruby Active Directory User Management

April 23rd, 2009

I decided to rename my ActiveDirectory module to RADUM (Ruby Active Directory User Management) to avoid name conflicts with another ActiveDirectory Ruby module/gem I found previously. There might even be two, but I could be wrong about that. I did not look all that closely. This kind of makes sense anyway perhaps. I do plan on making this a gem too. My goal is not to make a general tool to interact with Active Directory. I need a tool that works with users and groups, but one that also makes a distinction between regular Windows users and groups and those same objects that have UNIX attributes. There are some important rules to follow with users who have UNIX attributes. For example, they need to have a main UNIX group, and that group needs to have a GID attribute. While it is not an error for the user to also be a member of that group from the UNIX perspective, it is redundant, so I make sure that doesn’t happen. I also put logic in that lets one set the user’s (Windows or Windows with UNIX attributes) primary Windows group. Note that the primary Windows group for a user can only be certain group types, and users don’t show up in the members list of that group either. Instead of expecting the user to deal with this through Active Directory manipulation, I deal with it. Right now, RADUM requires that you make an AD object, jam some Container objects in there, and then tell the AD object to load all the users and groups in those containers from the Active Directory. I’ll probably add a way to load specific users and groups, but I’ll do it in a way that automatically adds Containers and Groups/Users (of the right type) automatically so that all the objects have every reference they need. In the end, I have to work on syncing modifications to the Active Directory, and this will probably only work on Windows. Right now, RADUM doesn’t do much because I am simply making sure the logic is sound.

This fits my goals. I am sure the other ActiveDirectory modules are good for something a little more low level, and that’s great. My goal is not to compete with them because my objective is not quite the same. This is my first significant Ruby program, aside from some Ruby on Rails applications I made for myself. I am sure I’ll have a use for RADUM, and perhaps someone else will too. I put this in a Git repository. It is not ready for real use, but anyone is free to look at it, and really do whatever. I put it under a BSD license.

Active Directory, Ruby

Exthide: Show/Hide File Extensions in Mac OS X

April 19th, 2009

I worked some more in my Exthide program to make it a little better. You can now use shell glob patterns, etc. and it will process those files as handed to it by your shell. Of course, there was no real special magic to do this – except paying attention to all command line arguments. I also updated it to not touch directories. I am sure it could be better, but it is just a tiny project.

Note: I did find a way to do this using the command line:

SetFile -a E fileName.ext

From what I can tell, you have to have the developer tools installed for that to work. That command is much more powerful than mine, so my project is less useful it seems. Oh well :-)

Mostly I needed something to put into a Git repository on my server to see if everything worked with clone, pull, and push. I now have an "official" project… if you want to call it that :-) I put it under a BSD license, uploaded a binary distribution, and put it in a Git repository. An example of its use is included below:

[rowland@rowland: foo]$ ls
bar.txt baz.txt

[rowland@rowland: foo]$ exthide *.txt
Hiding extension for: bar.txt
Hiding extension for: baz.txt

[rowland@rowland: foo]$ extunhide *.txt
Unhiding extension for: bar.txt
Unhiding extension for: baz.txt

Yes, it is noisy. At least I made a man page (as if it needs one).

Command Line

Unpack RID from objectSid AD Attribute

April 5th, 2009

I have been working more on my ActiveDirectory Ruby module. I ran into a problem I recall from when I did my original Perl program we are using to add users and groups to the AD in our Instructional Enterprise Support project. In that project I just made sure all users have the Domain Users group as their primary Windows group when created. This works nicely there, but it does not help much when writing a module that loads user and group information out of AD directly. What if a user has a different primary Windows group? How do you build a concept of group membership if you ignore the user’s pirmaryGroupID AD attribute? You can’t simply assume what it is, and it would be lame to hard-code RID values (since they are fairly standard if you don’t do non-standard things) in order to compare those with the primaryGroupID attribute value. Note that Windows does not really pay much attention to the concept of a primary Windows group. This designation exists for the POSIX subsystem. If you look at a group’s members from the Windows perspective, the primary Windows group is automatically included though.

Microsoft has decided to put a user’s primary Windows group in AD as a RID value. However, there is no easy way to look at all the groups and figure out which one this RID corresponds to. Each group has an rid AD attribute too, but it is empty. I do not know if that is what the rid attribute is about anyway, but obviously it is not used. If you look at the objectSid attribute in AD, you will notice that it contains the RID as the last value in the string. Of course, this is stored as a binary value. Thanks for making it so easy Microsoft! Microsoft has details about what I am talking about with respect to the primaryGroupID and converting a SID to a string. Let the fun begin!

First, let us consider the following from the converting a SID to a string documentation:

' C Declaration for the SID memory, included for completeness...
'
'typedef struct _SID {
'  BYTE  Revision;
'  BYTE  SubAuthorityCount;
'   SID_IDENTIFIER_AUTHORITY IdentifierAuthority;
'  DWORD SubAuthority[ANYSIZE_ARRAY];
'} SID;
'typedef PVOID PSID;

So, we now know that we have two fields that are 8 bits, but what about that SID_IDENTIFIER_AUTHORITY structure? That is conveniently documented here:

typedef struct _SID_IDENTIFIER_AUTHORITY {
  BYTE Value[6];
} SID_IDENTIFIER_AUTHORITY, *PSID_IDENTIFIER_AUTHORITY;

This means there are 48 bits in addition to the first 16 bits for a total of 64 bits until we get to the DWORD array (which should be an array of unsigned long values). The last value in the DWORD array will be the RID I am looking for. The easiest way to get the RID value is to throw away the first 64 bits and then grab the last unsigned long value. This is how I am accomplishing that:

# Unpack a RID from the SID value in the LDAP objectSid attribute for a
# user or group in Active Directory.
def sid2rid_int(sid)
  sid.unpack("qV*").pop.to_i
end

Of course, if you really need the other values for some reason, that code will not work. I only need the RID of a group. I need to figure out a group’s RID value so that I can find it using a user’s primaryGroupID AD attribute value. When I create group objects, I pull their objectSid AD attribute value and convert it to a RID. This is then added as an attribute to a Group object (and thus UNIXGroup) in my module. Just for the heck of it, I also did this for users. RIDs are in a flat namespace, so I check in the initialize() method before I actually create the objects to make sure the RID is not already used. This is just an extra sanity check for no real reason since I am not loading the entire directory, but I do things like that anyway. I also do the same thing for UIDs and GIDs for UNIXUser and UNIXGroup objects. My module has special concepts for users and groups that have UNIX attributes. Maybe I will write more about that later, but basically I want UNIXUser objects to be members of their UNIXGroup objects (and Group objects) from the Windows perspective, but it’s a little different when it comes to a group’s msSFU30PosixGroup and memberUid AD attributes. Those attributes describe group membership from a UNIX perspective. In UNIX a user has their main group specified in the /etc/passwd file (they don’t show up in /etc/group as a member of the group) and their supplementary groups defined in the /etc/group file. I have to make sure that the main group is specified by a user’s gidNumber AD attribute and their supplementary group memberships defined in the two previously mentioned attributes for a group. Hmm… that was a bit much for “basically”. Let’s get back to the RID again. I need this because I need to know all the groups an user is in from the Windows perspective. One of these days I’ll need to sync these objects back to the AD after making changes or making new objects. Why couldn’t Microsoft simply put an AD object’s RID somewhere?

I had to write some new tests of course, and I otherwise changed the code around already since the last post. My user and group objects have arrays that contain corresponding members of the other object type to make things easier. They add and remove each other automatically. Containers also handle pushing RID, UID, and GID values (whenever that makes sense) into arrays in the AD object, and everything makes sure that objects are not created if rules are violated. I forgot that, in Windows at least, groups can contain groups. More fun :-) That wasn’t hard to add of course.

This is working as far as I am aware at this point. I will see if this is not the case in further testing most likely.

Active Directory, Ruby

Ruby and Active Directory

March 31st, 2009

A large portion of last quarter at work was spent implementing a model enterprise environment in VMware for a course that teaches enterprise development using web technologies. That is one of the reasons I’ve not written much here. I was also working on a Cocoa application that uses the Broccoli library to talk to a Bro network intrusion detection system. The application itself is not very useful right now, but I did learn a lot, including:

  • Cocoa programming.
  • Adding the Growl Framework.
  • Multithreading Growl notifications (it is not thread-safe).
  • Modifying the location of shared library dependencies when including a shared library as a project resouce.
  • And much more…

Of course, this is not about that right now. While working on the Instructional Enterprise Support project I learned a couple of different ways to implement central authentication. There are numerous blog posts out there about doing this. Many of them seem to have slightly different information. I also ran into some easily confusing issues when trying to test password changes when using Kerberos that were clearly my fault with respect to Group Policy. This is the first time that I’ve messed with Active Directory in any significant way. I wrote a Perl program that uses Net::LDAP to add accounts along with using commands like dsadd, etc.

Right now I am working on a Ruby module that will deal with Active Directory using the ruby-net-ldap gem. My goal is to create an application that can load all the user and group objects in specific containers, allow me to modify certain attributes, and then synchronize that with Active Directory. I did find a ruby-activedirectory gem, but I need something that has a special concept of a “UNIXGroup” class that behaves like I want it to. I could extend that module (or work on it perhaps), but I am just going to do my own thing as a learning experience. Maybe I will make it into my own gem, who knows? I am not sure how I am going to add these users and groups into Active Directory. I’ll probably have something that’s Windows specific. As long as it does what I want, that’s good enough. Surely, it will be better than my Perl hack. I am a much bigger fan or Ruby these days.

I plan on writing some posts on what we decided to do with centralized authentication in our environment later, surely before we actually do it, but based on what I did for the Instructional Enterprise Support project. Over the weekend I wrote a 75 page CSE 762 User Guide for that project, so I’ve been busy. This is an example of a small test.rb program I wrote using my ActiveDirectory module:

#!/usr/bin/ruby

require 'active-directory'

ad = ActiveDirectory::AD.new('dc=vmware,dc=local', 'password',
                             'cn=Administrator,cn=Users', '192.168.10.4')
c1 = ActiveDirectory::Container.new("cn=Users", ad)
c2 = ActiveDirectory::Container.new("ou=People", ad)
puts ad
ad.add_container c1
ad.add_container c2
ad.load
puts
puts "Groups"
puts "------"

ad.groups.each do |group|
  puts group
end

puts
puts "Users"
puts "-----"

ad.users.each do |user|
  puts user
end

puts
puts
puts "Container View"
puts "=============="

ad.containers.each do |container|
  puts
  puts container
  puts
  puts "Groups"
  puts "------"

  container.groups.each do |group|
    puts group
  end

  puts
  puts "Users"
  puts "-----"

  container.users.each do |user|
    puts user
  end
end

Of course, I left out my test password. Here is the corresponding output:

AD [dc=vmware,dc=local 192.168.10.4 389]

Groups
------
Group [cn=DnsAdmins,cn=Users,dc=vmware,dc=local]
Group [cn=DnsUpdateProxy,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Computers,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Controllers,cn=Users,dc=vmware,dc=local]
Group [cn=Schema Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Enterprise Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Cert Publishers,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Users,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Guests,cn=Users,dc=vmware,dc=local]
Group [cn=Group Policy Creator Owners,cn=Users,dc=vmware,dc=local]
Group [cn=RAS and IAS Servers,cn=Users,dc=vmware,dc=local]
Group [cn=Allowed RODC Password Replication Group,cn=Users,dc=vmware,dc=local]
Group [cn=Denied RODC Password Replication Group,cn=Users,dc=vmware,dc=local]
Group [cn=Read-only Domain Controllers,cn=Users,dc=vmware,dc=local]
Group [cn=Enterprise Read-only Domain Controllers,cn=Users,dc=vmware,dc=local]
UNIXGroup [(GID 1000) cn=group1,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1001) cn=staff,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1002) cn=group2,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1003) cn=enable,ou=People,dc=vmware,dc=local]

Users
-----
User [testuser cn=Test M. User,cn=Users,dc=vmware,dc=local]
User [Administrator cn=Administrator,cn=Users,dc=vmware,dc=local]
User [Guest cn=Guest,cn=Users,dc=vmware,dc=local]
User [krbtgt cn=krbtgt,cn=Users,dc=vmware,dc=local]
UNIXUser [(UID 1000, GID 1000) student1 cn=student1,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1001, GID 1001) gradb cn=gradb,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1002, GID 1002) student6 cn=student6,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1003, GID 1001) grada cn=grada,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1004, GID 1002) student2 cn=student2,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1005, GID 1000) student5 cn=student5,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1006, GID 1001) igorm cn=igorm,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1007, GID 1002) student4 cn=student4,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1008, GID 1000) student3 cn=student3,ou=People,dc=vmware,dc=local]
User [mssql cn=mssql,ou=People,dc=vmware,dc=local]

Container View
==============

Container [cn=Users,dc=vmware,dc=local]

Groups
------
Group [cn=DnsAdmins,cn=Users,dc=vmware,dc=local]
Group [cn=DnsUpdateProxy,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Computers,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Controllers,cn=Users,dc=vmware,dc=local]
Group [cn=Schema Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Enterprise Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Cert Publishers,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Admins,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Users,cn=Users,dc=vmware,dc=local]
Group [cn=Domain Guests,cn=Users,dc=vmware,dc=local]
Group [cn=Group Policy Creator Owners,cn=Users,dc=vmware,dc=local]
Group [cn=RAS and IAS Servers,cn=Users,dc=vmware,dc=local]
Group [cn=Allowed RODC Password Replication Group,cn=Users,dc=vmware,dc=local]
Group [cn=Denied RODC Password Replication Group,cn=Users,dc=vmware,dc=local]
Group [cn=Read-only Domain Controllers,cn=Users,dc=vmware,dc=local]
Group [cn=Enterprise Read-only Domain Controllers,cn=Users,dc=vmware,dc=local]

Users
-----
User [testuser cn=Test M. User,cn=Users,dc=vmware,dc=local]
User [Administrator cn=Administrator,cn=Users,dc=vmware,dc=local]
User [Guest cn=Guest,cn=Users,dc=vmware,dc=local]
User [krbtgt cn=krbtgt,cn=Users,dc=vmware,dc=local]

Container [ou=People,dc=vmware,dc=local]

Groups
------
UNIXGroup [(GID 1000) cn=group1,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1001) cn=staff,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1002) cn=group2,ou=People,dc=vmware,dc=local]
UNIXGroup [(GID 1003) cn=enable,ou=People,dc=vmware,dc=local]

Users
-----
UNIXUser [(UID 1000, GID 1000) student1 cn=student1,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1001, GID 1001) gradb cn=gradb,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1002, GID 1002) student6 cn=student6,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1003, GID 1001) grada cn=grada,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1004, GID 1002) student2 cn=student2,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1005, GID 1000) student5 cn=student5,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1006, GID 1001) igorm cn=igorm,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1007, GID 1002) student4 cn=student4,ou=People,dc=vmware,dc=local]
UNIXUser [(UID 1008, GID 1000) student3 cn=student3,ou=People,dc=vmware,dc=local]
User [mssql cn=mssql,ou=People,dc=vmware,dc=local]

I wrote a lot of tests to make sure built-in logic worked, which was really difficult. If I get anywhere with this, I’ll write more about it too.

Active Directory, Ruby

Git Revision Control for Configuration Files

December 30th, 2008

I’ve decided to start using Git for revision control when editing configuration files on UNIX. Git is especially good for this since the repository is in the working directory. This avoids situations like the following:

[rowland@www conf]$ ls httpd.conf*
httpd.conf            httpd.conf-new        httpd.conf.both
httpd.conf-3-24-2004  httpd.conf-rowland    httpd.conf.default
httpd.conf-3-27-2002  httpd.conf.021607     httpd.conf.ssl
httpd.conf-9-18-2001  httpd.conf.bak

As you can see, my name is on the end of one of those files, so I can’t complain too much. On our new Linux systems I’ve decided to do the right thing however.

My first problem was that I did not want to track all files in /etc, but I wanted to track a number of them – the ones I’d be editing of course. To solve this, I created an /etc/git directory and hard linked files from /etc into /etc/git for tracking. I finally found a use for hard links! Sure, I’ve used them once or twice before, but this is a real use. I feel more complete now.

I did some quick searching for a post-commit script similar to what I’ve used with Subversion to email commits, but I did not find much. I was in a hurry though, so I came up with something quick:

#!/bin/bash

REPOSITORY="Description [/path/to/repo/dir]"
RECIPIENT="revision-control@example.com"

/usr/sbin/sendmail -t <<EOF
$(git log --patch-with-stat --no-color --max-count=1 --pretty=format:"From: %ce%nSubject: [$HOSTNAME] %s%nDate: %cD%nTo: $RECIPIENT%n%nHostname: $HOSTNAME%nRepository: $REPOSITORY%nAuthor: %an <%ae>%nCommitter: %cn <%ce>%nParent: %P%nCommit: %H%n%n%b%n%n")
EOF

That seems to meet our needs for now at least. Git is great for tracking changes to configuration files, and with this post-commit script, I now get somewhat useful email when changes happen.

UNIX System Administration