#format wiki = How to Setup an OpenLDAP-based PGP Keyserver = This howto will show you how to set up an LDAP-based PGP Keyserver. <> == LDAP: A Very Short Introduction == ~LDAP is a protocol for accessing data in a hierarchical directory. The fact that ~LDAP defines a protocol means that any ~LDAP client can talk to any ~LDAP server implementation. This is different from, say, ~SQL, which is just a query language. A consequence of this is that although all ~SQL servers understand ~SQL, how ~SQL commands are issued and how results are returned is different for each database implementation. ~LDAP's hierarchical structure is designed for storing and accessing directories, such as, telephone book data. In ~LDAP both the internal nodes and the leaves can store data. The nodes are referred to as entries. Entries contain attributes, which are just key-value pairs. To look up an entry (or attribute), we need its name or path. This is called a distinguished name ({{{dn}}}) in ~LDAP. A {{{dn}}} consists of components (relative {{{dn}}}s or {{{rdn}}}s). Working right to left, the server first checks that the first components match the base dn and selects that entry. Then, it finds the child entry that matches the next rdn, visits that entry. The server repeats this process until the path has been completely traversed or an error occurred. This should be straightforward to understand: this is basically how resolving a name in ~DNS works and how a filename is looked up. In ~LDAP, however, entries don't have names. Instead, any piece or pieces of the content can be used to address the entry as long as those pieces uniquely identify that entry among its siblings. This is both useful (there is no need to come up with names or for users to manage another piece of data) and very flexible (different applications may have different information available and can create names differently). Consider the following very simple directory: {{{ dc=example,dc=org | v ou=sales ou: Organizational Unit / | \ (think: department) v v v gn: John John Jane gn: Given Name sn: Smith Doe Doe sn: Surname }}} We can address the entry on the left (John Smith) using the following {{{dn}}}: {{{sn=Smith,ou=sales,dc=example,dc=org}}}. We can't use {{{gn=John}}} for the last component of the {{{dn}}}, since this is not unique among the siblings (the second entry's given name is also John). Note that we can just use the {{{sn}}} when addressing this entry even though not all surnames are unique. Smith, however, is (currently) unique and thus uniquely identifies the entry among its siblings. Since the second entry doesn't have any attributes whose value is unique among its siblings, we need to use a combined {{{rdn}}}, which uses multiple attributes joined by a {{{+}}}. In this case, we'd use: {{{gn=John+sn=Doe,ou=sales,dc=example,dc=org}}}. For more information about ~Open~LDAP, the following resources are excellent: * [[http://www.zytrax.com/books/ldap/|LDAP for Rocket Scientists]] * [[https://help.ubuntu.com/lts/serverguide/openldap-server.html|Ubuntu Server Guide: OpenLDAP Server]] == Installing OpenLDAP == ~Open~LDAP consists of the server ({{{slapd}}}) and some client utilities ({{{ldap-utils}}}). We need to install the utilities to be able to configure {{{slapd}}}: {{{slapd}}} uses an ~LDAP database for its configuration. To install on Debian, run: {{{ #!shell $ sudo apt-get install slapd ldap-utils }}} When installing ~Open~LDAP on Debian, {{{debconf}}} does some initial configuration for you. At normal priority, you'll only have to enter the administrator's password. If you use {{{dpkg-reconfigure slapd}}}, you can change some more settings quite easily. The most important of these are the base distinguished name ({{{bdn}}}), which is the name of the root of the tree managed by your slapd instance. This is normally set to the ~DNS domain name of the machine (Debian defaults to the output of {{{hostname -d}}}). If you use {{{example.org}}}, then slapd will serve requests for the tree rooted at {{{dc=example,dc=org}}}. To start the server (slapd), run: {{{ $ sudo /etc/init.d/slapd start }}} == Installing an Additional Schema == {{{#!wiki caution '''New Schema''' This section is out of date; installing a new schema is meanwhile much easier. In particular there is no need to convert the configuration file. Please use only the schema described by tehse two files: * [[https://gnupg.org/misc/gnupg-ldap-schema.ldif|Schema]] * [[https://gnupg.org/misc/gnupg-ldap-init.ldif|Init]] }}} Installing a new schema under ~Open~LDAP is rather painful. First, the schema needs to be converted to ~LDIF (the ~LDAP Data Interchange Format). This can be done using the {{{slaptest}}} utility, which takes an old-school ~Open~LDAP configuration file and converts it to a modern {{{cn=config}}} style. However, since you are already using a {{{cn=config}}} style configuration and there is no easy way to convert it to an old-school style configuration, we need to manually merge the two. First, we need a temporary directory: {{{ $ mkdir -p /tmp/ldap-config $ cd /tmp/ldap-config }}} Now, we create an old-school configuration file: {{{ $ echo 'include pgp-keyserver.schema' > slapd.conf }}} Add the {{attachment:pgp-keyserver.schema}} file to {{{/tmp/ldap-config}}} and then run: {{{ $ mkdir output $ /usr/sbin/slaptest -f slapd.conf -F output }}} This creates a {{{cn=config}}} hierarchy under the output directory with the following files: {{{ $ find output/ output/ output/cn=config output/cn=config/cn=schema output/cn=config/cn=schema/cn={0}pgp-keyserver.ldif output/cn=config/olcDatabase={-1}frontend.ldif output/cn=config/olcDatabase={0}config.ldif output/cn=config/cn=schema.ldif output/cn=config.ldif }}} Before we can merge the file {{{cn={0}pgp-keyserver.ldif}}} into our configuration tree, we need to modify it a bit. First, at the top of the file, we need to change the {{{dn}}}. It should be: {{{ cn={###}pgp-keyserver,cn=schema,cn=config }}} Where {{{###}}} is the load order of the schema. We want our schema to load last. To avoid figuring out the right number, just choose a large number, like 100, and on import ~Open~LDAP will set this to the maximum number plus one. A few lines later is a line that looks like: {{{ cn={0}pgp-keyserver }}} Remove this line. (~Open~LDAP will automatically fill in the {{{cn}}} attribute.) Finally, at the bottom of the file we need to remove the lines that start like this: {{{ structuralObjectClass: entryUUID: creatorsName: createTimestamp: entryCSN: modifiersName: modifyTimestamp: }}} At last, we can add the schema file: {{{ $ sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /tmp/ldap-config/output/cn=config/cn=schema/cn={0}pgp-keyserver.ldif SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 adding new entry "cn={100}pgp-keyserver,cn=schema,cn=config" }}} (Using the {{{EXTERNAL}}} authentication mechanism, you just need to be root to gain admin access to slapd; you don't need to use your slapd admin password.) If you get the following error: {{{ ldap_add: Server is unwilling to perform (53) additional info: operation requires sibling renumbering }}} Then the number you selected above conflicts with an existing schema. To see that the schema is really installed, we can list the installed schemas using the following command: {{{ $ sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b cn=schema,cn=config cn SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 dn: cn=schema,cn=config cn: schema dn: cn={0}core,cn=schema,cn=config cn: {0}core dn: cn={1}cosine,cn=schema,cn=config cn: {1}cosine dn: cn={2}nis,cn=schema,cn=config cn: {2}nis dn: cn={3}inetorgperson,cn=schema,cn=config cn: {3}inetorgperson dn: cn={4}pgp-keyserver,cn=schema,cn=config cn: {4}pgp-keyserver }}} In case you are wondering: yes, this is the recommended way to install new schemas. See, for instance, this section in [[http://www.zytrax.com/books/ldap/ch6/slapd-config.html#use-schemas|LDAP for Rocket Scientists]]. == Setting up the Data Structures == Recall that an ~LDAP server provides access to a hierarchical database. Thus, we need to create some containers for the users and the keys. {{{#!wiki note Please use the new schema and adjust the provided init file instead }}} Create a file called {{{/tmp/keyserver.ldif}}} with the following contents and replace {{{dc=EXAMPLE,dc=ORG}}} with your base {{{dn}}}: {{{ dn: cn=PGPServerInfo,dc=EXAMPLE,dc=ORG cn: PGPServerInfo objectclass: pgpserverinfo pgpSoftware: OpenLDAP pgpVersion: 2.2.27 pgpBaseKeyspaceDN: ou=PGP Keys,dc=EXAMPLE,dc=ORG dn: ou=PGP Keys,dc=EXAMPLE,dc=ORG objectclass: organizationalUnit ou: PGP Keys dn: ou=PGP Users,dc=EXAMPLE,dc=ORG objectclass: organizationalUnit ou: PGP Users }}} This file describes a change set in ~LDIF format (~LDAP Data Interchange Format). The first stanza creates an entry that ~GPG uses to detect that this is really an ~OpenPGP key server. The next stanza creates an organizational unit ({{{ou}}}) that all keys are added to. And the last stanza creates an organizational unit that all key server users are added to. To apply the changes, use the following command (as usual, change {{{dc=EXAMPLE,dc=ORG}}} accordingly): {{{ $ ldapadd -x -D "cn=admin,dc=EXAMPLE,dc=ORG" -W -f /tmp/keyserver.ldif Enter LDAP Password: adding new entry "cn=PGPServerInfo,dc=EXAMPLE,dc=ORG" adding new entry "ou=PGP Keys,dc=EXAMPLE,dc=ORG" adding new entry "ou=PGP Users,dc=EXAMPLE,dc=ORG" }}} The requested password is the admin password for your ~Open~LDAP installation (on Debian, this was entered when the package was installed). You'll see that three entries were successfully added. == Adding Users == We can now add some users who will be able to manage the keys in the directory. Create a file called {{{/tmp/keyserver-user.ldif}}} with the following contents: {{{ dn: uid=user1,ou=PGP Users,dc=EXAMPLE,dc=ORG objectClass: inetOrgPerson objectClass: uidObject sn: lastname cn: firstname lastname userPassword: {SSHA}... }}} Update the fields appropriately. In particular, you can change the value of {{{uid}}}, {{{sn}}} (the surname), {{{cn}}} (the common name). These are the required fields. Other fields, such as {{{gn}}} (given name), are optional and are elided. You can add more than one user at once. Just use multiple copies of the above entry. Be sure to separate each entry with a blank line. To add the user, run the following command: {{{ $ ldapadd -x -D "cn=admin,dc=EXAMPLE,dc=ORG" -W -f /tmp/keyserver-user.ldif Enter LDAP Password: adding new entry "uid=user1,ou=PGP Users,dc=EXAMPLE,dc=ORG" }}} To list all of the {{{PGP Users}}}, you can use the following command: {{{ $ ldapsearch -LLL -x -D "cn=admin,dc=EXAMPLE,dc=ORG" -W -b 'ou=PGP Users,dc=EXAMPLE,dc=ORG' Enter LDAP Password: dn: ou=PGP Users,dc=EXAMPLE,dc=ORG objectClass: organizationalUnit ou: PGP Users dn: uid=user1,ou=PGP Users,dc=EXAMPLE,dc=ORG objectClass: inetOrgPerson objectClass: uidObject uid: user1 sn: lastname cn: firstname lastname ... }}} Note: the {{{-b}}} selects a base {{{dn}}} and, since there is no filter, {{{ldapsearch}}} lists all of its descendants. == Access Control == We've created the users, but they can't actually access the directory yet. To do this, we need to modify the ~ACLs. In slapd, ~ACLs are applied on a per-database basis. To list the configured databases, use the following command: {{{ $ sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b "cn=config" | grep olcDatabase: SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 olcDatabase: {-1}frontend olcDatabase: {0}config olcDatabase: {1}mdb }}} Here, you see that I have three databases. This is normal. The frontend database is a special database that provides fallback access control: slapd uses a "first match wins" model for access control. The config database is used for slapd's own configuration and we don't want to mess with it. The last database is where our DIT lies. It is possible to have multiple databases and different formats are common. Currently, mdb is the recommended format. The database's {{{dn}}} is just it's name combined with the {{{bdn}}}. In my case, it is: {{{ olcDatabase={1}mdb,cn=config }}} We now need to get the current set of ~ACLs. On Debian the default set are: {{{ $ sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b olcDatabase={1}mdb,cn=config olCaccess SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 dn: olcDatabase={1}mdb,cn=config olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymous auth by * none olcAccess: {1}to dn.base="" by * read olcAccess: {2}to * by * read }}} The first rule allows an authenticated user to change his or her own password, an anonymous user to authenticate against (bind to) the entry and reject any other type of access. The second rule allows anyone to read the base {{{dn}}}. The third is a catch all and allows anyone to read any node. [Note_1] We now want to insert a rule allowing anyone connected from localhost to add or modify keys as well as authenticated ~PGP users connecting from anywhere. Further, anyone should be able to read the keys. Because ~Open~LDAP uses first match wins when resolving ~ACLs, we need to add the rule before the last entry: the last entry catches everything. Here, we add it immediately prior to the last entry. For the first part of this rule to work---anonymous updates from localhost---we also need to enable anonymous updates. We can do both of these at once. (Note: you can easily disable either of these, if you prefer.) Create {{{/tmp/keyserver-acls.ldif}}} with the following content: {{{ # userPassword may be written only by users themselves dn: olcDatabase={DDD}mdb,cn=config changetype: modify add: olcAccess # Allow access via localhost to add or modify keys. # Allow authenticated PGP Users to update keys. # Allow anyone else to read the keys. olcAccess: {XXX} to dn.subtree="ou=PGP Keys,dc=EXAMPLE,dc=ORG" by peername.ip=127.0.0.1 write by peername.ip=:: write by dn.regex="^uid=([^,]+),ou=PGP Users,dc=EXAMPLE,dc=ORG" write by * read # Allow any connection to localhost to update the PGP keys # (including removing them!) This is only needed if the anonymous # updates from localhost are desired. dn: cn=config add: olcAllows olcAllows: update_anon }}} Be sure to replace {{{DDD}}} with your database index (and change mdb, if necessary). Also, replace {{{XXX}}} with the index of the last entry in the ~ACL. In our example, this was 2. ~Open~LDAP will insert the entry at that position and push any entry that was at that position one forward. To add the changes, run: {{{ $ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/keyserver-acls.ldif }}} Now, enough machinery is in place to actually push some keys! Let's give it a try using an anonymous write: {{{ $ gpg --keyserver ldap://localhost --send-keys 8bafcdbd gpg: sending key 8BAFCDBD to ldap server localhost }}} And as an authenticated user: {{{ $ gpg --keyserver ldap://localhost/uid=user1,ou=PGP%20Users,dc=EXAMPLE,dc=ORG --send-keys 8BAFCDBD gpg: sending key 8BAFCDBD to ldap server localhost }}} Where {{{PASSWORD}}} is the user's password. The keyserver con be configured in {{{~/.gnupg/gpg.conf}}}: {{{ keyserver ldap://localhost/uid=user1,ou=PGP%20Users,dc=EXAMPLE,dc=ORG }}} Note: We set {{{tls}}} to {{{try}}}. In this howto, we didn't actually configure TLS. If you are going to access the host via an insecure network connection, it makes sense to enable this. Note: Since your password is in this file, be sure it is not world readable. == Debugging == If something goes wrong, then it is help to enabling logging in {{{slapd}}}. You can do this with the following {{{LDIF}}} file: {{{ dn: cn=config changetype: modify replace: olcLogLevel olcLogLevel: stats }}} Then, watch syslog for messages. Before to disable this once you are done. If you accidentally insert a bad ~ACL, you can remove it using the following ~LDIF file: {{{ dn: olcDatabase={1}mdb,cn=config changetype: modify delete: olcAccess olcAccess: {XXX} }}} Where {{{XXX}}} is the number of the rule. Apply it as follows: {{{ sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/revert.ldif }}} If you need additional help, please ask on the gnupg-users mailing list. [Note_1]: In Debian stable (actually, Knoppix), which uses hdb database format, the output is as follows: {{{ $ sudo ldapsearch -LLL -Y EXTERNAL -H ldapi:/// -b olcDatabase={1}hdb,cn=config olCaccess SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 dn: olcDatabase={1}hdb,cn=config olcAccess: {0}to attrs=userPassword,shadowLastChange by self write by anonymous auth by dn="cn=admin,dc=example,dc=org" write by * none olcAccess: {1}to dn.base="" by * read olcAccess: {2}to * by self write by dn="cn=admin,dc=example,dc=org" write by * read }}}