diff options
Diffstat (limited to 'nixpkgs/nixos/tests/ldap.nix')
-rw-r--r-- | nixpkgs/nixos/tests/ldap.nix | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/nixpkgs/nixos/tests/ldap.nix b/nixpkgs/nixos/tests/ldap.nix new file mode 100644 index 000000000000..fe859876ed25 --- /dev/null +++ b/nixpkgs/nixos/tests/ldap.nix @@ -0,0 +1,395 @@ +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"; + dbAdminDn = "cn=admin,${dbSuffix}"; + dbAdminPwd = "admin-password"; + # NOTE: slappasswd -h "{SSHA}" -s '${dbAdminPwd}' + dbAdminPwdHash = "{SSHA}i7FopSzkFQMrHzDMB1vrtkI0rBnwouP8"; + ldapUser = "test-ldap-user"; + ldapUserId = 10000; + ldapUserPwd = "user-password"; + # NOTE: slappasswd -h "{SSHA}" -s '${ldapUserPwd}' + ldapUserPwdHash = "{SSHA}v12XICMZNGT6r2KJ26rIkN8Vvvp4QX6i"; + ldapGroup = "test-ldap-group"; + ldapGroupId = 10000; + + mkClient = useDaemon: + { lib, ... }: + { + virtualisation.memorySize = 256; + virtualisation.vlans = [ 1 ]; + security.pam.services.su.rootOK = lib.mkForce false; + users.ldap.enable = true; + users.ldap.daemon = { + enable = useDaemon; + rootpwmoddn = "cn=admin,${dbSuffix}"; + rootpwmodpwFile = "/etc/nslcd.rootpwmodpw"; + }; + users.ldap.loginPam = true; + users.ldap.nsswitch = true; + users.ldap.server = "ldap://server"; + users.ldap.base = "ou=posix,${dbSuffix}"; + users.ldap.bind = { + distinguishedName = "cn=admin,${dbSuffix}"; + passwordFile = "/etc/ldap/bind.password"; + }; + # NOTE: passwords stored in clear in Nix's store, but this is a test. + environment.etc."ldap/bind.password".source = pkgs.writeText "password" dbAdminPwd; + environment.etc."nslcd.rootpwmodpw".source = pkgs.writeText "rootpwmodpw" dbAdminPwd; + }; +in + +{ + name = "ldap"; + meta = with pkgs.stdenv.lib.maintainers; { + maintainers = [ montag451 ]; + }; + + nodes = { + + server = + { 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 = "/var/db/openldap"; + services.openldap.configDir = "/var/db/slapd"; + services.openldap.urlList = [ + "ldap:///" + "ldapi:///" + ]; + 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 + client2 = mkClient false; # use nss_ldap and pam_ldap + + }; + + testScript = '' + $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"); + + 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"); + + 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}"); + $client1->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'"); + $client2->succeed("test \"\$(id -u '${ldapUser}')\" -eq ${toString ldapUserId}"); + $client2->succeed("test \"\$(id -u -n '${ldapUser}')\" = '${ldapUser}'"); + $client2->succeed("test \"\$(id -g '${ldapUser}')\" -eq ${toString ldapGroupId}"); + $client2->succeed("test \"\$(id -g -n '${ldapUser}')\" = '${ldapGroup}'"); + }; + + subtest "PAM", sub { + $client1->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true"); + $client2->succeed("echo ${ldapUserPwd} | su -l '${ldapUser}' -c true"); + }; + ''; +}) |