Last day I spin up an OpenLDAP server on one of my servers and as usual, to find an updated guide for OpenLDAP is really hard. OpenLDAP has two methods to be configured:
slapd.conf
which is deprecated;olc
orcn=config
which should be the de facto method.
I won't go too much in details but when you use slapd.conf
you have to restart your slapd
daemon each time you change the config. While it's ok for a small server, it can be slow with a big directory. One-Line Configuration or olc
is a way to store OpenLDAP configuration in the DIT
(Directory Information Tree) under cn=config
. If you do research be sure to find answers for the olc
and not slapd.con
(legacy is still highly present). If you want to go further:
- Zytrax article which explains well OLC;
- OpenLDAP doc on how to configure slapd.
NixOS version issue
To the day I'm writing this article NixOS current stable release is 20.09
. Between 20.09
and unstable
the OpenLDAP module change and I want to use the new module (mostly because it eases a lot of configuration). While it is more or less easy to use the unstable
channel to use or update a software (e.g. firefox
), I found no documentation or nothing about how to use the unstable
channel for a service.
Actually the trick is "basic" but mostly clever:
{ config, pkgs, ... }:
let
nixpkgs-unstable = fetchTarball
"https://github.com/nixos/nixpkgs/archive/f217c0ea7c148ddc0103347051555c7c252dcafb.tar.gz";
in {
imports = [
(nixpkgs-unstable + "/nixos/modules/services/databases/openldap.nix")
...
];
# Cf. above, we use openldap service from unstable
disabledModules = [ "services/databases/openldap.nix" ];
...
}
We simply disable a module (from the current channel) and import the unstable module from the unstable
channel.
Spin up the OpenLDAP server
Once the unstable
module is used, you mostly have to copy and paste default settings written in the documentation, and it should work straight forward. If you want to open your LDAP server on the Internet, don't forgot to open the according port(s) in the firewall (TCP).
It should look to something like that:
services.openldap = {
enable = true;
settings = {
attrs.olcLogLevel = [ "stats" ];
children = {
"cn=schema" = {
includes = [
"${pkgs.openldap}/etc/schema/core.ldif"
"${pkgs.openldap}/etc/schema/cosine.ldif"
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
];
};
"olcDatabase={-1}frontend" = {
attrs = {
objectClass = "olcDatabaseConfig";
olcDatabase = "{-1}frontend";
olcAccess = [
"{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage stop by * none stop"
];
};
};
"olcDatabase={0}config" = {
attrs = {
objectClass = "olcDatabaseConfig";
olcDatabase = "{0}config";
olcAccess = [ "{0}to * by * none break" ];
};
};
"olcDatabase={1}mdb" = {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
olcDatabase = "{1}mdb";
olcDbDirectory = "/var/db/ldap";
olcDbIndex = [
"objectClass eq"
"cn pres,eq"
"uid pres,eq"
"sn pres,eq,subany"
];
olcSuffix = "dc=locahlo,dc=st";
olcAccess = [ "{0}to * by * read break" ];
};
};
};
};
};
The config pasted above is pretty much the same as the default one.
Add TLS to encrypt traffic
By default, OpenLDAP traffic is not encrypted which is not really great to be honest. Let's add TLS!
It needs three olc
directives in cn=config
(which is the configuration top node). While all guides I found online use ldapmodify
to modify OpenLDAP configuration, we won't use it because we are on NixOS and want reproducibility. We rather will edit our NixOS configuration to modify our OpenLDAP DIT
(I agree it goes against olc
will but eh, not a big deal).
It took me a little while to understand where to put those directives. Actually, in openldap.settings
, all directives are under cn=config
. If we look at the very first child above, cn=schema
, when transformed into LDIF during NixOS evaluation it will be translated/mutated into cn=schema,cn=config
.
We have to edit our services.openldap.settings.attr
to add our directives. Currently, there is only one directive: olcLogLevel
. According to OpenLDAP TLS documentation we have to add:
olcTLSCACertifacetFile
;olcTLSCertificateFile
;olcTLSCertificateKeyFile
.
services.openldap = {
settings = {
attrs = {
olcLogLevel = [ "stats" ];
olcTLSCACertificateFile = "";
olcTLSCertificateFile = "";
olcTLSCertificateKeyFile = "";
};
...
TLS powered by Let's Encrypt
It would have been too easy to just stop there and to deliver certificates manually or with a provisioning tool. Our encryption will be powered by Let's Encrypt. I agree the way I went is far from perfect, but it's the easier.
services.nginx = {
enable = true;
recommendedTlsSettings = true;
virtualHosts."ldap.locahlo.st" = {
enableACME = true;
forceSSL = true;
locations."/.well-known" = {
extraConfig = ''
proxy_ssl_server_name on;
'';
};
locations."/" = {
extraConfig = ''
proxy_ssl_server_name on;
deny all;
'';
};
};
};
It activates the Nginx service (if yet activated), allow the /.well-known
directory to be accessed (required for ACME challenge validation) and refuses all traffic on the "landing page" (403 everybody). Now just fill certificates' location to OpenLDAP and job's done:
services.openldap = {
settings = {
attrs = {
olcLogLevel = [ "stats" ];
olcTLSCACertificateFile = "/var/lib/acme/ldap.locahlo.st/fullchain.pem";
olcTLSCertificateFile = "/var/lib/acme/ldap.locahlo.st/cert.pem";
olcTLSCertificateKeyFile = "/var/lib/acme/ldap.locahlo.st/key.pem";
};
...
nixos-rebuild test
aaaaaaand it fails. Why? Because OpenLDAP process don't have rights to read the certificates (actually it lacks rights to read directory's content (740 acme nginx
)).
So Nginx can read content? Clearly not perfect, but things are as they are, we will change the group with which OpenLDAP process runs, services.openldap.group = "nginx";
.
nixos-rebuild test
and it now works. Don't forget to change on which protocol OpenLDAP listens and to open the according port.
TL;DR
{ config, pkgs, ... }:
let
subdomain = "ldap";
domain = "locahlo";
tld = "st";
fqdn = subdomain + "." + domain + "." + tld;
nixpkgs-unstable = fetchTarball
"https://github.com/nixos/nixpkgs/archive/f217c0ea7c148ddc0103347051555c7c252dcafb.tar.gz";
in {
imports = [
(nixpkgs-unstable + "/nixos/modules/services/databases/openldap.nix")
];
# Cf. above, we use openldap service from unstable
disabledModules = [ "services/databases/openldap.nix" ];
services.nginx = {
enable = true;
recommendedTlsSettings = true;
virtualHosts."${fqdn}" = {
enableACME = true;
forceSSL = true;
locations."/.well-known" = {
extraConfig = ''
proxy_ssl_server_name on;
'';
};
locations."/" = {
extraConfig = ''
proxy_ssl_server_name on;
deny all;
'';
};
};
};
networking.firewall.allowedTCPPorts = [ 636 ];
services.openldap = {
enable = true;
urlList = [ "ldaps:///" ];
group = "nginx"; # FIXME workaround to access to Let's Encrypt certificates
settings = {
attrs = {
olcLogLevel = [ "stats" ];
olcTLSCACertificateFile =
"/var/lib/acme/${fqdn}/fullchain.pem";
olcTLSCertificateFile = "/var/lib/acme/${fqdn}/cert.pem";
olcTLSCertificateKeyFile = "/var/lib/acme/${fqdn}/key.pem";
};
children = {
"cn=schema" = {
includes = [
"${pkgs.openldap}/etc/schema/core.ldif"
"${pkgs.openldap}/etc/schema/cosine.ldif"
"${pkgs.openldap}/etc/schema/inetorgperson.ldif"
];
};
"olcDatabase={-1}frontend" = {
attrs = {
objectClass = "olcDatabaseConfig";
olcDatabase = "{-1}frontend";
olcAccess = [
"{0}to * by dn.exact=uidNumber=0+gidNumber=0,cn=peercred,cn=external,cn=auth manage stop by * none stop"
];
};
};
"olcDatabase={0}config" = {
attrs = {
objectClass = "olcDatabaseConfig";
olcDatabase = "{0}config";
olcAccess = [ "{0}to * by * none break" ];
};
};
"olcDatabase={1}mdb" = {
attrs = {
objectClass = [ "olcDatabaseConfig" "olcMdbConfig" ];
olcDatabase = "{1}mdb";
olcDbDirectory = "/var/db/ldap";
olcDbIndex = [
"objectClass eq"
"cn pres,eq"
"uid pres,eq"
"sn pres,eq,subany"
];
olcRootDN = "cn=admin,dc=${domain},dc=${tld}";
olcSuffix = "dc=${domain},dc=${tld}";
olcAccess = [ "{0}to * by * read break" ];
};
};
};
};
};
...
}
Side note
If you wish to extend your Config I highly recommend you to only do it via your nix configuration and not by ldapmodify
.
Thanks
I'd like to thank @petabyteboy, they gave me the snippet to use OpenLDAP from unstable
.