diff --git a/nixos/tests/kerberos/default.nix b/nixos/tests/kerberos/default.nix index f2f1a438918c..e21d25c519b5 100644 --- a/nixos/tests/kerberos/default.nix +++ b/nixos/tests/kerberos/default.nix @@ -4,4 +4,5 @@ { mit = import ./mit.nix { inherit system pkgs; }; heimdal = import ./heimdal.nix { inherit system pkgs; }; + ldap = import ./ldap { inherit system pkgs; }; } diff --git a/nixos/tests/kerberos/ldap/default.nix b/nixos/tests/kerberos/ldap/default.nix new file mode 100644 index 000000000000..3c92f34b7f11 --- /dev/null +++ b/nixos/tests/kerberos/ldap/default.nix @@ -0,0 +1,7 @@ +{ + system ? builtins.currentSystem, + pkgs ? import ../../../.. { inherit system; }, +}: +{ + mit = import ./mit.nix { inherit system pkgs; }; +} diff --git a/nixos/tests/kerberos/ldap/mit.nix b/nixos/tests/kerberos/ldap/mit.nix new file mode 100644 index 000000000000..f5b6b47c8cef --- /dev/null +++ b/nixos/tests/kerberos/ldap/mit.nix @@ -0,0 +1,192 @@ +import ../../make-test-python.nix ( + { pkgs, ... }: + let + DITRoot = "dc=example,dc=com"; + realm = "EXAMPLE.COM"; + + krb5Package = pkgs.krb5.override { withLdap = true; }; + + # Password used by Kerberos services to bind to their identities + krbSrvPwd = "kerberos_service_password"; + # Stash file read by Kerberos daemons containing the service password + # DO NOT DO THIS IN PRODUCTION! The stash file is a fundamental secret! + krbPwdStash = pkgs.runCommand "krb-pwd-stash" { } '' + for srv in cn=kadmin,${DITRoot} cn=kdc,${DITRoot} + do + echo -e "${krbSrvPwd}\n${krbSrvPwd}" | \ + ${krb5Package}/bin/kdb5_ldap_util -r ${realm} stashsrvpw -f $out $srv 2>&1 > /dev/null + done + ''; + + # The LDAP schema for Kerberos 5 objects is part of the source distribution of Kerberos 5 + krbLdapSchema = pkgs.runCommand "krb-ldap-schema" { } '' + tar -Oxf ${krb5Package.src} \ + ${krb5Package.sourceRoot}/plugins/kdb/ldap/libkdb_ldap/kerberos.openldap.ldif > $out + ''; + + # Initial LDAP tree containing only the Kerberos services + ldapDIT = '' + dn: ${DITRoot} + objectClass: organization + objectClass: dcObject + dc: example + o: Example Company + + dn: cn=kdc,${DITRoot} + objectClass: krbKdcService + objectClass: simpleSecurityObject + cn: kdc + userPassword: ${krbSrvPwd} + + dn: cn=kadmin,${DITRoot} + objectClass: krbAdmService + objectClass: simpleSecurityObject + cn: kadmin + userPassword: ${krbSrvPwd} + ''; + + rootDnPwd = "ldap_root_password"; + in + { + name = "kerberos_server-mit-ldap"; + + nodes.machine = + { pkgs, ... }: + { + + services.openldap = { + enable = true; + urlList = [ + "ldapi:///" + "ldap://" + ]; + declarativeContents."${DITRoot}" = ldapDIT; + settings = { + children = { + "cn=schema".includes = [ + "${pkgs.openldap}/etc/schema/core.ldif" + "${pkgs.openldap}/etc/schema/cosine.ldif" + "${pkgs.openldap}/etc/schema/inetorgperson.ldif" + "${pkgs.openldap}/etc/schema/nis.ldif" + "${krbLdapSchema}" + ]; + "olcDatabase={0}config" = { + attrs = { + objectClass = [ "olcDatabaseConfig" ]; + olcDatabase = "{0}config"; + }; + }; + "olcDatabase={1}mdb" = { + attrs = { + objectClass = [ + "olcDatabaseConfig" + "olcMdbConfig" + ]; + olcDatabase = "{1}mdb"; + olcDbDirectory = "/var/lib/openldap/db"; + olcSuffix = DITRoot; + olcRootDN = "cn=root,${DITRoot}"; + olcRootPW = rootDnPwd; + # A tiny but realistic ACL + olcAccess = [ + '' + to attrs=userPassword + by anonymous auth + by * none'' + '' + to dn.subtree="cn=${realm},cn=realms,${DITRoot}" + by dn.exact="cn=kdc,${DITRoot}" write + by dn.exact="cn=kadmin,${DITRoot}" write + by * none'' + '' + to * + by * read'' + ]; + }; + }; + }; + }; + }; + + services.kerberos_server = { + enable = true; + settings = { + libdefaults.default_realm = realm; + realms = { + "${realm}" = { + acl = [ + { + principal = "admin"; + access = "all"; + } + ]; + }; + }; + dbmodules = { + "${realm}" = { + db_library = "kldap"; + ldap_kerberos_container_dn = "cn=realms,${DITRoot}"; + ldap_kdc_dn = "cn=kdc,${DITRoot}"; + ldap_kadmind_dn = "cn=kadmin,${DITRoot}"; + ldap_service_password_file = toString krbPwdStash; + ldap_servers = "ldapi:///"; + }; + }; + }; + }; + + security.krb5 = { + enable = true; + package = krb5Package; + settings = { + libdefaults = { + default_realm = realm; + }; + realms = { + "${realm}" = { + admin_server = "machine"; + kdc = "machine"; + }; + }; + }; + }; + + users.extraUsers.alice = { + isNormalUser = true; + }; + }; + + testScript = '' + machine.wait_for_unit("openldap.service") + + with subtest("realm container initialization"): + machine.succeed( + # Passing a master key directly avoids the need for a separate master key stash file + "kdb5_ldap_util -D cn=root,${DITRoot} create -w ${rootDnPwd} -s -P master_key", + ) + + # These units are bound to fail, as they are started before the directory service is ready + machine.execute("systemctl restart kadmind.service kdc.service") + + with subtest("service bind"): + for unit in ["kadmind", "kdc"]: + machine.wait_for_unit(f"{unit}.service") + + with subtest("administration principal initialization"): + machine.succeed("kadmin.local add_principal -pw admin_pw admin") + + with subtest("user principal creation and kinit"): + machine.succeed( + "kadmin -p admin -w admin_pw addprinc -pw alice_pw alice", + "echo alice_pw | sudo -u alice kinit", + ) + # Make extra sure that the user principal actually exists in the directory + machine.succeed( + "ldapsearch -x -D cn=root,${DITRoot} -w ${rootDnPwd} \ + -b ${DITRoot} 'krbPrincipalName=alice@${realm}' | grep 'numEntries: 1'" + ) + ''; + + meta.maintainers = [ pkgs.lib.maintainers.nessdoor ]; + } +) diff --git a/pkgs/by-name/op/openldap/package.nix b/pkgs/by-name/op/openldap/package.nix index 8ca98b386aa2..453cb1dfebde 100644 --- a/pkgs/by-name/op/openldap/package.nix +++ b/pkgs/by-name/op/openldap/package.nix @@ -129,6 +129,7 @@ stdenv.mkDerivation rec { passthru.tests = { inherit (nixosTests) openldap; + kerberosWithLdap = nixosTests.kerberos.ldap; }; meta = with lib; {