1 import ./make-test-python.nix (
4 certs = import ./common/acme/server/snakeoil-certs.nix;
5 serverDomain = certs.domain;
7 # copy certs to store to work around mount namespacing
8 certsPath = pkgs.runCommandNoCC "snakeoil-certs" { } ''
10 cp ${certs."${serverDomain}".cert} $out/snakeoil.crt
11 cp ${certs."${serverDomain}".key} $out/snakeoil.key
14 provisionAdminPassword = "very-strong-password-for-admin";
15 provisionIdmAdminPassword = "very-strong-password-for-idm-admin";
16 provisionIdmAdminPassword2 = "very-strong-alternative-password-for-idm-admin";
19 name = "kanidm-provisioning";
20 meta.maintainers = with pkgs.lib.maintainers; [ oddlama ];
26 package = pkgs.kanidm.withSecretProvisioning;
29 origin = "https://${serverDomain}";
30 domain = serverDomain;
31 bindaddress = "[::]:443";
32 ldapbindaddress = "[::1]:636";
33 tls_chain = "${certsPath}/snakeoil.crt";
34 tls_key = "${certsPath}/snakeoil.key";
36 # So we can check whether provisioning did what we wanted
39 uri = "https://${serverDomain}";
41 verify_hostnames = true;
45 specialisation.credentialProvision.configuration =
48 services.kanidm.provision = lib.mkForce {
50 adminPasswordFile = pkgs.writeText "admin-pw" provisionAdminPassword;
51 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword;
55 specialisation.changedCredential.configuration =
58 services.kanidm.provision = lib.mkForce {
60 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword2;
64 specialisation.addEntities.configuration =
67 services.kanidm.provision = lib.mkForce {
69 # Test whether credential recovery works without specific idmAdmin password
70 #idmAdminPasswordFile =
72 groups.supergroup1 = {
73 members = [ "testgroup1" ];
76 groups.testgroup1 = { };
79 displayName = "Test User";
80 legalName = "Jane Doe";
81 mailAddresses = [ "jane.doe@example.com" ];
89 displayName = "Powerful Test User";
90 legalName = "Ryouiki Tenkai";
91 groups = [ "service1-admin" ];
94 groups.service1-access = { };
95 groups.service1-admin = { };
96 systems.oauth2.service1 = {
97 displayName = "Service One";
98 originUrl = "https://one.example.com/";
99 originLanding = "https://one.example.com/landing";
100 basicSecretFile = pkgs.writeText "bs-service1" "very-strong-secret-for-service1";
101 scopeMaps.service1-access = [
106 supplementaryScopeMaps.service1-admin = [ "admin" ];
108 valuesByGroup.service1-admin = [ "admin" ];
112 systems.oauth2.service2 = {
113 displayName = "Service Two";
114 originUrl = "https://two.example.com/";
115 originLanding = "https://landing2.example.com/";
116 # Test not setting secret
118 allowInsecureClientDisablePkce = true;
119 preferShortUsername = true;
124 specialisation.changeAttributes.configuration =
127 services.kanidm.provision = lib.mkForce {
129 # Changing admin credentials at any time should not be a problem:
130 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword;
132 groups.supergroup1 = {
133 #members = ["testgroup1"];
136 groups.testgroup1 = { };
138 persons.testuser1 = {
139 displayName = "Test User (changed)";
140 legalName = "Jane Doe (changed)";
142 "jane.doe@example.com"
143 "second.doe@example.com"
151 persons.testuser2 = {
152 displayName = "Powerful Test User (changed)";
153 legalName = "Ryouiki Tenkai (changed)";
154 groups = [ "service1-admin" ];
157 groups.service1-access = { };
158 groups.service1-admin = { };
159 systems.oauth2.service1 = {
160 displayName = "Service One (changed)";
161 # multiple origin urls
163 "https://changed-one.example.com/"
164 "https://changed-one.example.org/"
166 originLanding = "https://changed-one.example.com/landing-changed";
167 basicSecretFile = pkgs.writeText "bs-service1" "changed-very-strong-secret-for-service1";
168 scopeMaps.service1-access = [
173 supplementaryScopeMaps.service1-admin = [ "adminchanged" ];
175 valuesByGroup.service1-admin = [ "adminchanged" ];
179 systems.oauth2.service2 = {
180 displayName = "Service Two (changed)";
181 originUrl = "https://changed-two.example.com/";
182 originLanding = "https://changed-landing2.example.com/";
183 # Test not setting secret
185 allowInsecureClientDisablePkce = false;
186 preferShortUsername = false;
191 specialisation.removeAttributes.configuration =
194 services.kanidm.provision = lib.mkForce {
196 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword;
198 groups.supergroup1 = { };
200 persons.testuser1 = {
201 displayName = "Test User (changed)";
204 persons.testuser2 = {
205 displayName = "Powerful Test User (changed)";
206 groups = [ "service1-admin" ];
209 groups.service1-access = { };
210 groups.service1-admin = { };
211 systems.oauth2.service1 = {
212 displayName = "Service One (changed)";
213 originUrl = "https://changed-one.example.com/";
214 originLanding = "https://changed-one.example.com/landing-changed";
215 basicSecretFile = pkgs.writeText "bs-service1" "changed-very-strong-secret-for-service1";
216 # Removing maps requires setting them to the empty list
217 scopeMaps.service1-access = [ ];
218 supplementaryScopeMaps.service1-admin = [ ];
221 systems.oauth2.service2 = {
222 displayName = "Service Two (changed)";
223 originUrl = "https://changed-two.example.com/";
224 originLanding = "https://changed-landing2.example.com/";
229 specialisation.removeEntities.configuration =
232 services.kanidm.provision = lib.mkForce {
234 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword;
238 security.pki.certificateFiles = [ certs.ca.cert ];
240 networking.hosts."::1" = [ serverDomain ];
241 networking.firewall.allowedTCPPorts = [ 443 ];
243 users.users.kanidm.shell = pkgs.bashInteractive;
245 environment.systemPackages = with pkgs; [
256 # We need access to the config file in the test script.
257 filteredConfig = pkgs.lib.converge (pkgs.lib.filterAttrsRecursive (
259 )) nodes.provision.services.kanidm.serverSettings;
260 serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig;
262 specialisations = "${nodes.provision.system.build.toplevel}/specialisation";
267 def assert_contains(haystack, needle):
268 if needle not in haystack:
269 print("The haystack that will cause the following exception is:")
273 raise Exception(f"Expected string '{needle}' was not found")
275 def assert_matches(haystack, expr):
276 if not re.search(expr, haystack):
277 print("The haystack that will cause the following exception is:")
281 raise Exception(f"Expected regex '{expr}' did not match")
283 def assert_lacks(haystack, needle):
284 if needle in haystack:
285 print("The haystack that will cause the following exception is:")
287 print(haystack, end="")
289 raise Exception(f"Unexpected string '{needle}' was found")
293 def provision_login(pw):
294 provision.wait_for_unit("kanidm.service")
295 provision.wait_until_succeeds("curl -Lsf https://${serverDomain} | grep Kanidm")
297 pw = provision.succeed("su - kanidm -c 'kanidmd recover-account -c ${serverConfigFile} idm_admin 2>&1 | rg -o \'[A-Za-z0-9]{48}\' '").strip().removeprefix("'").removesuffix("'")
298 out = provision.succeed(f"KANIDM_PASSWORD={pw} kanidm login -D idm_admin")
299 assert_contains(out, "Login Success for idm_admin")
301 with subtest("Test Provisioning - setup"):
302 provision_login(None)
303 provision.succeed("kanidm logout -D idm_admin")
305 with subtest("Test Provisioning - credentialProvision"):
306 provision.succeed('${specialisations}/credentialProvision/bin/switch-to-configuration test')
307 provision_login("${provisionIdmAdminPassword}")
309 # Test provisioned admin pw
310 out = provision.succeed("KANIDM_PASSWORD=${provisionAdminPassword} kanidm login -D admin")
311 assert_contains(out, "Login Success for admin")
312 provision.succeed("kanidm logout -D admin")
313 provision.succeed("kanidm logout -D idm_admin")
315 with subtest("Test Provisioning - changedCredential"):
316 provision.succeed('${specialisations}/changedCredential/bin/switch-to-configuration test')
317 provision_login("${provisionIdmAdminPassword2}")
318 provision.succeed("kanidm logout -D idm_admin")
320 with subtest("Test Provisioning - addEntities"):
321 provision.succeed('${specialisations}/addEntities/bin/switch-to-configuration test')
322 # Unspecified idm admin password
323 provision_login(None)
325 out = provision.succeed("kanidm group get testgroup1")
326 assert_contains(out, "name: testgroup1")
328 out = provision.succeed("kanidm group get supergroup1")
329 assert_contains(out, "name: supergroup1")
330 assert_contains(out, "member: testgroup1")
332 out = provision.succeed("kanidm person get testuser1")
333 assert_contains(out, "name: testuser1")
334 assert_contains(out, "displayname: Test User")
335 assert_contains(out, "legalname: Jane Doe")
336 assert_contains(out, "mail: jane.doe@example.com")
337 assert_contains(out, "memberof: testgroup1")
338 assert_contains(out, "memberof: service1-access")
340 out = provision.succeed("kanidm person get testuser2")
341 assert_contains(out, "name: testuser2")
342 assert_contains(out, "displayname: Powerful Test User")
343 assert_contains(out, "legalname: Ryouiki Tenkai")
344 assert_contains(out, "memberof: service1-admin")
345 assert_lacks(out, "mail:")
347 out = provision.succeed("kanidm group get service1-access")
348 assert_contains(out, "name: service1-access")
350 out = provision.succeed("kanidm group get service1-admin")
351 assert_contains(out, "name: service1-admin")
353 out = provision.succeed("kanidm system oauth2 get service1")
354 assert_contains(out, "name: service1")
355 assert_contains(out, "displayname: Service One")
356 assert_contains(out, "oauth2_rs_origin: https://one.example.com/")
357 assert_contains(out, "oauth2_rs_origin_landing: https://one.example.com/landing")
358 assert_matches(out, 'oauth2_rs_scope_map: service1-access.*{"email", "openid", "profile"}')
359 assert_matches(out, 'oauth2_rs_sup_scope_map: service1-admin.*{"admin"}')
360 assert_matches(out, 'oauth2_rs_claim_map: groups:.*"admin"')
362 out = provision.succeed("kanidm system oauth2 show-basic-secret service1")
363 assert_contains(out, "very-strong-secret-for-service1")
365 out = provision.succeed("kanidm system oauth2 get service2")
366 assert_contains(out, "name: service2")
367 assert_contains(out, "displayname: Service Two")
368 assert_contains(out, "oauth2_rs_origin: https://two.example.com/")
369 assert_contains(out, "oauth2_rs_origin_landing: https://landing2.example.com/")
370 assert_contains(out, "oauth2_allow_insecure_client_disable_pkce: true")
371 assert_contains(out, "oauth2_prefer_short_username: true")
373 provision.succeed("kanidm logout -D idm_admin")
375 with subtest("Test Provisioning - changeAttributes"):
376 provision.succeed('${specialisations}/changeAttributes/bin/switch-to-configuration test')
377 provision_login("${provisionIdmAdminPassword}")
379 out = provision.succeed("kanidm group get testgroup1")
380 assert_contains(out, "name: testgroup1")
382 out = provision.succeed("kanidm group get supergroup1")
383 assert_contains(out, "name: supergroup1")
384 assert_lacks(out, "member: testgroup1")
386 out = provision.succeed("kanidm person get testuser1")
387 assert_contains(out, "name: testuser1")
388 assert_contains(out, "displayname: Test User (changed)")
389 assert_contains(out, "legalname: Jane Doe (changed)")
390 assert_contains(out, "mail: jane.doe@example.com")
391 assert_contains(out, "mail: second.doe@example.com")
392 assert_lacks(out, "memberof: testgroup1")
393 assert_contains(out, "memberof: service1-access")
395 out = provision.succeed("kanidm person get testuser2")
396 assert_contains(out, "name: testuser2")
397 assert_contains(out, "displayname: Powerful Test User (changed)")
398 assert_contains(out, "legalname: Ryouiki Tenkai (changed)")
399 assert_contains(out, "memberof: service1-admin")
400 assert_lacks(out, "mail:")
402 out = provision.succeed("kanidm group get service1-access")
403 assert_contains(out, "name: service1-access")
405 out = provision.succeed("kanidm group get service1-admin")
406 assert_contains(out, "name: service1-admin")
408 out = provision.succeed("kanidm system oauth2 get service1")
409 assert_contains(out, "name: service1")
410 assert_contains(out, "displayname: Service One (changed)")
411 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.com/")
412 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.org/")
413 assert_contains(out, "oauth2_rs_origin_landing: https://changed-one.example.com/landing")
414 assert_matches(out, 'oauth2_rs_scope_map: service1-access.*{"email", "openid"}')
415 assert_matches(out, 'oauth2_rs_sup_scope_map: service1-admin.*{"adminchanged"}')
416 assert_matches(out, 'oauth2_rs_claim_map: groups:.*"adminchanged"')
418 out = provision.succeed("kanidm system oauth2 show-basic-secret service1")
419 assert_contains(out, "changed-very-strong-secret-for-service1")
421 out = provision.succeed("kanidm system oauth2 get service2")
422 assert_contains(out, "name: service2")
423 assert_contains(out, "displayname: Service Two (changed)")
424 assert_contains(out, "oauth2_rs_origin: https://changed-two.example.com/")
425 assert_contains(out, "oauth2_rs_origin_landing: https://changed-landing2.example.com/")
426 assert_lacks(out, "oauth2_allow_insecure_client_disable_pkce: true")
427 assert_lacks(out, "oauth2_prefer_short_username: true")
429 provision.succeed("kanidm logout -D idm_admin")
431 with subtest("Test Provisioning - removeAttributes"):
432 provision.succeed('${specialisations}/removeAttributes/bin/switch-to-configuration test')
433 provision_login("${provisionIdmAdminPassword}")
435 out = provision.succeed("kanidm group get testgroup1")
436 assert_lacks(out, "name: testgroup1")
438 out = provision.succeed("kanidm group get supergroup1")
439 assert_contains(out, "name: supergroup1")
440 assert_lacks(out, "member: testgroup1")
442 out = provision.succeed("kanidm person get testuser1")
443 assert_contains(out, "name: testuser1")
444 assert_contains(out, "displayname: Test User (changed)")
445 assert_lacks(out, "legalname: Jane Doe (changed)")
446 assert_lacks(out, "mail: jane.doe@example.com")
447 assert_lacks(out, "mail: second.doe@example.com")
448 assert_lacks(out, "memberof: testgroup1")
449 assert_lacks(out, "memberof: service1-access")
451 out = provision.succeed("kanidm person get testuser2")
452 assert_contains(out, "name: testuser2")
453 assert_contains(out, "displayname: Powerful Test User (changed)")
454 assert_lacks(out, "legalname: Ryouiki Tenkai (changed)")
455 assert_contains(out, "memberof: service1-admin")
456 assert_lacks(out, "mail:")
458 out = provision.succeed("kanidm group get service1-access")
459 assert_contains(out, "name: service1-access")
461 out = provision.succeed("kanidm group get service1-admin")
462 assert_contains(out, "name: service1-admin")
464 out = provision.succeed("kanidm system oauth2 get service1")
465 assert_contains(out, "name: service1")
466 assert_contains(out, "displayname: Service One (changed)")
467 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.com/")
468 assert_lacks(out, "oauth2_rs_origin: https://changed-one.example.org/")
469 assert_contains(out, "oauth2_rs_origin_landing: https://changed-one.example.com/landing")
470 assert_lacks(out, "oauth2_rs_scope_map")
471 assert_lacks(out, "oauth2_rs_sup_scope_map")
472 assert_lacks(out, "oauth2_rs_claim_map")
474 out = provision.succeed("kanidm system oauth2 show-basic-secret service1")
475 assert_contains(out, "changed-very-strong-secret-for-service1")
477 out = provision.succeed("kanidm system oauth2 get service2")
478 assert_contains(out, "name: service2")
479 assert_contains(out, "displayname: Service Two (changed)")
480 assert_contains(out, "oauth2_rs_origin: https://changed-two.example.com/")
481 assert_contains(out, "oauth2_rs_origin_landing: https://changed-landing2.example.com/")
482 assert_lacks(out, "oauth2_allow_insecure_client_disable_pkce: true")
483 assert_lacks(out, "oauth2_prefer_short_username: true")
485 provision.succeed("kanidm logout -D idm_admin")
487 with subtest("Test Provisioning - removeEntities"):
488 provision.succeed('${specialisations}/removeEntities/bin/switch-to-configuration test')
489 provision_login("${provisionIdmAdminPassword}")
491 out = provision.succeed("kanidm group get testgroup1")
492 assert_lacks(out, "name: testgroup1")
494 out = provision.succeed("kanidm group get supergroup1")
495 assert_lacks(out, "name: supergroup1")
497 out = provision.succeed("kanidm person get testuser1")
498 assert_lacks(out, "name: testuser1")
500 out = provision.succeed("kanidm person get testuser2")
501 assert_lacks(out, "name: testuser2")
503 out = provision.succeed("kanidm group get service1-access")
504 assert_lacks(out, "name: service1-access")
506 out = provision.succeed("kanidm group get service1-admin")
507 assert_lacks(out, "name: service1-admin")
509 out = provision.succeed("kanidm system oauth2 get service1")
510 assert_lacks(out, "name: service1")
512 out = provision.succeed("kanidm system oauth2 get service2")
513 assert_lacks(out, "name: service2")
515 provision.succeed("kanidm logout -D idm_admin")