Linux Server Security (2nd Edition( [Electronic resources]

Michael D. Bauer

نسخه متنی -صفحه : 94/ 51
نمايش فراداده

7.3. LDAP Database Management

Okay, we've installed OpenLDAP, configured slapd, gotten TLS encryption working, and created our first LDAP record. Now it's time to add some users and start using our servere.g., for authenticating IMAP sessions.

7.3.1. Database Structure

The first step in creating an LDAP user database is to decide on a directory structure i.e., whether to group users and other entities or whether to instead use a completely flat structure. If your LDAP database will be used strictly as an online address book or authentication server, a flat database may suffice; in that case, your users' Distinguished Names (DNs) will look like this: dn=Mick Bauer,dc=wiremonkeys,dc=org. We discussed some of the issues surrounding LDAP database structure earlier, in the section "Hierarchies and Naming Conventions." As I mentioned then, LDAP is extremely flexible, and there are far more ways to structure an LDAP database than I can do justice to here. So to keep this discussion simple, I'm going to use a flat database for the rest of this chapter's examples; I leave it to you to determine whether and how to structure an LDAP database that best meets your particular LDAP needs. The documentation at http://www.openldap.org and included with OpenLDAP software provides ample examples.

7.3.1.1 Schema and user records

A related decision you'll need to make is which LDAP attributes to include for each record. I've described how these are grouped and interrelated in schemas; you may recall that the schemas you specify (include) in /etc/openldap/slapd.conf determine which attributes will be available for you to use in records.

In addition to including schema in /etc/openldap/slapd.conf, in each record you create you'll need to use objectclass statements to associate the appropriate schemas with each user. Again, the schema files in /etc/openldap/schema determine which schema support which attributes, and within a given schema, which object classes those attributes apply to.

It may seem like a kluge to sort through and combine objectclasses, trying to cobble together the right combination of LDAP attributes to meet your particular needs: wouldn't it make more sense to somehow pull all your desired attributes into a single, custom objectclass? It would, and you can, by creating your own schema file. However, it turns out to be much less work, and much less of a "reinventing the wheel" exercise, to simply combine a few standard objectclasses.

Suppose you intend to use your LDAP server to authenticate one of the many protocols such as POP or IMAP, which request a username and a password. The essential LDAP attributes for this purpose are uid and userPassword..

One way to determine which schema and object classes provide uid and userPassword is to grep the contents of /etc/openldap/schema for the strings uid and userPassword, note which files contain them, and then manually parse those files to find the object classes that contain those attributes in MUST() or MAY( ) statements. If I do this for uid on Red Hat 7.3 system running OpenLDAP 2.0, I find that the files core.schema, cosine.schema, inetorgperson.schema, nis.schema, and openldap.schema contain references to the uid attribute.

Quick scans of these files (using less) tell me that:

core.schema's object uidObject requires uid cosine.schema's only reference to the attribute uid is commented out and can be disregarded inetorgperson.schema contains an object class, inetOrgPerson, which supports uid as an optional attribute nis.schema contains two object classes, posixAccount and shadowAccount, both of which require uid openldap.schema's object class OpenLDAPperson also requires uid Luckily, there's a much faster way to determine the same information: the gq LDAP tool allows you to browse all supported attributes in all supported schema on your LDAP server. Figure 7-5 contains a screenshot illustrating my LDAP server's support for uid, according to gq.

Figure 7-5. Schema browsing with gq

Note the "Used in objectclasses" box in Figure 7-5, which tells us that the selected attribute, uid, is used in the object classes uidObject, posixAccount, shadowAccount, and inetOrgPerson, all four of which we identified earlier via grep. The object class OpenLDAPperson does not appear in the gq screen: this is because the LDAP server in question doesn't have an include statement in its /etc/openldap/slapd.conf file for the file openldap.schema. When in doubt, therefore, you should include even schemas you're not sure you need: after you settle on an LDAP record format, you can always uninclude schemas that don't contain object classes you need.

All this probably sounds like a lot of trouble, and indeed it can be, but it's extremely important for you to be able to create records that contain the kinds of information pertinent to your LDAP needs, and since LDAP is so flexible, figuring out precisely how to assemble that information in the form of attributes can take some tinkering.

7.3.2. Building and Adding Records

Just as schema-browsing can be done either manually or via GUI, so can adding LDAP records. We used the manual method to create our root-organization entry, and we'll do so again to add our first user record. This method has two steps: first create a special text file in LDIF format, and then use the ldapadd command to import it into the LDAP database. Consider the LDIF file in Example 7-6.

Example 7-6. LDIF file for a user record

dn: cn=Wong Fei Hung,dc=wiremonkeys,dc=org cn: Wong Fei Hung sn: Wong givenname: Fei Hung objectclass: person objectclass: top objectclass: inetOrgPerson mail: wongfh@wiremonkeys.org telephonenumber: 651-344-1043 o: Wiremonkeys uid: wongfh Since they determine everything else, we'll begin by examining Example 7-6s objectclass statements: this user has been associated with the object classes top (mandatory for all records), person, and inetorgperson. I chose person because it supports the attributes userPassword (which is not set in Example 7-6; we'll set Mr. Wong's password shortly) and telephonenumber, which I don't need yet but may in the future. The object class inetOrgPerson, as we've seen, supports the uid attribute, plus a whole slew of others that may also come in handy later.

One way around having to know and comply with the MUST and MAY restrictions in schema is to add the statement schemacheck off to /etc/openldap/slapd.conf. This will allow you to use any attribute defined in any schema file included in slapd.conf without needing to pay any attention to object classes. However, it will also adversely affect your LDAP server's interoperability with other LDAP servers, and even with other applications (besides flouting LDAP RFCs), so many LDAP experts consider it poor form to disable schema-checking in this manner.

It isn't necessary to discuss each and every line in Example 7-6; many of the attributes are self-explanatory. Just know that:

You don't need to set every attribute you intend to use, but some are mandatory (i.e., are contained in MUST() statements in their respective object class definitions).

Each attribute you do define must be specified in the MUST( ) or MAY( ) statement of at least one of the object classes defined in the record.

Some attributes, such as cn, may be defined multiple times in the same record.

To add the record specified in Example 7-6, use the ldapadd command:

ldapadd -x -D "cn=ldapguy,dc=wiremonkeys,dc=org" -W -f ./wong.ldif This is very similar to how we used ldapadd in the previous section. For a complete explanation of this command's syntax, see the ldapadd(1) manpage.

If you specified the attributes required by all object classes set in the LDIF file and if all attributes you specified are supported by those object classes and if, when prompted, you provide the correct LDAP bind password, the record will be added to the database. If any of those conditions is false, however, the action will fail and ldapadd will tell you what went wrong. Thus, you can use good old trial and error to craft a workable record format; after all, once you've figured this out once, you can use the same format for subsequent records without going through all this schema-induced zaniness.

I offer one caveat: if your LDIF file contains multiple records, which is permitted, keep in mind that if your LDAP server detects an error, it will quit parsing the file and will not attempt to add any records below the one that failed. Therefore, you should stick to single-record LDIF files for the first couple of user-adds, until you've finalized your record format.

That's the manual record-creation method: it's a little clunky, but it easily accommodates tinkering, which is especially useful in the early stages of LDAP database construction.

Once you've got a user record or two in place, you can use a GUI tool such as gq or ldapbrowser to create additional records. In gq, for example, left-clicking on a record pops up a menu containing the option "New Use current entry," which copies the selected record into a new record. This is much faster and simpler than manually typing everything into an LDIF file.

7.3.3. Creating Passwords

I mentioned in the description of Example 7-6 that we generally don't specify user passwords in LDIF files: there's a separate mechanism for that, in the form of the command ldappasswd. By design, its syntax is very similar to that of ldapadd:

ldappasswd -S -x -D "cn=hostmaster,dc=upstreamsolutions,dc=com" / -W "cn=Phil Lesh,dc=upstreamsolutions,dc=com" (You'll be prompted for your existing and new passwords after you enter this command.) You don't need to be logged in to a shell session on the LDAP server to use the ldappasswd command; you can use the -H flag to specify the URL of a remote LDAP server. For example:

ldappasswd -S -x -H ldaps://ldap.upstreamsolutions.com / -D "cn=hostmaster,dc=upstreamsolutions,dc=com" -W "cn=Phil Lesh,dc=upstreamsolutions,dc=com" This flag may also be used with ldapadd.

Note the ldaps:// URL in the previous example: since I've specified the -x flag for simple cleartext authentication, I definitely need to connect to the server with TLS encryption (again, ldaps is ldap secure) rather than in the clear. (See the previous section.) Having said all that, however, I must point out that password management for end users is one of LDAP's problem areas. On the one hand, if your users all have access to the ldappasswd command (e.g., if they run Linux), you can use a combination of local /etc/ldap.conf files and scripts/frontends for ldappasswd to make it reasonably simple for users to change their own passwords.

But if users run some other OS (e.g., Windows), you must either manage passwords centrally (i.e., have all users contact the email administrator every time they need to change their password) or issue users LDAP client software such as LDAP Browser/Editor and then teach users how to use it. The former option needn't be as distasteful as it may sound, so long as your email administrator is trustworthy (this is necessary, regardless) and some common sense is applied in how you go about it.

7.3.4. Access Controls

Technically, we've covered or touched on all the tasks needed to build an LDAP server using OpenLDAP (excluding, necessarily, the sometimes lengthy step of actually getting your various server applications to successfully authenticate users against it, which is covered by those respective applications' own documentation). In the interest of robust security, there's one more thing we should discuss in detail: OpenLDAP access-control lists (ACLs).

Like most other things affecting the slapd daemon, these are set in /etc/openldap/slapd.conf. And like most other things involving LDAP, they can be confusing, to say the least, and usually require some tinkering to get right.

Example 7-7 shows a sample set of ACLs.

Example 7-7. ACLs in /etc/slapd.conf

access to attrs=userPassword by dn="cn=ldapguy,dc=wiremonkeys,dc=org" write by self write by * compare access to * by dn="cn=ldapguy,dc=wiremonkeys,dc=org" write by users read by * auth ACLs are described in detail in the slapd.conf(5) manpage, but in Example 7-7, you can get the gist of how these work: for each LDAP specification to which you wish to control access, you specify who may access it and with what level of access. Technically, an entire ACL may be listed on one line (e.g., access to * by users read by * auth), but by convention, we list each by... statement on its own line; slapd is smart enough to know that the string access to marks the beginning of the next ACL.

While I'm not going to describe ACL syntax in great detail, there are a few important points to note. First, ACLs are parsed from top to bottom, and "first match wins": they act like a stack of filters. Therefore, it's crucial that you put specific ACLs and by... statements above more general ones.

For example, in Example 7-7 we see an ACL restricting access to the userPassword attribute, followed by one applicable to *, meaning the entire LDAP database. Putting the userPassword ACL first means that the rule "allow users to change their own passwords" (i.e., access to attrs=userPassword by self write) is an exception to the more general rule "users may have only read-access to anything" (i.e., access to * by users read).

Another important point is that access levels are hierarchical. Possible levels are none, auth, compare, search, read, and write, where none is the lowest level of access and write is the highest, and where each level includes the rights of all levels lower than it. These two points, the "first match wins" rule and the inclusive nature of access levels, are crucial in understanding how ACLs are parsed and in making sure yours don't lead to either greater or lesser levels of access in a given situation than you intend.