about summary refs log tree commit diff
path: root/nixpkgs/nixos/tests/ldap.nix
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2019-02-07 15:19:21 +0000
committerAlyssa Ross <hi@alyssa.is>2019-02-07 23:35:47 +0000
commite5013c05a2f845255debf94318ab38ecef1c186b (patch)
treebec11a0bd31d3432a16899e5539f1098f1c168a4 /nixpkgs/nixos/tests/ldap.nix
parent4fc07c92ec07cafcf6d56143ea7334693143ef88 (diff)
parent2d2f10475138b7206572dc3ec288184df2be022e (diff)
downloadnixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar.gz
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar.bz2
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar.lz
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar.xz
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.tar.zst
nixlib-e5013c05a2f845255debf94318ab38ecef1c186b.zip
Merge commit '2d2f10475138b7206572dc3ec288184df2be022e'
Diffstat (limited to 'nixpkgs/nixos/tests/ldap.nix')
-rw-r--r--nixpkgs/nixos/tests/ldap.nix383
1 files changed, 330 insertions, 53 deletions
diff --git a/nixpkgs/nixos/tests/ldap.nix b/nixpkgs/nixos/tests/ldap.nix
index 035a81924174..b3fd42e75886 100644
--- a/nixpkgs/nixos/tests/ldap.nix
+++ b/nixpkgs/nixos/tests/ldap.nix
@@ -1,41 +1,23 @@
 import ./make-test.nix ({ pkgs, lib, ...} :
 
 let
+  unlines = lib.concatStringsSep "\n";
+  unlinesAttrs = f: as: unlines (lib.mapAttrsToList f as);
 
+  dbDomain = "example.com";
   dbSuffix = "dc=example,dc=com";
-  dbPath = "/var/db/openldap";
   dbAdminDn = "cn=admin,${dbSuffix}";
-  dbAdminPwd = "test";
-  serverUri = "ldap:///";
+  dbAdminPwd = "admin-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${dbAdminPwd}'
+  dbAdminPwdHash = "{SSHA}i7FopSzkFQMrHzDMB1vrtkI0rBnwouP8";
   ldapUser = "test-ldap-user";
   ldapUserId = 10000;
-  ldapUserPwd = "test";
+  ldapUserPwd = "user-password";
+  # NOTE: slappasswd -h "{SSHA}" -s '${ldapUserPwd}'
+  ldapUserPwdHash = "{SSHA}v12XICMZNGT6r2KJ26rIkN8Vvvp4QX6i";
   ldapGroup = "test-ldap-group";
   ldapGroupId = 10000;
-  setupLdif = pkgs.writeText "test-ldap.ldif" ''
-    dn: ${dbSuffix}
-    dc: ${with lib; let dc = head (splitString "," dbSuffix); dcName = head (tail (splitString "=" dc)); in dcName}
-    o: ${dbSuffix}
-    objectclass: top
-    objectclass: dcObject
-    objectclass: organization
-
-    dn: cn=${ldapUser},${dbSuffix}
-    sn: ${ldapUser}
-    objectClass: person
-    objectClass: posixAccount
-    uid: ${ldapUser}
-    uidNumber: ${toString ldapUserId}
-    gidNumber: ${toString ldapGroupId}
-    homeDirectory: /home/${ldapUser}
-    loginShell: /bin/sh
-    userPassword: ${ldapUserPwd}
-
-    dn: cn=${ldapGroup},${dbSuffix}
-    objectClass: posixGroup
-    gidNumber: ${toString ldapGroupId}
-    memberUid: ${ldapUser}
-  '';
+
   mkClient = useDaemon:
     { lib, ... }:
     {
@@ -43,13 +25,24 @@ let
       virtualisation.vlans = [ 1 ];
       security.pam.services.su.rootOK = lib.mkForce false;
       users.ldap.enable = true;
-      users.ldap.daemon.enable = useDaemon;
+      users.ldap.daemon = {
+        enable = useDaemon;
+        rootpwmoddn = "cn=admin,${dbSuffix}";
+        rootpwmodpw = "/etc/nslcd.rootpwmodpw";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."nslcd.rootpwmodpw".source = pkgs.writeText "rootpwmodpw" dbAdminPwd;
       users.ldap.loginPam = true;
       users.ldap.nsswitch = true;
       users.ldap.server = "ldap://server";
-      users.ldap.base = "${dbSuffix}";
+      users.ldap.base = "ou=posix,${dbSuffix}";
+      users.ldap.bind = {
+        distinguishedName = "cn=admin,${dbSuffix}";
+        password = "/etc/ldap/bind.password";
+      };
+      # NOTE: password stored in clear in Nix's store, but this is a test.
+      environment.etc."ldap/bind.password".source = pkgs.writeText "password" dbAdminPwd;
     };
-
 in
 
 {
@@ -61,28 +54,237 @@ in
   nodes = {
 
     server =
-      { pkgs, ... }:
+      { pkgs, config, ... }:
+      let
+        inherit (config.services) openldap;
+
+        slapdConfig = pkgs.writeText "cn=config.ldif" (''
+          dn: cn=config
+          objectClass: olcGlobal
+          #olcPidFile: /run/slapd/slapd.pid
+          # List of arguments that were passed to the server
+          #olcArgsFile: /run/slapd/slapd.args
+          # Read slapd-config(5) for possible values
+          olcLogLevel: none
+          # The tool-threads parameter sets the actual amount of CPU's
+          # that is used for indexing.
+          olcToolThreads: 1
+
+          dn: olcDatabase={-1}frontend,cn=config
+          objectClass: olcDatabaseConfig
+          objectClass: olcFrontendConfig
+          # The maximum number of entries that is returned for a search operation
+          olcSizeLimit: 500
+          # Allow unlimited access to local connection from the local root user
+          olcAccess: to *
+            by dn.exact=gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth manage
+            by * break
+          # Allow unauthenticated read access for schema and base DN autodiscovery
+          olcAccess: to dn.exact=""
+            by * read
+          olcAccess: to dn.base="cn=Subschema"
+            by * read
+
+          dn: olcDatabase=config,cn=config
+          objectClass: olcDatabaseConfig
+          olcRootDN: cn=admin,cn=config
+          #olcRootPW:
+          # NOTE: access to cn=config, system root can be manager
+          # with SASL mechanism (-Y EXTERNAL) over unix socket (-H ldapi://)
+          olcAccess: to *
+            by dn.exact="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" manage
+            by * break
+
+          dn: cn=schema,cn=config
+          objectClass: olcSchemaConfig
+
+          include: file://${pkgs.openldap}/etc/schema/core.ldif
+          include: file://${pkgs.openldap}/etc/schema/cosine.ldif
+          include: file://${pkgs.openldap}/etc/schema/nis.ldif
+          include: file://${pkgs.openldap}/etc/schema/inetorgperson.ldif
+
+          dn: cn=module{0},cn=config
+          objectClass: olcModuleList
+          # Where the dynamically loaded modules are stored
+          #olcModulePath: /usr/lib/ldap
+          olcModuleLoad: back_mdb
+
+          ''
+          + unlinesAttrs (olcSuffix: {conf, ...}:
+              "include: file://" + pkgs.writeText "config.ldif" conf
+            ) slapdDatabases
+          );
+
+        slapdDatabases = {
+          "${dbSuffix}" = {
+            conf = ''
+              dn: olcBackend={1}mdb,cn=config
+              objectClass: olcBackendConfig
+
+              dn: olcDatabase={1}mdb,cn=config
+              olcSuffix: ${dbSuffix}
+              olcDbDirectory: ${openldap.dataDir}/${dbSuffix}
+              objectClass: olcDatabaseConfig
+              objectClass: olcMdbConfig
+              # NOTE: checkpoint the database periodically in case of system failure
+              # and to speed up slapd shutdown.
+              olcDbCheckpoint: 512 30
+              # Database max size is 1G
+              olcDbMaxSize: 1073741824
+              olcLastMod: TRUE
+              # NOTE: database superuser. Needed for syncrepl,
+              # and used to auth as admin through a TCP connection.
+              olcRootDN: cn=admin,${dbSuffix}
+              olcRootPW: ${dbAdminPwdHash}
+              #
+              olcDbIndex: objectClass eq
+              olcDbIndex: cn,uid eq
+              olcDbIndex: uidNumber,gidNumber eq
+              olcDbIndex: member,memberUid eq
+              #
+              olcAccess: to attrs=userPassword
+                by self write
+                by anonymous auth
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to attrs=shadowLastChange
+                by self write
+                by dn="cn=admin,${dbSuffix}" write
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" write
+                by * none
+              olcAccess: to dn.sub="ou=posix,${dbSuffix}"
+                by self read
+                by dn="cn=admin,${dbSuffix}" read
+                by dn="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth" read
+              olcAccess: to *
+                by self read
+                by * none
+            '';
+            data = ''
+              dn: ${dbSuffix}
+              objectClass: top
+              objectClass: dcObject
+              objectClass: organization
+              o: ${dbDomain}
+
+              dn: cn=admin,${dbSuffix}
+              objectClass: simpleSecurityObject
+              objectClass: organizationalRole
+              description: ${dbDomain} LDAP administrator
+              roleOccupant: ${dbSuffix}
+              userPassword: ${ldapUserPwdHash}
+
+              dn: ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=accounts,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+
+              dn: ou=groups,ou=posix,${dbSuffix}
+              objectClass: top
+              objectClass: organizationalUnit
+            ''
+            + lib.concatMapStrings posixAccount [
+              { uid=ldapUser; uidNumber=ldapUserId; gidNumber=ldapGroupId; userPassword=ldapUserPwdHash; }
+            ]
+            + lib.concatMapStrings posixGroup [
+              { gid=ldapGroup; gidNumber=ldapGroupId; members=[]; }
+            ];
+          };
+        };
+
+        # NOTE: create a user account using the posixAccount objectClass.
+        posixAccount =
+          { uid
+          , uidNumber ? null
+          , gidNumber ? null
+          , cn ? ""
+          , sn ? ""
+          , userPassword ? ""
+          , loginShell ? "/bin/sh"
+          }: ''
+
+            dn: uid=${uid},ou=accounts,ou=posix,${dbSuffix}
+            objectClass: person
+            objectClass: posixAccount
+            objectClass: shadowAccount
+            cn: ${cn}
+            gecos:
+            ${if gidNumber == null then "#" else "gidNumber: ${toString gidNumber}"}
+            homeDirectory: /home/${uid}
+            loginShell: ${loginShell}
+            sn: ${sn}
+            ${if uidNumber == null then "#" else "uidNumber: ${toString uidNumber}"}
+            ${if userPassword == "" then "#" else "userPassword: ${userPassword}"}
+          '';
+
+        # NOTE: create a group using the posixGroup objectClass.
+        posixGroup =
+          { gid
+          , gidNumber
+          , members
+          }: ''
+
+            dn: cn=${gid},ou=groups,ou=posix,${dbSuffix}
+            objectClass: top
+            objectClass: posixGroup
+            gidNumber: ${toString gidNumber}
+            ${lib.concatMapStrings (member: "memberUid: ${member}\n") members}
+          '';
+      in
       {
         virtualisation.memorySize = 256;
         virtualisation.vlans = [ 1 ];
         networking.firewall.allowedTCPPorts = [ 389 ];
         services.openldap.enable = true;
-        services.openldap.dataDir = dbPath;
+        services.openldap.dataDir = "/var/db/openldap";
+        services.openldap.configDir = "/var/db/slapd";
         services.openldap.urlList = [
-          serverUri
+          "ldap:///"
+          "ldapi:///"
         ];
-        services.openldap.extraConfig = ''
-          include ${pkgs.openldap.out}/etc/schema/core.schema
-          include ${pkgs.openldap.out}/etc/schema/cosine.schema
-          include ${pkgs.openldap.out}/etc/schema/inetorgperson.schema
-          include ${pkgs.openldap.out}/etc/schema/nis.schema
-
-          database mdb
-          suffix ${dbSuffix}
-          rootdn ${dbAdminDn}
-          rootpw ${dbAdminPwd}
-          directory ${dbPath}
-        '';
+        systemd.services.openldap = {
+          preStart = ''
+              set -e
+              # NOTE: slapd's config is always re-initialized.
+              rm -rf "${openldap.configDir}"/cn=config \
+                     "${openldap.configDir}"/cn=config.ldif
+              install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" "${openldap.configDir}"
+              # NOTE: olcDbDirectory must be created before adding the config.
+              '' +
+              unlinesAttrs (olcSuffix: {data, ...}: ''
+                # NOTE: database is always re-initialized.
+                rm -rf "${openldap.dataDir}/${olcSuffix}"
+                install -D -d -m 0700 -o "${openldap.user}" -g "${openldap.group}" \
+                 "${openldap.dataDir}/${olcSuffix}"
+                '') slapdDatabases
+              + ''
+              # NOTE: slapd is supposed to be stopped while in preStart,
+              #       hence slap* commands can safely be used.
+              umask 0077
+              ${pkgs.openldap}/bin/slapadd -n 0 \
+               -F "${openldap.configDir}" \
+               -l ${slapdConfig}
+              chown -R "${openldap.user}:${openldap.group}" "${openldap.configDir}"
+              # NOTE: slapadd(8): To populate the config database slapd-config(5),
+              #                   use -n 0 as it is always the first database.
+              #                   It must physically exist on the filesystem prior to this, however.
+            '' +
+            unlinesAttrs (olcSuffix: {data, ...}: ''
+              # NOTE: load database ${olcSuffix}
+              # (as root to avoid depending on sudo or chpst)
+              ${pkgs.openldap}/bin/slapadd \
+               -F "${openldap.configDir}" \
+               -l ${pkgs.writeText "data.ldif" data}
+              '' + ''
+              # NOTE: redundant with default openldap's preStart, but do not harm.
+              chown -R "${openldap.user}:${openldap.group}" \
+               "${openldap.dataDir}/${olcSuffix}"
+            '') slapdDatabases;
+        };
       };
 
     client1 = mkClient true; # use nss_pam_ldapd
@@ -91,15 +293,91 @@ in
   };
 
   testScript = ''
-    startAll;
+    $server->start;
     $server->waitForUnit("default.target");
+
+    subtest "slapd", sub {
+      subtest "auth as database admin with SASL and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldapi:// -Y EXTERNAL',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+      subtest "auth as database admin with password and check a POSIX account", sub {
+        $server->succeed(join ' ', 'test',
+         '"$(ldapsearch -LLL -H ldap://server',
+             '-D \'cn=admin,${dbSuffix}\' -w \'${dbAdminPwd}\' ',
+             '-b \'uid=${ldapUser},ou=accounts,ou=posix,${dbSuffix}\' ',
+             '-s base uidNumber |',
+           'sed -ne \'s/^uidNumber: \\(.*\\)/\\1/p\' ',
+         ')" -eq ${toString ldapUserId}');
+      };
+    };
+
+    $client1->start;
     $client1->waitForUnit("default.target");
-    $client2->waitForUnit("default.target");
 
-    $server->succeed("ldapadd -D '${dbAdminDn}' -w ${dbAdminPwd} -H ${serverUri} -f '${setupLdif}'");
+    subtest "password", sub {
+      subtest "su with password to a POSIX account", sub {
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+      };
+      subtest "change password of a POSIX account as root", sub {
+        $client1->succeed("chpasswd <<<'${ldapUser}:new-password'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+      subtest "change password of a POSIX account from itself", sub {
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su --login ${ldapUser} -c passwd',
+          'expect "Password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "(current) UNIX password: "',
+          'send "${ldapUserPwd}\n"',
+          'expect "New password: "',
+          'send "new-password\n"',
+          'expect "Retype new password: "',
+          'send "new-password\n"',
+          'expect "passwd: password updated successfully" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "${ldapUserPwd}\n"',
+          'expect "su: Authentication failure" {exit}',
+          'exit 1' . "'");
+        $client1->succeed("${pkgs.expect}/bin/expect -c '" . join ';',
+          'spawn su "${ldapUser}"',
+          'expect "Password:"',
+          'send "new-password\n"',
+          'expect "*"',
+          'send "whoami\n"',
+          'expect -ex "${ldapUser}" {exit}',
+          'exit 1' . "'");
+        $client1->succeed('chpasswd <<<\'${ldapUser}:${ldapUserPwd}\' ');
+      };
+    };
+
+    $client2->start;
+    $client2->waitForUnit("default.target");
 
-    # NSS tests
-    subtest "nss", sub {
+    subtest "NSS", sub {
         $client1->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}");
         $client1->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'");
         $client1->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}");
@@ -110,8 +388,7 @@ in
         $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'");
     };
 
-    # PAM tests
-    subtest "pam", sub {
+    subtest "PAM", sub {
         $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
         $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true");
     };