1import ../../make-test-python.nix (
2 { pkgs, ... }:
3 let
4 DITRoot = "dc=example,dc=com";
5 realm = "EXAMPLE.COM";
6
7 krb5Package = pkgs.krb5.override { withLdap = true; };
8
9 # Password used by Kerberos services to bind to their identities
10 krbSrvPwd = "kerberos_service_password";
11 # Stash file read by Kerberos daemons containing the service password
12 # DO NOT DO THIS IN PRODUCTION! The stash file is a fundamental secret!
13 krbPwdStash = pkgs.runCommand "krb-pwd-stash" { } ''
14 for srv in cn=kadmin,${DITRoot} cn=kdc,${DITRoot}
15 do
16 echo -e "${krbSrvPwd}\n${krbSrvPwd}" | \
17 ${krb5Package}/bin/kdb5_ldap_util -r ${realm} stashsrvpw -f $out $srv 2>&1 > /dev/null
18 done
19 '';
20
21 # The LDAP schema for Kerberos 5 objects is part of the source distribution of Kerberos 5
22 krbLdapSchema = pkgs.runCommand "krb-ldap-schema" { } ''
23 tar -Oxf ${krb5Package.src} \
24 ${krb5Package.sourceRoot}/plugins/kdb/ldap/libkdb_ldap/kerberos.openldap.ldif > $out
25 '';
26
27 # Initial LDAP tree containing only the Kerberos services
28 ldapDIT = ''
29 dn: ${DITRoot}
30 objectClass: organization
31 objectClass: dcObject
32 dc: example
33 o: Example Company
34
35 dn: cn=kdc,${DITRoot}
36 objectClass: krbKdcService
37 objectClass: simpleSecurityObject
38 cn: kdc
39 userPassword: ${krbSrvPwd}
40
41 dn: cn=kadmin,${DITRoot}
42 objectClass: krbAdmService
43 objectClass: simpleSecurityObject
44 cn: kadmin
45 userPassword: ${krbSrvPwd}
46 '';
47
48 rootDnPwd = "ldap_root_password";
49 in
50 {
51 name = "kerberos_server-mit-ldap";
52
53 nodes.machine =
54 { pkgs, ... }:
55 {
56
57 services.openldap = {
58 enable = true;
59 urlList = [
60 "ldapi:///"
61 "ldap://"
62 ];
63 declarativeContents."${DITRoot}" = ldapDIT;
64 settings = {
65 children = {
66 "cn=schema".includes = [
67 "${pkgs.openldap}/etc/schema/core.ldif"
68 "${pkgs.openldap}/etc/schema/cosine.ldif"
69 "${pkgs.openldap}/etc/schema/inetorgperson.ldif"
70 "${pkgs.openldap}/etc/schema/nis.ldif"
71 "${krbLdapSchema}"
72 ];
73 "olcDatabase={0}config" = {
74 attrs = {
75 objectClass = [ "olcDatabaseConfig" ];
76 olcDatabase = "{0}config";
77 };
78 };
79 "olcDatabase={1}mdb" = {
80 attrs = {
81 objectClass = [
82 "olcDatabaseConfig"
83 "olcMdbConfig"
84 ];
85 olcDatabase = "{1}mdb";
86 olcDbDirectory = "/var/lib/openldap/db";
87 olcSuffix = DITRoot;
88 olcRootDN = "cn=root,${DITRoot}";
89 olcRootPW = rootDnPwd;
90 # A tiny but realistic ACL
91 olcAccess = [
92 ''
93 to attrs=userPassword
94 by anonymous auth
95 by * none''
96 ''
97 to dn.subtree="cn=${realm},cn=realms,${DITRoot}"
98 by dn.exact="cn=kdc,${DITRoot}" write
99 by dn.exact="cn=kadmin,${DITRoot}" write
100 by * none''
101 ''
102 to *
103 by * read''
104 ];
105 };
106 };
107 };
108 };
109 };
110
111 services.kerberos_server = {
112 enable = true;
113 settings = {
114 libdefaults.default_realm = realm;
115 realms = {
116 "${realm}" = {
117 acl = [
118 {
119 principal = "admin";
120 access = "all";
121 }
122 ];
123 };
124 };
125 dbmodules = {
126 "${realm}" = {
127 db_library = "kldap";
128 ldap_kerberos_container_dn = "cn=realms,${DITRoot}";
129 ldap_kdc_dn = "cn=kdc,${DITRoot}";
130 ldap_kadmind_dn = "cn=kadmin,${DITRoot}";
131 ldap_service_password_file = toString krbPwdStash;
132 ldap_servers = "ldapi:///";
133 };
134 };
135 };
136 };
137
138 security.krb5 = {
139 enable = true;
140 package = krb5Package;
141 settings = {
142 libdefaults = {
143 default_realm = realm;
144 };
145 realms = {
146 "${realm}" = {
147 admin_server = "machine";
148 kdc = "machine";
149 };
150 };
151 };
152 };
153
154 users.extraUsers.alice = {
155 isNormalUser = true;
156 };
157 };
158
159 testScript = ''
160 machine.wait_for_unit("openldap.service")
161
162 with subtest("realm container initialization"):
163 machine.succeed(
164 # Passing a master key directly avoids the need for a separate master key stash file
165 "kdb5_ldap_util -D cn=root,${DITRoot} create -w ${rootDnPwd} -s -P master_key",
166 )
167
168 # These units are bound to fail, as they are started before the directory service is ready
169 machine.execute("systemctl restart kadmind.service kdc.service")
170
171 with subtest("service bind"):
172 for unit in ["kadmind", "kdc"]:
173 machine.wait_for_unit(f"{unit}.service")
174
175 with subtest("administration principal initialization"):
176 machine.succeed("kadmin.local add_principal -pw admin_pw admin")
177
178 with subtest("user principal creation and kinit"):
179 machine.succeed(
180 "kadmin -p admin -w admin_pw addprinc -pw alice_pw alice",
181 "echo alice_pw | sudo -u alice kinit",
182 )
183 # Make extra sure that the user principal actually exists in the directory
184 machine.succeed(
185 "ldapsearch -x -D cn=root,${DITRoot} -w ${rootDnPwd} \
186 -b ${DITRoot} 'krbPrincipalName=alice@${realm}' | grep 'numEntries: 1'"
187 )
188 '';
189
190 meta.maintainers = [ pkgs.lib.maintainers.nessdoor ];
191 }
192)