Using the Ruby LDAP extension library
So I was recently working on a ruby/rails project which had to interact with OpenLDAP. This was easy enough using this Ruby/LDAP extension library found on Sourceforge (LNUX). As a quick aside, there is also the whole ActiveLDAP RubyForge project which treats LDAP as another backend just like a SQL database with different adapters, etc etc. For now, however, I will discuss the extension library and will perhaps go into ActiveLDAP some other time.First and foremost, a good reference and place to start is the rdoc documentation for the extension library. With this in hand, it is easy to get started interacting with an LDAP server. In my examples, I’m assuming use of OpenLDAP. I’m also running on a Debian/sid system with the following package installed: libldap-ruby1.8. I have a fresh DIT with a base of dc=foo,dc=com and TLS configured like so in slapd.conf:
TLSCACertificateFile /etc/ldap/ssl/ca.crt
TLSCertificateFile /etc/ldap/ssl/localhost.foo.com.crt
TLSCertificateKeyFile /etc/ldap/ssl/localhost.foo.com.key
TLSVerifyClient never
Note: A very common gotcha with openldap/TLS configuration is that the certificate does not match exactly with the hostname. The cn of the server certificate MUST be the fqdn of the host and that fqdn MUST be used for the connection. This can also be the case for the client, but TLSVerifyClient is set to “never” by default as seen above. What does this mean? This means that if your OpenLDAP server is on baz.foo.com, the cn of the certificate must be “baz.foo.com” and you must use “baz.foo.com” to connect as the client. Anything else and you’ll probably see an error like “Error in the certificate.” Note that your ca certificate must also match accordingly.
My LDAP begins with the following:
rwoodrum@frums:~/tmp$ ldapsearch -x -W -D cn=admin,dc=foo,dc=com -b dc=foo,dc=com
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=foo,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#</dc=foo,dc=com>
#
# foo.com
dn: dc=foo,dc=com
objectClass: top
objectClass: dcObject
objectClass: organization
o: foo
dc: foo
#
# group, foo.com
dn: ou=group,dc=foo,dc=com
ou: group
objectClass: top
objectClass: organizationalUnit
#
# bar_group, group, foo.com
dn: cn=bar_group,ou=group,dc=foo,dc=com
cn: bar_group
description: the bar group
gidNumber: 3000
objectClass: posixGroup
objectClass: top
memberUid: rwoodrum
And now, we’ll use a trivial module to modify this group, changing the memberUid attribute. Here’s the module code that will pull in from elsewhere:
require 'ldap'module FooLDAP
def self.setup_ldap_connection
conn = LDAP::Conn.new(host='localhost.foo.com')
conn.set_option(LDAP::LDAP_OPT_PROTOCOL_VERSION, 3)
begin
conn.start_tls.nil?
rescue
conn.perror("debug")
raise "error during start_tls"
end
conn.simple_bind(dn = 'cn=admin,dc=foo,dc=com', password = 'secret')
begin
!conn.bound?
rescue
conn.perror("debug")
raise "error during simple_bind"
end
return conn
end def self.update_bar_group
conn = setup_ldap_connection
conn.modify('cn=bar_group,ou=group,dc=foo,dc=com', {"memberUid" => ["ryan_woodrum"]})
conn.unbind
return
end
end
And now from outside, use the module:
require 'foo_ldap' FooLDAP.update_bar_group()
After running, you can see the the memberUid attribute has changed inside of ldap:
rwoodrum@frums:~/tmp$ ldapsearch -x -W -D cn=admin,dc=foo,dc=com -b dc=foo,dc=com cn=bar_group
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <dc=foo,dc=com> with scope subtree
# filter: cn=bar_group
# requesting: ALL
#
#
# bar_group, group, foo.com
dn: cn=bar_group,ou=group,dc=foo,dc=com
cn: bar_group
description: the bar group
gidNumber: 3000
objectClass: posixGroup
objectClass: top
memberUid: ryan_woodrum
... snip ...
</dc=foo,dc=com>
Quid est demonstratum.