Projects/OpenLDAP DIT

From Mandriva Community Wiki

Jump to: navigation, search


A proposal for a default DIT

This page is about the openldap-mandriva-dit package, used by default in the upcoming Corporate Server 4 product and also available for cooker users. This text is part of the package's documentation (several README files).

This documentation is correct for openldap-mandriva-dit-0.16-1*.

Contents


Introduction

This document aims to explain the Directory Information Tree (DIT) used in the openldap-mandriva-dit package. The package could be used in other distributions with small adjustments. Currently it relies on:

  • openldap-2.3 features
  • certain schema files placed in /usr/share/openldap/schemas/
  • overlay modules built as .so objects and available in /usr/lib/openldap/

So, to make it work in other distributions, basically one would at most need to adapt these paths, include schema files or remove configuration lines that load modules if these are builtin. The configuration file to change would be the standard /etc/openldap/slapd.conf or, in the case of the template, /usr/share/openldap/mandriva-dit/mandriva-dit-slapd-template.conf.

The motivation for this new layout is the need for a better separation of privileges regarding access to the information stored in the directory. The super user account of the directory should be used rarely and delegation of privileges should be easier.

We think this proposed layout accomplishes that by providing several groups which have distinctive access rules we provide a clear separation of privileges. In order to give a user a new privilege, all that is needed is to add him/her to one of these specific groups.

These are the characteristics of the proposed DIT:

  • several groups for common services
  • most access control rules based on group membership
  • several system accounts ready to use (just add a password) by many services such as:
    • sudo
    • dns
    • accounts
    • etc
  • simple installation script which prepares the tree asking very few questions (just two, and one of them is just a password)
  • easy support for OpenLDAP's password policy overlay

These accounts get their privileges by being associated to specific groups.

Administrators should note that we will probably find out that there are too few groups, or too many. Or that some ACLs are too restrictive, or too broad. It is difficult to come up with a one-size-fits-all DIT, but we can start here.

By the way, there is no password set for the rootdn account as it (the account) is not used.

If you just want to know how to use this DIT, skip to the end of the document to the section called "Enough with the theory: how to use this?".

The tree

                             dc=example,dc=com

  ou=Hosts                          ou=System Groups            ou=System Accounts
  ou=Idmap                          cn=LDAP Admins              uid=Ldap Admin
  ou=Address Book                   cn=Sudo Admins              uid=Sudo Admin
  ou=dhcp                           cn=DNS Admins               uid=DNS Admin
  ou=dns                            cn=DNS Readers              uid=DNS Reader
  ou=People                         cn=DHCP Admins              uid=DHCP Admin
  ou=Group                          cn=Address Book Admins      uid=Address Book Admin
  ou=Password Policies              cn=LDAP Replicators         uid=LDAP Replicator
  ou=Sudoers                        cn=Account Admins           uid=Account Admin
                                    cn=MTA Admins               uid=MTA Admin
                                    cn=LDAP Monitors            uid=LDAP Monitor
                                    cn=Idmap Admins             uid=Idmap Admin
                                                                uid=smbldap-tools
                                                                uid=nssldap

The services

We created some entries for a few services that can use LDAP to store their information. More will probably be added in the future. For now, we have branches for:

  • dns (ou=dns)
  • sudo (ou=sudoers)
  • dhcp (ou=dhcp)

The respective administrative groups have read/write access to these branches for specific entries.

The groups

Groups are the core of this proposed DIT layout, because most ACLs are constructed via group membership to allow for greater flexibility and delegation.

The current default groups that are born with the new DIT layout are as follows:

  • LDAP Admins
  • Sudo Admins
  • DNS Admins
  • DNS Readers
  • DHCP Admins
  • DHCP Readers
  • Address Book Admins
  • LDAP Replicators
  • Account Admins
  • MTA Admins
  • LDAP Monitors
  • Idmap Admins

Each entry has a description attribute filled in with a brief text describing the purpose of the members of each group. For example:

dn: cn=Sudo Admins,ou=System Groups,dc=example,dc=com
description: Members can administer ou=sudoers entries and attributes

In order to use groups in ACLs, the objectClass used for these entries has to use attributes where membership is indicated distinguished names and not just names. In other words, the membership attribute has to use a full DN to indicate its member. The standard object class used for this by OpenLDAP is groupOfNames, and this is what we used. For example:

dn: cn=Sudo Admins,ou=System Groups,dc=example,dc=com
member: uid=Sudo Admin,ou=System Accounts,dc=example,dc=com

A side effect of using groupOfNames is that we have to have at least one member in each group. So we needed to create standard accounts, which proved to be useful anyway. The previous example showed the standard account for adminstering sudo entries and attributes.

The accounts

As was the case with the groups, many standard system accounts were created. Each group has at least a corresponding system account as its membership. The current list is as follows:

  • Account Admin
  • smbldap-tools
  • nssldap
  • MTA Admin
  • DHCP Admin
  • DHCP Reader
  • DNS Admin
  • DNS Reader
  • Sudo Admin
  • Address Book Admin
  • LDAP Admin
  • LDAP Replicator
  • LDAP Monitor
  • Idmap Admin

The privileges

The idea is to give each group the privileges needed to complete it's administration tasks. This usually means having access to the respective ou=foo branch of the directory. For example, the Sudo Admins group has rights over the ou=sudoers branch of the directory.

Whenever possible, however, these rights are limited to that specific service, i.e. it's not any kind of entry that can be created but just those relevant to the service. For example, the Sudo Admins members can only create entries one level below ou=sudoers, and only with the attributes allowed by the sudoRole object class.

Other cases, however, can be more complicated. We will list them here and the reasoning behind the chosen ACLs.

Monitoring access

The LDAP Monitors group is the only group besides LDAP Admins which can read entries under cn=monitor. This base dn contains statistics about the server, such as operations performed, backends and overlays being used, etc. So, if you need a user to have read access to this kind of information, just put him/her in this group.

Samba, Unix and Kerberos admins

Samba needs to have corresponding unix accounts for it's users and machine accounts. It will not by itself create those, however. For example, when running "smbpasswd -a foo", the "foo" user account will only be created if samba can find the corresponding unix attributes. The same for group mappings and machine accounts.

Earlier versions of openldap-mandriva-dit had two separate privilege groups: one for Unix accounts and another for Samba accounts. This complicated ACLs, and it was worse when we later added Kerberos Admins to the mix because they also had to touch some of the account-related attributes.

So, since version 0.11, we merged these groups into one called Account Admins (and the respective Account Admin account). This made the ACLs simpler and faster, at the expense of some granularity in privileges.

The smbldap-tools account, uid=smbldap-tools,ou=System Accounts, still exists but is now a member of the Account Admins group.

MTA

As of this moment, there is no clear scenario for usage of this account. For now, it can administer just a few attributes: all the ones from the inetLocalMailRecipient object class plus the single mail attribute.

As more usage scenarios appear, these ACLs should be incremented.

LDAP Admins

Members of this group can write to and read from all entries and attributes of the directory and have no size or time limits.

Note that, contrary to rootdn, LDAP Admins are subject to password policies if these are being used.

LDAP Replicators

The members of the LDAP Replicators group have read access to all attributes and entries of the directory so that they can be used in a syncrepl replication setup. The bind dn used for the replication should be a member of this group. For example:

syncrepl        rid=100
                provider=ldap://dirserv.example.com
                type=refreshAndPersist
                retry="60 +"
                searchbase="dc=example,dc=com"
                starttls=critical
                bindmethod=simple
                binddn="uid=LDAP Replicator,ou=System Accounts,dc=example,dc=com"
                credentials="secret"

Here, uid=LDAP Replicator,ou=System Accounts,dc=example,dc=com is a member of the LDAP Replicators group and is automatically granted read rights to all entries of the directory (assuming the provider was also installed with this base DIT and ACLs).

Generic directory read accounts

A few accounts were created for specific read access. Some administrators prefer to block anonymous read access to the directory, in which case these accounts would then be used. For the moment we have:

  • nssldap: nss_ldap can bind to the directory either anonymously or with a specific account. The uid=nssldap,ou=System Accounts was created for this purpose. Currently no ACLs make use of this account. Were the administrator to use it, he/she would also have to block anonymous read access to many attributes.
  • DNS Readers: members of this group can read the ou=dns branch. Anonymous access is denied.
  • DHCP Readers: members of this group can read the ou=dhcp branch, but note that anonymous access is still allowed. This group exists just for the convenience for the administrators who want to, later on, restrict anonymous access to this branch.

Currently anonymous read access is granted to many attributes. As of this moment, if the administrator wants to restrict anonymous access and use these accounts, the ACLs would have to be changed manually. This topic should be further improved in the package.

The installation script

The openldap-mandriva-dit package contains a shell script which can be used to install the accounts and ACLs described in this document. The script is installed at /usr/share/openldap/scripts/mandriva-dit-setup.sh and performs the following:

  • asks the DNS domain (suggesting whatever was auto-detected)
  • constructs the top-level directory entry from this domain using dc style attributes
  • creates and imports an ldif file with the accounts and groups described here
  • installs new slapd.conf and mandriva-dit-access.conf files (making backups of the previous ones) with the default ACLs and other useful configurations (like cache)
  • loads the ldif file, backing up the previous database directory

Even though the script performs many tests and backups many files before overwriting them, administrators are advised to backup all data before running this script.

Enough with the theory: how to use this?

The sample installation script will overwrite some OpenLDAP files and directories. Specifically, it will backup and overwrite the following:

  • /etc/openldap/slapd.conf
  • /etc/openldap/ldap.conf
  • /etc/openldap/mandriva-dit-access.conf (THIS ONE HAS NO BACKUP CURRENTLY)
  • /var/lib/ldap contents

So, after you are satisfied that nothing important will be lost, run the script. It has a few command line options, use --help for a list. Below is a sample run using the example.com domain:

[root@cs4 ~]# /usr/share/openldap/scripts/mandriva-dit-setup.sh
Please enter your DNS domain name [conectiva]:
example.com


Administrator account

The administrator account for this directory is
uid=LDAP Admin,ou=System Accounts,dc=example,dc=com

Please choose a password for this account:
New password: secretpass
Re-enter new password: secretpass


Summary
__=__

Domain:      example.com
LDAP suffix: dc=example,dc=com

Confirm? (Y/n)
Y
config file testing succeeded
Stopping ldap service
Finished, starting ldap service
Running /usr/bin/db_recover on /var/lib/ldap
removing /var/lib/ldap/alock
Starting slapd (ldap + ldaps):                                  [  OK  ]

Your previous database directory has been backed up as /var/lib/ldap.1145397294

Now, fire up an LDAP browser and use the LDAP Admin account shown above to set up some passwords for the other less privileged accounts that you are going to use. Note that the rootdn account is not used.

Examples

Here are some configuration examples for some of the services that can be hosted in this DIT. Don't forget that the only account that is enabled after running the script is the LDAP Admin one: all others are disabled and you need to use the LDAP Admin account to give a password and thus enable the other administration accounts you are going to use.

For example, to enable (i.e., give a password to) the DNS Admin account, one could use:

$ ldappasswd -x -D "uid=LDAP Admin,ou=System Accounts,dc=example,dc=com" \  
  -W -S "uid=DNS Admin,ou=System Accounts,dc=example,dc=com"
New password: [choose password for DNS Admin]
Re-enter new password: [retype it]
Enter LDAP Password: [here is the password for the bind (-D) user: LDAP Admin]
Result: Success (0)

DNS

Necessary steps:

  • import zone into LDAP at ou=dns branch, which is where our ACLs expect the DNS information to be stored
  • configure named.conf to use LDAP for each zone that was imported
  • configure LDAP authentication parameters in named.conf (ou=dns can only be read by DNS Admins and DNS Readers' members)

Example

We will import the zone below into LDAP at ou=dns:

$TTL 86400
$ORIGIN example.com.
@               IN      SOA     aurelio.example.com. hostmaster.example.com. (
                                1               ; serial number
                                10800           ; refresh
                                3600            ; retry
                                604800          ; expires
                                86400 )         ; TTL
@               IN      NS      aurelio.example.com.
@               IN      MX      10      mail.example.com.

gateway         IN      A       10.0.1.1
dogs            IN      A       10.0.1.7
mail            IN      A       10.0.1.8
aurelio         IN      A       10.0.1.9

dhcp010         IN      A       10.0.1.10
dhcp011         IN      A       10.0.1.11

ns1             IN      CNAME   aurelio
kdc             IN      CNAME   dogs

localhost       IN      A       127.0.0.1

;_kerberos._udp IN      SRV     0 0 88 dogs

The SRV record has to be commented because zonetoldap can't handle that yet. We will add it manually later.

The corresponding reverse zone is this:

$TTL 86400
$ORIGIN 1.0.10.in-addr.arpa.
@               IN      SOA     aurelio.example.com. hostmaster.example.com. (
                                        1       ; serial
                                        10800   ; refresh
                                        3600    ; retry
                                        604800  ; expires
                                        86400 ) ; TTL

@               IN      NS      aurelio.example.com.

1               IN      PTR     gateway.example.com.
7               IN      PTR     dogs.example.com.
8               IN      PTR     mail.example.com.
9               IN      PTR     aurelio.example.com.
10              IN      PTR     dhcp010.example.com.
11              IN      PTR     dhcp011.example.com.

After preparing our tree with the mandriva-setup.sh script, we can insert these zone files in LDAP at ou=dns (using the DNS Admin user credentials):

$ zonetoldap -D 'uid=DNS Admin,ou=System Accounts,dc=example,dc=com' -W \ 
  -b ou=dns,dc=example,dc=com -z example.com -f example.com.zone -h localhost -c
Enter LDAP Password: secretpass
$ zonetoldap -D 'uid=DNS Admin,ou=System Accounts,dc=example,dc=com' -W \ 
  -b ou=dns,dc=example,dc=com -z 1.0.10.in-addr.arpa \ 
  -f 1.0.10.in-addr.arpa.zone -h localhost -c
Enter LDAP Password: secretpass
$

This will produce the following entries under ou=dns,dc=example,dc=com:

dc=com
  dc=example
    relativeDomainName=ns1+zoneName=example.com
    relativeDomainName=mail+zoneName=example.com
    relativeDomainName=localhost+zoneName=example.com
    relativeDomainName=kdc+zoneName=example.com
    relativeDomainName=gateway+zoneName=example.com
    relativeDomainName=dogs+zoneName=example.com
    relativeDomainName=dhcp011+zoneName=example.com
    relativeDomainName=dhcp010+zoneName=example.com
    relativeDomainName=aurelio+zoneName=example.com
    relativeDomainName=@+zoneName=example.com
dc=arpa
  dc=in-addr
    dc=10
      dc=0
        dc=1
          relativeDomainName=9+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=8+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=7+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=11+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=10+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=1+zoneName=1.0.10.in-addr.arpa
          relativeDomainName=@+zoneName=1.0.10.in-addr.arpa

We sill have to add the SRV record from the zone file which we commented. The following LDIF can be used:

dn: relativeDomainName=_kerberos._udp+zoneName=example.com,dc=example,dc=com,ou=dns,dc=example,dc=com
objectClass: dNSZone
relativeDomainName: _kerberos._udp
zoneName: example.com
SRVRecord: 0 0 88 dogs

Let's add it:

$ ldapadd -x -D 'uid=DNS Admin,ou=System Accounts,dc=example,dc=com' \ 
  -W -f srv.ldif
Enter LDAP Password: secretpass
adding new entry "relativeDomainName=_kerberos._udp+zoneName=example.com,dc=example,dc=com,
ou=dns,dc=example,dc=com"

You should always review the entries produced by the zonetoldap tool, there may be other inconsistensies with other records.

Now we have to configure named.conf to consult LDAP for these two zones. Below is an example file.

Please remember that named runs in a chroot and won't use the system /etc/hosts file, but its own inside the chroot. Also avoid making loops: for example, don't use a server name that's in the ldap zone to specify the ldap server. In general, it's better to use IP addresses instead of hostnames in named.conf.

options {
        directory "/var/named";
        allow-transfer { none; };
        notify no;
        allow-query { any; };
};

zone "." {
        type hint;
        file "named.ca";
};

zone "0.0.127.in-addr.arpa" {
        type master;
        file "named.local";
};

zone "example.com" {
        type master;
        database "ldap ldap://127.0.0.1/ou=dns,dc=example,dc=com??sub??!bindname=uid=DNS%20Reader%2c
ou=System%20Accounts%2cdc=example%2cdc=com,!x-bindpw=dnsreader 86400";
};

zone "1.0.10.in-addr.arpa" {
        type master;
        database "ldap ldap://127.0.0.1/ou=dns,dc=example,dc=com??sub??!bindname=uid=DNS%20Reader%2c
ou=System%20Accounts%2cdc=example%2cdc=com,!x-bindpw=dnsreader 86400";
};

key "rndc-key" {
        algorithm hmac-md5;
        secret "U8C6P+RjbAM2udmutlz0Vw==";
};

controls {
        inet 127.0.0.1 port 953
                allow { 127.0.0.1; } keys { "rndc-key"; };
};

The database parameter specifies the database to use for the zone file. In our case, it's LDAP. The URL seems a bit complicated, so let's explain it. The generic format of the URL follows RFC 2255 and is like this:

ldap://server/basedn?attributes?scope?filter?extensions

So, if we want to specify a subtree search on ou=dns on localhost with no default filter, attributes or extensions, it would be like this:

ldap://localhost?ou=dns,dc=example,dc=com??sub?

Our DIT, however, requires authenticated searches on ou=dns, so we have to add extensions. Extensions are a comma-separated list of names optionally preceeded by "!" indicating it's usage is critical. We will use bindname and x-bindpw (the latter one, being not standard, is prefixed by "x-"). The URL now looks like this:

ldap://localhost?ou=dns,dc=example,dc=com??sub??!bindname=uid=DNS Reader,
ou=System Accounts,dc=example,dc=com,!x-bindpw=dnsreader

Since extensions are comma-separated, we have to espace the commas in the binddn. We also have to escape the spaces. We do this using standard URL encoding formats. In our case, the URL then finally becomes:

ldap://localhost/ou=dns,dc=example,dc=com??sub??!bindname=uid=DNS%20Reader%2c
ou=System%20Accounts%2cdc=example%2cdc=com,!x-bindpw=dnsreader

Beware that /etc/named.conf has to be mode 0640 owner root:named because it contains two secrets now: the rndc key and the LDAP credentials used to bind to the directory (just because of the rndc key it should already have these permissions, but make sure).

This is all there is to it. Now start the daemon and do some tests with dig:

$ dig +short @127.0.0.1 -t mx example.com
10 mail.example.com.
$ dig +short @127.0.0.1 mail.example.com
10.0.1.8
$ dig +short @127.0.0.1 -x 10.0.1.8
mail.example.com.
$ dig +short @127.0.0.1 -t srv _kerberos._udp.example.com
0 0 88 dogs.example.com.

Done!

Delegating

It's very easy to delegate DNS administrative privileges to someone else in your directory. Just put his/her dn in the "DNS Admins" group and he/she will be able to change anything under ou=dns. Here is an example command to add the uid=anderson,ou=People,dc=example,dc=com user to this group:

$ ldapadd -x -D 'uid=DNS Admin,ou=System Accounts,dc=example,dc=com' -W
Enter LDAP Password: secretpassword
dn: cn=DNS Admins,ou=System Groups,dc=example,dc=com
changetype: modify
add: member
member: uid=anderson,ou=People,dc=example,dc=com

modifying entry "cn=DNS Admins,ou=System Groups,dc=example,dc=com"

^D

Now this user can add new records, delete some, change others and even create new zones, although for new zones to be loaded the named.conf file on the DNS server has to be changed.

Samba

To use this DIT with Samba, the following configuration details have to be observed.

Layout in LDAP

The following layout is the one that has to be configured in /etc/samba/smb.conf and /etc/smbldap-tools/smbldap.conf:

  • machine accounts: under ou=Hosts
  • user accounts: under ou=People
  • group accounts: under ou=Group
  • idmap branch: under ou=Idmap

ldap admin dn

When it comes to the "ldap admin dn" /etc/samba/smb.conf configuration parameter, use a member of the Account Admins group. For example:

ldap admin dn = uid=Account Admin,ou=System Accounts,dc=example,dc=com

smbldap-tools

In /etc/smbldap-tools/smbldap_bind.conf, use the smbldap-tools user instead of the directory's rootdn:

masterDN="uid=smbldap-tools,ou=System Accounts,dc=example,dc=com"

This user is a member of the Account Admins group. If you want to use another account, then make sure it's a member of this same group or else the default OpenLDAP ACLs won't work.

smbldap-populate

The default smbldap-populate behaviour, at least with version 0.9.2, is to create an administrator account with the following attributes:

  • uidNumber = 0
  • gidNumber = 0
  • name: root
  • member of Domain Admins

This means that a root user is created in LDAP. We advise against that and suggest to use this command line with smbldap-populate:

# smbldap-populate -a Administrator -k 1000 -m 512

This will create an user with the name Administrator, uidNumber 1000 and gidNumber 512. You can also use uidNumber 500 if you want to match windows' RID for this kind of user, but you may already have a local user with this number.

Later on the Domain Admins group could be given privileges (see "net rights grant" command), or your shares could use the admin users parameter.

IDMAP

If using IDMAP's LDAP backend in a member server, set the ldap admin dn configuration parameter in /etc/samba/smb.conf to the dn of a member of the Idmap Admins group. For example:

ldap admin dn = uid=Idmap Admin,ou=System Accounts,dc=example,dc=com

In members servers, there is no need to use the full blown Account Admin user: the Idmap Admins group is the right one as it can only write to the ou=Idmap container.

WARNING: there is a potential security vulnerability with using Idmap in LDAP. Because all domain machines need to have write access to this branch of the directory (and thus need a clear text password stored somewhere), a malicious user with root privileges on such a machine could obtain this password and create any identity mapping in ou=Idmap. See this thread for more information: http://lists.samba.org/archive/samba/2006-March/119196.html

DHCP

This DIT has support for DHCP information stored under ou=dhcp. Necessary steps:

  • import /etc/dhcpd.conf data into ou=dhcp
  • configure /etc/dhcpd.conf to use LDAP (with or without authentication)

Please also read the README.ldap file in the documentation directory of the dhcp-common package.

Importing data

The dhcp-common package has a contrib script which can be used to import an existing /etc/dhcpd.conf file into LDAP. The script is located at the documentation directory inside the contrib directory:

/usr/share/doc/dhcp-common-<version>/contrib/dhcpd-conf-to-ldap.pl

More experienced administrators wanting to create an LDIF file from scratch should consult the README.ldap file mentioned before.

For this example, we will import the following simple configuration file:

ddns-update-style none;

subnet 172.16.10.0 netmask 255.255.255.0 {
        option routers 172.16.10.1;
        option subnet-mask 255.255.255.0;

        option domain-name "example.com";

        option domain-name-servers 10.0.0.5;
        default-lease-time 21600;
        max-lease-time 43200;

        deny unknown-clients;

        host test009.example.com {
                hardware ethernet 00:C0:DF:02:93:71;
                fixed-address 172.16.10.5;
        }
}

The command below creates the ldif file corresponding to our current dhcpd.conf configuration. Please note that this script has not yet been tested with all possible dhcp configuration scenarios. Always review the resulting LDIF file.

$ perl /usr/share/doc/dhcp-common-3.0.3/contrib/dhcpd-conf-to-ldap.pl \ 
--basedn "ou=dhcp,dc=example,dc=com" \ 
--dhcpdn "cn=DHCP Config,ou=dhcp,dc=example,dc=com" \ 
--conf /etc/dhcpd.conf --server cs4.example.com --ldif dhcpd.ldif
Creating LDAP Configuration with the following options:
        Base DN: ou=dhcp,dc=example,dc=com
        DHCP DN: cn=DHCP Config,ou=dhcp,dc=example,dc=com
        Server DN: cn=cs4.example.com, ou=dhcp,dc=example,dc=com

Done.

The options we used are:

  • basedn: branch where dhcp information will be stored
  • dhcpdn: entry which will contain the configuration of our server
  • conf: dhcpd.conf file which will be migrated to LDAP
  • server: fqdn of the dhcp server (should match the output of the hostname command)
  • ldif: output ldif file

dhcpd.ldif now has the data we will import. Let's take a look:

dn: cn=cs4.example.com, ou=dhcp,dc=example,dc=com
cn: cs4.example.com
objectClass: top
objectClass: dhcpServer
dhcpServiceDN: cn=DHCP Config,ou=dhcp,dc=example,dc=com

dn: cn=DHCP Config,ou=dhcp,dc=example,dc=com
cn: DHCP Config
objectClass: top
objectClass: dhcpService
dhcpPrimaryDN: cn=cs4.example.com, ou=dhcp,dc=example,dc=com
dhcpStatements: ddns-update-style none

dn: cn=172.16.10.0, cn=DHCP Config,ou=dhcp,dc=example,dc=com
cn: 172.16.10.0
objectClass: top
objectClass: dhcpSubnet
objectClass: dhcpOptions
dhcpNetMask: 24
dhcpStatements: default-lease-time 21600
dhcpStatements: max-lease-time 43200
dhcpStatements: deny unknown-clients
dhcpOption: routers 172.16.10.1
dhcpOption: subnet-mask 255.255.255.0
dhcpOption: domain-name "example.com"
dhcpOption: domain-name-servers 10.0.0.5

dn: cn=test009.example.com, cn=172.16.10.0, cn=DHCP Config,ou=dhcp,dc=example,dc=com
cn: test009.example.com
objectClass: top
objectClass: dhcpHost
dhcpHWAddress: ethernet 00:c0:df:02:93:71
dhcpStatements: fixed-address 172.16.10.5

This data can now be imported. We will use the DHCP Admin account for this:

$ ldapadd -x -D "uid=DHCP Admin,ou=System Accounts,dc=example,dc=com" -W -f dhcpd.ldif
Enter LDAP Password: secretpass
adding new entry "cn=cs4.example.com, ou=dhcp,dc=example,dc=com"

adding new entry "cn=DHCP Config,ou=dhcp,dc=example,dc=com"

adding new entry "cn=172.16.10.0, cn=DHCP Config,ou=dhcp,dc=example,dc=com"

adding new entry "cn=test009.example.com, cn=172.16.10.0, cn=DHCP Config,ou=dhcp,dc=example,dc=com"

Final adjustments to dhcpd.conf

We can now remove most of the configuration from /etc/dhcpd.conf, leaving only the LDAP part. This results in the following file:

ldap-server "cs4.conectiva";
ldap-port 389;
ldap-username "uid=DHCP Reader,ou=System Accounts,dc=example,dc=com";
ldap-password "dhcpreader";
ldap-base-dn "ou=dhcp,dc=example,dc=com";
ldap-method dynamic;

Above we chose to use authenticated binds, but anonymous searches can also be used: juse leave ldap-username and ldap-password out. After this last change, the dhcp server can be started and it will be consulting the LDAP tree.

Delegation

If you want to give someone DHCP administrative privileges, just put his/her dn in the DHCP Admins group. For example, to give such privileges to the user joe:

$ ldapmodify -x -D 'uid=DHCP Admin,ou=System Accounts,dc=example,dc=com' -W
Enter LDAP Password: secretpass
dn: cn=DHCP Admins,ou=System Groups,dc=example,dc=com
changetype: modify
add: member
member: uid=joe,ou=People,dc=example,dc=com

modifying entry "cn=DHCP Admins,ou=System Groups,dc=example,dc=com"

^D

Sudo

Sudo has support for storing its rules in a branch in an LDAP tree. See the README.LDAP file in sudo's documentation directory for specific details. Here we will show some basic examples.

Importing data

The sudo rpm package contains a script called sudoers2ldif that can be used to convert an existing /etc/sudoers file to an ldif one which can be imported into LDAP. Alternatively, since the sudo schema is reasonable simple, it can be populated by hand.

Suppose you have /etc/sudoers like this:

Defaults authenticate
%webadm web.example.com=NOPASSWD: /sbin/service httpd *,/usr/local/sbin/admindomains.sh
%users gateway.example.com=/usr/local/sbin/upload.sh
ROOT ALL=(ALL) ALL

Converting this file into an LDIF file that can be imported at the ou=sudoers,dc=example,dc=com branch is done like this:

# export SUDOERS_BASE=ou=sudoers,dc=example,dc=com
# sudoers2ldif /etc/sudoers > sudoers.ldif

The resulting ldif file contains:

dn: cn=defaults,ou=sudoers,dc=example,dc=com
objectClass: top
objectClass: sudoRole
cn: defaults
description: Default sudoOption's go here
sudoOption: authenticate

dn: cn=root,ou=sudoers,dc=example,dc=com
objectClass: top
objectClass: sudoRole
cn: root
sudoUser: root
sudoHost: ALL
sudoCommand: (ALL) ALL

dn: cn=%webadm,ou=sudoers,dc=example,dc=com
objectClass: top
objectClass: sudoRole
cn: %webadm
sudoUser: %webadm
sudoHost: web.example.com
sudoCommand: /sbin/service httpd *
sudoCommand: /usr/local/sbin/admindomains.sh
sudoOption: !authenticate

dn: cn=%users,ou=sudoers,dc=example,dc=com
objectClass: top
objectClass: sudoRole
cn: %users
sudoUser: %users
sudoHost: gateway.example.com
sudoCommand: /usr/local/sbin/upload.sh

Note that the RDN of each entry was choosen by the script to be equal to the user/group who can execute the commands, because that's basically the layout of /etc/sudoers. With LDAP, however, we could give more meaningful names for these entries, such as "cn=Web Administration" or "cn=Upload rights" for the examples above.

Anyway, this LDIF can be easily imported using the Sudo Admin account:

# ldapadd -x -D 'uid=Sudo Admin,ou=System Accounts,dc=example,dc=com' -W -f sudoers.ldif
Enter LDAP Password: secretpass
adding new entry "cn=defaults,ou=sudoers,dc=example,dc=com"

adding new entry "cn=%webadm,ou=sudoers,dc=example,dc=com"

adding new entry "cn=%users,ou=sudoers,dc=example,dc=com"

Configuring sudo

Now we have just to configure sudo to actually use our LDAP server for its configuration (or just part of the configuration).

As of this writing, the sudo package by default uses /etc/ldap.conf for the ldap configuration. This file is shared with pam_ldap and nss_ldap, and there are pros and cons for this sharing. See the URL below for an unfinished thread about this: http://archives.mandrivalinux.com/cooker/2006-05/msg00527.php

The most important setting in this file is sudoers_base, which we must point to ou=sudoers,dc=example,dc=com in our case. By default, anonymous searches of ou=sudoers are allowed, so we don't have to worry about authentication credentials now.

Finally, if you want sudo to completely ignore the contents of /etc/sudoers, then add the attribute below to the cn=defaults entry:

sudoOption: ignore_local_sudoers

Delegating

To give administrative rights over the ou=sudoers branch in LDAP, just include the user's dn in the Sudo Admins group. For example, to give such rights to the martins user:

$ ldapmodify -x -D 'uid=Sudo Admin,ou=System Accounts,dc=example,dc=com' -W
Enter LDAP Password: >secretpass
dn: cn=Sudo Admins,ou=System Groups,dc=example,dc=com
changetype: modify
add: member
member: uid=martins,ou=People,dc=example,dc=com

modifying entry "cn=Sudo Admins,ou=System Groups,dc=example,dc=com"

^D

Caveats

The default ACLs allow anonymous access to the ou=sudoers branch. This means that any user with network access to the LDAP server can list everybody's sudo privileges. Depending on the security policies of the site, this may or may not be wanted.

To avoid this, the ACLs could be changed to disallow anonymous access to this branch, but each machine with sudo would need to have the sudo ldap configuration file changed to have a binddn and a password. And, since this configuration file and its options are also shared among pam_ldap and nss_ldap, this means that nss_ldap would also start to use authenticated binds. See http://archives.mandrivalinux.com/cooker/2006-05/msg00527.php for a small discussion about the pros and cons.

Heimdal (kerberos)

OpenLDAP can be used as a backend for heimdal's database, meaning principal accounts can be stored in LDAP. This text will document the steps needed to integrate Heimdal's LDAP backend with OpenLDAP and openldap-mandriva-dit.

Introduction

We will start with a new realm which we will call EXAMPLE.COM. The rest of this text assumes that openldap-mandriva-dit is installed and that the supplied installation script was executed, either manually or via Fibric.

When using the LDAP backend, it's advisable to have a script to create users, because Heimdal by default will use the account structural objectClass. Since it's more common to use inetOrgPerson (or a derived class), the principal entry would need to be removed and re-added later with inetOrgPerson.

Another approach would be to first create the user with whatever means are standard (smbldap-tools, manual script, a template in gq or luma, etc.) and then add the kerberos attributes later. We will document both approaches here.

Packages

Due to conflicts with MIT's Kerberos packages, Heimdal is packaged as follows in CS4:

  • heimdal-libs
  • heimdal-server
  • heimdal-workstation

Conflicts have been added where needed. Only heimdal-libs can be installed concurrently with MIT's libraries.

Overview of the changes

Here is a quick overview of the needed changes so that Heimdal can user OpenLDAP as its database backend, as well as use the openldap-mandriva-dit DIT:

  • configure Heimdal to use LDAP for its backend
  • configure OpenLDAP to accept connections from Heimdal via ldapi://
  • configure OpenLDAP to map the Heimdal ldapi:// connection to an account administration DN
  • test this mapping
  • initialize the database
  • managing user accounts

Heimdal with OpenLDAP

In order to have a database in LDAP, the following [kdc] section has to be used in Heimdal's /etc/krb5.conf:

[kdc]
        database = {
                dbname = ldap:ou=People,dc=example,dc=com
                mkey_file = /var/heimdal/mkey
                acl_file = /var/heimdal/kadmind.acl
        }

This will instruct Heimdal to use the OpenLDAP server installed on the same host and to use the ou=People branch for its principals. The access method Heimdal uses is ldapi://, which is a unix socket on the local filesystem, and authentication is handled by SASL EXTERNAL which we will configure in a moment.

Using ldapi://

OpenLDAP needs to be configured to accept conections via ldapi://, a local unix socket. This is done in the /etc/sysconfig/ldap file. Change the SLAPD URL list to the following:

# SLAPD URL list
SLAPDURLLIST="ldap:/// ldaps:/// ldapi:///"

OpenLDAP will need to be restarted, of course.

Using SASL EXTERNAL

Heimdal uses SASL EXTERNAL to authenticate itself to the OpenLDAP server when connecting via the ldapi:// socket. When doing this, the bind dn becomes:

[root@cs4 ~]# ldapwhoami -Y EXTERNAL -H ldapi:///var/run/ldap/ldapi
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn:gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
Result: Success (0)

We are going to map this dn to a more meaningful binddn via authz-regexp. The slapd.conf file provided with openldap-mandriva-dit already does this, but here it is anyway for completeness:

(...)
ppolicy_default "cn=default,ou=Password Policies,dc=example,dc=com"

authz-regexp "gidNumber=0\\\+uidNumber=0,cn=peercred,cn=external,cn=auth"
        "uid=Account Admin,ou=System Accounts,dc=example,dc=com"
authz-regexp ^uid=([^,]+),cn=[^,]+,cn=auth$ uid=$1,ou=People,dc=example,dc=com

With this change, and after restarting OpenLDAP, ldapwhoami now says we are an Account Admin:

[root@cs4 ~]# ldapwhoami -Y EXTERNAL -H ldapi:///var/run/ldap/ldapi
SASL/EXTERNAL authentication started
SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth
SASL SSF: 0
dn:uid=account admin,ou=system accounts,dc=example,dc=com
Result: Success (0)

Note that any process connecting to the ldapi:// socket as root (uid=0, gid=0) will be treated as an Account Administrator after this change!

Initializing the realm

We can now initialize the Kerberos realm. After OpenLDAP has been restarted, run the following:

[root@cs4 ~]# kadmin -l
kadmin> init EXAMPLE.COM
Realm max ticket life [unlimited]:7d<
Realm max renewable ticket life [unlimited]:7d
kadmin>

This will create some default principals under ou=People:

ou=People
  krb5PrincipalName=krbtgt/EXAMPLE.COM@EXAMPLE.COM,ou=People,dc=example,dc=com
  krb5PrincipalName=kadmin/changepw@EXAMPLE.COM,ou=People,dc=example,dc=com
  krb5PrincipalName=kadmin/admin@EXAMPLE.COM,ou=People,dc=example,dc=com
  krb5PrincipalName=changepw/kerberos@EXAMPLE.COM,ou=People,dc=example,dc=co
  krb5PrincipalName=kadmin/hprop@EXAMPLE.COM,ou=People,dc=example,dc=com
  krb5PrincipalName=default@EXAMPLE.COM,ou=People,dc=example,dc=com

Managing user and principal accounts

The Heimdal schema allows for principal accounts to be stored in a separate branch from the user accounts. For example, one could have the principal accounts under ou=KerberosPrincipals and user accounts under ou=People.

This has the obvious disadvantage of creating a problem with user management: when an user is removed, for example, the corresponding principal account has to be removed also. In other words, we would need a pointer in the user entry for the principal account (the seeAlso attribute is commonly used for things like this). And a script would need to follow this attribute and delete the principal account.

The advantage would be that one user could be associated with many kerberos principals using the seeAlso attribute, like john@REALM and john/admin@REALM.

But the biggest disadvantage of this scheme where principals are separated from users is integration with Samba and Ldap simple binds: it's lost. Heimdal will only update the samba password hash if it's stored in the same entry. The same with userPassword: with OpenLDAP using the smbk5pwd module (built with kerberos support), simple binds will only be able to use the kerberos password if everything is in the same entry.

Another option would be to store the principal keys and related attributes right under the user entry. We can do this because the kerberos object classes are auxiliary. So, user John would be, for example, uid=john,ou=people,dc=example,dc=com and the kerberos keys would be stored in this same entry. When this user is removed, so is the principal account. The disadvantage is that one user can only have one principal, and not several as in the previous case (where john could have john@REALM and john/admin@REALM associated with the same uid=john,ou=people,dc=example,dc=com entry).

But one issue comes up: what do we use to create this user in the first place? If we use kadmin, then it will create an entry of the form krb5PrincipalName=john@EXAMPLE.COM,ou=People,dc=example,dc=com with account being the structural object class. Since we tend to use a class derived from person as the structural class (such as inetOrgPerson), there is a conflict. If we use kadmin, we would have to remove the entry and re-add it with inetOrgPerson (and its mandatory attributes).

We can change the structural class that Heimdal will use, but it doesn't add the mandatory attributes so we can't just switch to inetOrgPerson in Heimdal's configuration: it will not work.

Another better option would be to first create the user with another tool, such as smbldap or another script, and later add the kerberos attributes. The main advantages are:

  • RDN naming consistent with the rest of the entries (no krb5PrincipalName in the RDN if we don't want it)
  • structural object class as we want it (for example, inetOrgPerson)
  • user and principal accounts together under ou=People

The biggest disadvantage is that the mapping between users and principals would be 1:1, that is, one user could have at most only one kerberos principal associated with its entry.

Both schemes can be used together, however. It's actually more a question about how the accounts will be administered. So, regular users could have their kerberos keys stored in the user's entry, while administration and service keys would be stored under the same branch, but have no user associated with them. It's not very consistent with the tree (after all, ou=People was meant to host actual persons), but it works.

We will now give examples of two possibilities: using kadmin directly and using another script to first create the user account and then add kerberos attributes.

Using kadmin directly

We will create a kerberos account for the user "john" using kadmin directly. We don't even have to start heimdal at this stage because we will be using kadmin in local mode:

[root@cs4 ~]# kadmin -l
kadmin> add john
Max ticket life [1 day]:10h
Max renewable life [1 week]:1w
Principal expiration time [never]:
Password expiration time [never]:
Attributes []:
john@EXAMPLE.COM's Password: secretpassword
Verifying - john@EXAMPLE.COM's Password: secretpassword
kadmin>

This creates the following entry:

dn: krb5PrincipalName=john@EXAMPLE.COM,ou=People,dc=example,dc=com
objectClass: top
objectClass: account
objectClass: krb5Principal
objectClass: krb5KDCEntry
krb5PrincipalName: john@EXAMPLE.COM
uid: john
krb5KeyVersionNumber: 0
krb5MaxLife: 36000
krb5MaxRenew: 604800
krb5KDCFlags: 126
(...)

We can obtain a ticket for this user:

# service heimdal start
Starting kdc:                                                  [  OK  ]
[root@cs4 ~]# kinit john
john@EXAMPLE.COM's Password: secretpassword
[root@cs4 ~]# klist
Credentials cache: FILE:/tmp/krb5cc_0
        Principal: john@EXAMPLE.COM

  Issued           Expires          Principal
Jun 20 15:43:23  Jun 20 22:23:23  krbtgt/EXAMPLE.COM@EXAMPLE.COM
[root@cs4 ~]#

Notice, however that this "john" user doesn't have the necessary posix attributes to become a system user. We will need something else to create this posix user anyway: Heimdal's role here is over.

Adding kerberos attributes to an existing user entry

If the user account already exists in the directory, then all we need to do is add the necessary Heimdal object classes to this account. Being auxiliary, this makes perfect sense.

So, for this example, we will use a pre-configured smbldap-tools package to create a sample user and then add the kerberos classes and attributes to it, but any posix user that already exists would work.

Notice we don't add the samba attributes just yet:

[root@cs4 ~]# smbldap-useradd mary
[root@cs4 ~]# getent passwd mary
mary:x:1001:513:System User:/home/mary:/bin/bash

The user looks like this in the directory:

dn: uid=mary,ou=People,dc=example,dc=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: mary
sn: mary
givenName: mary
uid: mary
uidNumber: 1001
gidNumber: 513
homeDirectory: /home/mary
loginShell: /bin/bash
gecos: System User
userPassword: {crypt}x

We will use the following LDAP modification to add the kerberos attributes and classes to this user:

[andreas@cs4 ~]$ ldapmodify -x -D 'uid=Account Admin,ou=System Accounts,dc=example,dc=com' -W
Enter LDAP Password: somepass
dn: uid=mary,ou=people,dc=example,dc=com
changetype: modify
add: objectClass
objectClass: krb5Principal
objectClass: krb5KDCEntry
-
add: krb5PrincipalName
krb5PrincipalName: mary@EXAMPLE.COM
-
add: krb5KDCFlags
krb5KDCFlags: 126
-
add: krb5KeyVersionNumber
krb5KeyVersionNumber: 0

modifying entry "uid=mary,ou=people,dc=example,dc=com"

Now "mary" is recognized as a kerberos principal and we can have Heimdal add the keys and other missing attributes by just invoking the password change command from as an administrator or in local admin mode:

[root@cs4 ~]# kadmin -l
kadmin> passwd mary
mary@EXAMPLE.COM's Password: newpass
Verifying - mary@EXAMPLE.COM's Password: newpass
kadmin>

This adds the remaining attributes and now "mary" is a full kerberos principal:

[andreas@cs4 ~]$ kinit mary
mary@EXAMPLE.COM's Password: newpass
[andreas@cs4 ~]$ klist
Credentials cache: FILE:/tmp/krb5cc_500
        Principal: mary@EXAMPLE.COM

  Issued           Expires          Principal
Jun 20 16:14:48  Jun 20 22:54:48  krbtgt/EXAMPLE.COM@EXAMPLE.COM
[andreas@cs4 ~]$

And with the added bonus of being a posix account as well.

Password integration

Probably the most wanted feature of a setup where Heimdal uses OpenLDAP as its database backend is the password integration.

Three very common authentication sources in a network are Samba passwords, posix passwords and kerberos passwords. Just using LDAP doesn't magically integrate these three passwords: LDAP is just a storage and, in fact, each application uses it for itself:

  • samba: sambaNTPassword, sambaLMPassword
  • heimdal: krb5Key
  • posix: userPassword

So, pam_ldap can change the userPassword when the user runs the password command at the console, but the heimdal key and samba hashes won't be changed. And thus we have a syncronization problem.

Some administrators run scripts to solve this, or only allow the user to change his/her password via some sort of front-end which will take care of the details of updating all password hashes. Another option that is available is to use the contributed smbk5pwd module.

smbk5pwd

The smbk5pwd module is available in the contribs directory of the OpenLDAP tarball and, when built with Samba and Kerberos support, allows for this password integration to work automatically. The module is available by default in the openldap-servers package.

This integration happens in three ways:

a) EXOP password modifications
This module intercepts OpenLDAP EXOP password modifications and updates both the Kerberos key and the Samba hashes of the same entry, if they are present. This means that a ldappasswd command, for example, will also end up changing the Samba and Kerberos passwords. Samba, when using the ldap passwd sync option in smb.conf, also ends up performing an EXOP password modification and will thus update the Kerberos key without even knowing it.

b) kpasswd
When Heimdal receives a password change request via kadmin or kpasswd, it will check if the target entry contains Samba password hashes. If it does, these hashes will also be updated. The userPassword attribute, used for simple binds, is not touched, but see below.

c) simple binds (userPassword)
Simple binds use the userPassword attribute for password verification. If this attribute contains the special hash specified {K5KEY}, then the password verification will be performed against the kerberos key of the same entry. So, in order to make simple binds use the kerberos password, all we have to do is replace the userPassword attribute with {K5KEY}.

Using smbk5pwd

The following configuration changes are necessary in order to use the smbk5pwd module:

(...)
modulepath      /usr/lib/openldap
moduleload      back_monitor.la
moduleload      syncprov.la
moduleload      ppolicy.la
moduleload      smbk5pwd.so
password-hash   {K5KEY}
(...)
database bdb
(...)
overlay ppolicy
ppolicy_default "cn=default,ou=Password Policies,dc=example,dc=com"

overlay smbk5pwd
(...)

Openldap will now need to be able to enter the /var/heimdal directory, so change its permissions to something like this:

# chmod g+rx /var/heimdal
# chgrp ldap /var/heimdal

If this permissions change is not done, openldap startup will fail.

Note we need to change the server password hash mechanism to {K5KEY}. If we don't do it, then password changes via EXOP will overwrite the userPassword attribute with the new hash instead of leaving it at {K5KEY} and we will loose our password integration.

The smbk5pwd module accepts some configuration directives like smbk5pwd-enable and smbk5pwd-must-change, please see its README file in the openldap-servers documentation directory for details.

If Samba is being used, then the ldap passwd sync option should be set to Only. With this option, Samba will only perform the EXOP password modification and expect the OpenLDAP server to update the Samba hashes, which is exactly what smbk5pwd will do:

To the [global] section of /etc/samba/smb.conf, add:

ldap passwd sync = Only

Now, test ldappasswd, smbpasswd and kpasswd: a password change performed by any of these should change all three authentication sources.

TODO

  • better MTA Admins definitions/roles
  • have a good MTA schema (virtual domains ready perhaps?) and ACLs for it
  • sort out the read-only accounts: use a generic one instead of creating one for each service (like nssldap today). Or not. There are other services which need read access, like for example Postfix when using LDAP maps.
  • always keep in mind the possibility that we may have too many system accounts: don't let this get out of control.
  • add minssf support for password access?
  • come up with an easy way to switch between a profile of anonymous-can-read and only-authenticated-users-can-read. Would be nice if we could add the anonynous user to a group and then just have the ACLs end in something like "by anongroup read by * none".
  • provide personal address books for each user (Image:bug_small.png Bug #22658)
  • try to get rid of anonymous access to ou=sudoers. We may need to rebuild sudo pointing it to another configuration file to avoid sharing stuff with nss_ldap and pam_ldap, since by default it uses /etc/ldap.conf. We can then make this other file mode 0600 owned by root:root and voilĂ , same behaviour as with the regular /etc/sudoers.
  • use a global switch in the installation script so that the administrator can choose to have anonymous access enabled or not. A slightly different set of ACLs would then be used.
  • heimdal uses only one branch to create its principals. Here we configured ou=People, but that is not very nice because service principals (like ldap/ldap.example.com@REALM) would also be stored there. When searching for principals, heimdal can find them anywhere on the tree, so perhaps we can get away with creating another branch for these service principals?
  • benchmark to see the impact of all these ACLs!
  • add support for autfs maps in LDAP
Personal tools