Effectively working with LDAP as an authentication mechanism for PostgreSQL typically requires extensive knowledge in both domains. While trying to be as complete yet succinct as possible, I am detailing how to enable TLS between PostgreSQL and the OpenLDAP server.
Ironically, the most complicated aspect has nothing to do with either PostgreSQL or OpenLDAP. It has to do with the steps of creating and signing private keys and certificates.
Note: I had seriously considered leaving out much of the OpenLDAP commands, but I figured it might benefit you if, like me, you do not touch this very often.
The underlying assumptions are:
The proof of concept described in this document consists of a single stand-alone server:
my-ldapcn=admin,dc=nodomainadmindn: dc=pg_user,dc=nodomaindn: cn=postgres,dc=pg_user,dc=nodomaindn: cn=user1,dc=pg_user,dc=nodomaindn: cn=user2,dc=pg_user,dc=nodomainApart from the standard steps of installing and configuring Postgres for remote access, edit the host-based authentication file to enable Postgres to refer to the LDAP service for authentication.
Roles and user accounts used in Postgres should be declared. Keep in mind that assigning passwords is not required:
|
1 2 3 |
-- Example. CREATE ROLE user1 WITH LOGIN PASSWORD NULL; CREATE ROLE user2 WITH LOGIN PASSWORD NULL; |
pg_hba.conf assumptions:
|
1 2 3 4 5 |
# IPv4 local connections: host all all 0.0.0.0/0 ldap ldapserver=127.0.0.1 ldapprefix="cn=" ldapsuffix=", dc=pg_user, dc=nodomain" # IPv6 local connections: host all all ::0/0 ldap ldapserver=127.0.0.1 ldapprefix="cn=" ldapsuffix=", dc=pg_user, dc=nodomain" |
|
1 |
apt-get install -y slapd ldap-utils |
Running netstat -tlnp returns the following:
|
1 2 3 |
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 0.0.0.0:389 0.0.0.0:* LISTEN 7742/slapd tcp6 0 0 :::389 :::* LISTEN 7742/slapd |
You can control the behavior of OpenLDAP by using these command line utilities:
ldapmodifyldapaddldapdeleteldapmodrdnldapsearchldapwhoamiIt is understood that administering the LDAP server requires setting the password. Although LDAP installation includes setting the password, which will be admin in this example, you can reset the password at will by executing the following command:
|
1 2 3 4 5 6 |
# Select "No" when asked to configure the database with dbconfig-common. # Set the domain name for your LDAP server, for example, "example.com". # Set the organization name for your LDAP server, for example, "Example Inc". # Set the administrator password for your LDAP server. dpkg-reconfigure slapd |
The following bash script demonstrates configuring OpenLDAP to authenticate three Postgres roles: postgres, user1, and user2.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
#!/bin/bash set -e ########################################## # Admin password is "admin". # The top domain is assigned as "nodomain". ########################################## ldapadd -v -xD "cn=admin,dc=nodomain" -w admin <<'_eof_' # Create the topmost domain. dn: dc=pg_user,dc=nodomain objectClass: dcObject objectClass: organization dc: pg_user o: Postgres Users description: all postgres users reside here # Create superuser role postgres. dn: cn=postgres,dc=pg_user,dc=nodomain objectclass: top objectclass: person cn: postgres sn: postgres userPassword: postgres _eof_ ########################################## # Add roles. ########################################## ldapadd -v -xD "cn=admin,dc=nodomain" -w admin <<'_eof_' # Create other user accounts. # Create role user1. dn: cn=user1,dc=pg_user,dc=nodomain objectclass: top objectclass: person cn: user1 sn: user1 userPassword: user1 # Create role user2. dn: cn=user2,dc=pg_user,dc=nodomain objectclass: top objectclass: person cn: user2 sn: user2 userPassword: user2 _eof_ |
A simple login confirms LDAP and PostgreSQL are working correctly. Even though there is an encrypted session between psql and the Postgres server, there is no encrypted session between Postgres and LDAP as authentication is performed:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) root@my-ldap:~# psql 'host=my-ldap dbname=postgres user=postgres password=postgres' -c "SELECT 'ping' AS test_connectivity;" test_connectivity ------------------- ping root@my-ldap:~# psql 'host=my-ldap dbname=postgres user=user1 password=user1' -c "SELECT 'ping from user1' AS test_connectivity;" test_connectivity ------------------- ping from user1 root@my-ldap:~# psql 'host=my-ldap dbname=postgres user=user2 password=user2' -c "SELECT 'ping from user2' AS test_connectivity;" test_connectivity ------------------- ping from user2 |
To work with SSL certificates on Ubuntu, these packages should be present:
|
1 |
apt install -y gnutls-bin ssl-cert |
Authentication between the Postgres and LDAP servers includes confirming that the hosts making connection attempts are legitimate. For that reason, the certificates for both servers must be signed, which means a Certificate Authority mechanism is required.
|
1 2 |
# Generate private key for self-signed Certificate Authority. certtool --generate-privkey --bits 4096 --outfile /etc/ssl/private/mycakey.pem |
In this case, the CA certificate is configured to expire in 10 years:
|
1 2 3 4 5 6 7 |
# Define CA certificate attributes. cat > /etc/ssl/ca.info <<'EOF' cn = my-ldap ca cert_signing_key expiration_days = 3650 EOF |
An internal system can get away with using self-signed CA certificates. Otherwise, it is strongly recommended that your certificates be signed by an authorized CA. In this case, the certificate, once signed, is placed in the same directory where the other CA certificates are stored: /usr/local/share/ca-certificates.
|
1 2 3 4 5 6 |
# Generate a self-signed CA certificate and copy the CRT to the trusted CA # certificates directory used by both the Postgres and LDAP servers. certtool --generate-self-signed \ --load-privkey /etc/ssl/private/mycakey.pem \ --template /etc/ssl/ca.info \ --outfile /usr/local/share/ca-certificates/mycacert.crt |
Attention: Once signed, the CA certificate is copied onto the Postgres and LDAP servers respectively. In this case, because they are on the same host, it is located on host my-ldap. Otherwise, it must be copied to all Postgres and LDAP hosts.
Once copied into the correct directory, the list of CA certificates is updated, adding the self-signed CA certificate:
|
1 2 |
# Update the list of CA certificates on both the Postgres and LDAP servers. update-ca-certificates |
From here on, it is important to include the fully qualified domain name of the LDAP certificate hostname, which in this case is my-ldap.
Generate a private key for the LDAP server:
|
1 2 3 |
certtool --generate-privkey \ --bits 2048 \ --outfile /etc/ldap/my-ldap_slapd_key.pem |
Define LDAP certificate attributes:
|
1 2 3 4 5 6 7 8 9 |
# For a certificate request that expires in one year. cat > /etc/ssl/my-ldap.info <<'EOF' organization = mycompany cn = my-ldap tls_www_server encryption_key signing_key expiration_days = 365 EOF |
Sign the LDAP private key using the self-signed Certificate Authority certificate and its private key:
|
1 2 3 4 5 6 |
certtool --generate-certificate \ --load-privkey /etc/ldap/my-ldap_slapd_key.pem \ --load-ca-certificate /etc/ssl/certs/mycacert.pem \ --load-ca-privkey /etc/ssl/private/mycakey.pem \ --template /etc/ssl/my-ldap.info \ --outfile /etc/ldap/my-ldap_slapd_cert.pem |
Update access permissions of the private key:
|
1 2 |
sudo chgrp openldap /etc/ldap/my-ldap_slapd_key.pem sudo chmod 0640 /etc/ldap/my-ldap_slapd_key.pem |
Create and save the LDAP TLS configuration file, certinfo.ldif:
|
1 2 3 4 5 6 7 8 9 10 11 |
cat > /etc/ldap/schema/certinfo.ldif <<'EOF' dn: cn=config add: olcTLSCACertificateFile olcTLSCACertificateFile: /etc/ssl/certs/mycacert.pem - add: olcTLSCertificateFile olcTLSCertificateFile: /etc/ldap/my-ldap_slapd_cert.pem - add: olcTLSCertificateKeyFile olcTLSCertificateKeyFile: /etc/ldap/my-ldap_slapd_key.pem EOF |
Enable OpenLDAP to use TLS:
|
1 |
ldapmodify -Y EXTERNAL -H ldapi:/// -f /etc/ldap/schema/certinfo.ldif |
Validation returns the string anonymous:
|
1 |
ldapwhoami -x -ZZ -H ldap://my-ldap |
This test confirms that the previous behavior without TLS still works:
|
1 2 |
# Without TLS. ldapsearch -x -H ldap://my-ldap -b dc=nodomain -D cn=admin,dc=nodomain -w admin |
This test should return the exact output of the previous ldapsearch. Failure is indicated by a short message that is invoked by using the switch -ZZ.
|
1 2 |
# With TLS. ldapsearch -x -ZZ -H ldap://my-ldap -b dc=nodomain -D cn=admin,dc=nodomain -w admin |
This is the host-based rule with TLS configured. Notice the minor edit in red:
|
1 2 3 4 5 |
# IPv4 local connections: hostssl all all 0.0.0.0/0 ldap ldapserver=my-ldap ldapprefix="cn=" ldapsuffix=", dc=pg_user, dc=nodomain" <span style="color: #ff0000;">ldaptls=1</span> # IPv6 local connections: hostssl all all ::0/0 ldap ldapserver=my-ldap ldapprefix="cn=" ldapsuffix=", dc=pg_user, dc=nodomain" <span style="color: #ff0000;">ldaptls=1</span> |
After updating pg_hba.conf with the new argument, ldaptls=1, a server reload is executed, followed by a psql ping:
|
1 |
systemctl reload postgresql@14-main |
|
1 2 3 4 |
root@my-ldap:~# psql 'host=10.231.38.243 dbname=postgres user=postgres password=postgres' -c "SELECT 'ping from postgres' AS test_connectivity;" test_connectivity -------------------- ping from postgres |
Percona Distribution for PostgreSQL provides the best and most critical enterprise components from the open-source community in a single distribution, designed and tested to work together.
Resources
RELATED POSTS