at 25.11-pre 23 kB view raw
1import ./make-test-python.nix ( 2 { pkgs, ... }: 3 let 4 certs = import ./common/acme/server/snakeoil-certs.nix; 5 serverDomain = certs.domain; 6 7 # copy certs to store to work around mount namespacing 8 certsPath = pkgs.runCommandNoCC "snakeoil-certs" { } '' 9 mkdir $out 10 cp ${certs."${serverDomain}".cert} $out/snakeoil.crt 11 cp ${certs."${serverDomain}".key} $out/snakeoil.key 12 ''; 13 14 provisionAdminPassword = "very-strong-password-for-admin"; 15 provisionIdmAdminPassword = "very-strong-password-for-idm-admin"; 16 provisionIdmAdminPassword2 = "very-strong-alternative-password-for-idm-admin"; 17 in 18 { 19 name = "kanidm-provisioning"; 20 meta.maintainers = with pkgs.lib.maintainers; [ oddlama ]; 21 22 nodes.provision = 23 { pkgs, lib, ... }: 24 { 25 services.kanidm = { 26 package = pkgs.kanidmWithSecretProvisioning; 27 enableServer = true; 28 serverSettings = { 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"; 35 }; 36 # So we can check whether provisioning did what we wanted 37 enableClient = true; 38 clientSettings = { 39 uri = "https://${serverDomain}"; 40 verify_ca = true; 41 verify_hostnames = true; 42 }; 43 }; 44 45 specialisation.credentialProvision.configuration = 46 { ... }: 47 { 48 services.kanidm.provision = lib.mkForce { 49 enable = true; 50 adminPasswordFile = pkgs.writeText "admin-pw" provisionAdminPassword; 51 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword; 52 }; 53 }; 54 55 specialisation.changedCredential.configuration = 56 { ... }: 57 { 58 services.kanidm.provision = lib.mkForce { 59 enable = true; 60 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword2; 61 }; 62 }; 63 64 specialisation.addEntities.configuration = 65 { ... }: 66 { 67 services.kanidm.provision = lib.mkForce { 68 enable = true; 69 # Test whether credential recovery works without specific idmAdmin password 70 #idmAdminPasswordFile = 71 72 groups.supergroup1 = { 73 members = [ "testgroup1" ]; 74 }; 75 76 groups.testgroup1 = { }; 77 78 persons.testuser1 = { 79 displayName = "Test User"; 80 legalName = "Jane Doe"; 81 mailAddresses = [ "jane.doe@example.com" ]; 82 groups = [ 83 "testgroup1" 84 "service1-access" 85 ]; 86 }; 87 88 persons.testuser2 = { 89 displayName = "Powerful Test User"; 90 legalName = "Ryouiki Tenkai"; 91 groups = [ "service1-admin" ]; 92 }; 93 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 = [ 102 "openid" 103 "email" 104 "profile" 105 ]; 106 supplementaryScopeMaps.service1-admin = [ "admin" ]; 107 claimMaps.groups = { 108 valuesByGroup.service1-admin = [ "admin" ]; 109 }; 110 }; 111 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 117 # basicSecretFile = 118 allowInsecureClientDisablePkce = true; 119 preferShortUsername = true; 120 }; 121 }; 122 }; 123 124 specialisation.changeAttributes.configuration = 125 { ... }: 126 { 127 services.kanidm.provision = lib.mkForce { 128 enable = true; 129 # Changing admin credentials at any time should not be a problem: 130 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword; 131 132 groups.supergroup1 = { 133 #members = ["testgroup1"]; 134 }; 135 136 groups.testgroup1 = { }; 137 138 persons.testuser1 = { 139 displayName = "Test User (changed)"; 140 legalName = "Jane Doe (changed)"; 141 mailAddresses = [ 142 "jane.doe@example.com" 143 "second.doe@example.com" 144 ]; 145 groups = [ 146 #"testgroup1" 147 "service1-access" 148 ]; 149 }; 150 151 persons.testuser2 = { 152 displayName = "Powerful Test User (changed)"; 153 legalName = "Ryouiki Tenkai (changed)"; 154 groups = [ "service1-admin" ]; 155 }; 156 157 groups.service1-access = { }; 158 groups.service1-admin = { }; 159 systems.oauth2.service1 = { 160 displayName = "Service One (changed)"; 161 # multiple origin urls 162 originUrl = [ 163 "https://changed-one.example.com/" 164 "https://changed-one.example.org/" 165 ]; 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 = [ 169 "openid" 170 "email" 171 #"profile" 172 ]; 173 supplementaryScopeMaps.service1-admin = [ "adminchanged" ]; 174 claimMaps.groups = { 175 valuesByGroup.service1-admin = [ "adminchanged" ]; 176 }; 177 }; 178 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 184 # basicSecretFile = 185 allowInsecureClientDisablePkce = false; 186 preferShortUsername = false; 187 }; 188 }; 189 }; 190 191 specialisation.removeAttributes.configuration = 192 { ... }: 193 { 194 services.kanidm.provision = lib.mkForce { 195 enable = true; 196 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword; 197 198 groups.supergroup1 = { }; 199 200 persons.testuser1 = { 201 displayName = "Test User (changed)"; 202 }; 203 204 persons.testuser2 = { 205 displayName = "Powerful Test User (changed)"; 206 groups = [ "service1-admin" ]; 207 }; 208 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 = [ ]; 219 }; 220 221 systems.oauth2.service2 = { 222 displayName = "Service Two (changed)"; 223 originUrl = "https://changed-two.example.com/"; 224 originLanding = "https://changed-landing2.example.com/"; 225 }; 226 }; 227 }; 228 229 specialisation.removeEntities.configuration = 230 { ... }: 231 { 232 services.kanidm.provision = lib.mkForce { 233 enable = true; 234 idmAdminPasswordFile = pkgs.writeText "idm-admin-pw" provisionIdmAdminPassword; 235 }; 236 }; 237 238 security.pki.certificateFiles = [ certs.ca.cert ]; 239 240 networking.hosts."::1" = [ serverDomain ]; 241 networking.firewall.allowedTCPPorts = [ 443 ]; 242 243 users.users.kanidm.shell = pkgs.bashInteractive; 244 245 environment.systemPackages = with pkgs; [ 246 kanidm 247 openldap 248 ripgrep 249 jq 250 ]; 251 }; 252 253 testScript = 254 { nodes, ... }: 255 let 256 # We need access to the config file in the test script. 257 filteredConfig = pkgs.lib.converge (pkgs.lib.filterAttrsRecursive ( 258 _: v: v != null 259 )) nodes.provision.services.kanidm.serverSettings; 260 serverConfigFile = (pkgs.formats.toml { }).generate "server.toml" filteredConfig; 261 262 specialisations = "${nodes.provision.system.build.toplevel}/specialisation"; 263 in 264 '' 265 import re 266 267 def assert_contains(haystack, needle): 268 if needle not in haystack: 269 print("The haystack that will cause the following exception is:") 270 print("---") 271 print(haystack) 272 print("---") 273 raise Exception(f"Expected string '{needle}' was not found") 274 275 def assert_matches(haystack, expr): 276 if not re.search(expr, haystack): 277 print("The haystack that will cause the following exception is:") 278 print("---") 279 print(haystack) 280 print("---") 281 raise Exception(f"Expected regex '{expr}' did not match") 282 283 def assert_lacks(haystack, needle): 284 if needle in haystack: 285 print("The haystack that will cause the following exception is:") 286 print("---") 287 print(haystack, end="") 288 print("---") 289 raise Exception(f"Unexpected string '{needle}' was found") 290 291 provision.start() 292 293 def provision_login(pw): 294 provision.wait_for_unit("kanidm.service") 295 provision.wait_until_succeeds("curl -Lsf https://${serverDomain} | grep Kanidm") 296 if pw is None: 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") 300 301 with subtest("Test Provisioning - setup"): 302 provision_login(None) 303 provision.succeed("kanidm logout -D idm_admin") 304 305 with subtest("Test Provisioning - credentialProvision"): 306 provision.succeed('${specialisations}/credentialProvision/bin/switch-to-configuration test') 307 provision_login("${provisionIdmAdminPassword}") 308 309 # Make sure neither password is logged 310 provision.fail("journalctl --since -10m --unit kanidm.service --grep '${provisionAdminPassword}'") 311 provision.fail("journalctl --since -10m --unit kanidm.service --grep '${provisionIdmAdminPassword}'") 312 313 # Test provisioned admin pw 314 out = provision.succeed("KANIDM_PASSWORD=${provisionAdminPassword} kanidm login -D admin") 315 assert_contains(out, "Login Success for admin") 316 provision.succeed("kanidm logout -D admin") 317 provision.succeed("kanidm logout -D idm_admin") 318 319 with subtest("Test Provisioning - changedCredential"): 320 provision.succeed('${specialisations}/changedCredential/bin/switch-to-configuration test') 321 provision_login("${provisionIdmAdminPassword2}") 322 provision.succeed("kanidm logout -D idm_admin") 323 324 with subtest("Test Provisioning - addEntities"): 325 provision.succeed('${specialisations}/addEntities/bin/switch-to-configuration test') 326 # Unspecified idm admin password 327 provision_login(None) 328 329 out = provision.succeed("kanidm group get testgroup1") 330 assert_contains(out, "name: testgroup1") 331 332 out = provision.succeed("kanidm group get supergroup1") 333 assert_contains(out, "name: supergroup1") 334 assert_contains(out, "member: testgroup1") 335 336 out = provision.succeed("kanidm person get testuser1") 337 assert_contains(out, "name: testuser1") 338 assert_contains(out, "displayname: Test User") 339 assert_contains(out, "legalname: Jane Doe") 340 assert_contains(out, "mail: jane.doe@example.com") 341 assert_contains(out, "memberof: testgroup1") 342 assert_contains(out, "memberof: service1-access") 343 344 out = provision.succeed("kanidm person get testuser2") 345 assert_contains(out, "name: testuser2") 346 assert_contains(out, "displayname: Powerful Test User") 347 assert_contains(out, "legalname: Ryouiki Tenkai") 348 assert_contains(out, "memberof: service1-admin") 349 assert_lacks(out, "mail:") 350 351 out = provision.succeed("kanidm group get service1-access") 352 assert_contains(out, "name: service1-access") 353 354 out = provision.succeed("kanidm group get service1-admin") 355 assert_contains(out, "name: service1-admin") 356 357 out = provision.succeed("kanidm system oauth2 get service1") 358 assert_contains(out, "name: service1") 359 assert_contains(out, "displayname: Service One") 360 assert_contains(out, "oauth2_rs_origin: https://one.example.com/") 361 assert_contains(out, "oauth2_rs_origin_landing: https://one.example.com/landing") 362 assert_matches(out, 'oauth2_rs_scope_map: service1-access.*{"email", "openid", "profile"}') 363 assert_matches(out, 'oauth2_rs_sup_scope_map: service1-admin.*{"admin"}') 364 assert_matches(out, 'oauth2_rs_claim_map: groups:.*"admin"') 365 366 out = provision.succeed("kanidm system oauth2 show-basic-secret service1") 367 assert_contains(out, "very-strong-secret-for-service1") 368 369 out = provision.succeed("kanidm system oauth2 get service2") 370 assert_contains(out, "name: service2") 371 assert_contains(out, "displayname: Service Two") 372 assert_contains(out, "oauth2_rs_origin: https://two.example.com/") 373 assert_contains(out, "oauth2_rs_origin_landing: https://landing2.example.com/") 374 assert_contains(out, "oauth2_allow_insecure_client_disable_pkce: true") 375 assert_contains(out, "oauth2_prefer_short_username: true") 376 377 provision.succeed("kanidm logout -D idm_admin") 378 379 with subtest("Test Provisioning - changeAttributes"): 380 provision.succeed('${specialisations}/changeAttributes/bin/switch-to-configuration test') 381 provision_login("${provisionIdmAdminPassword}") 382 383 out = provision.succeed("kanidm group get testgroup1") 384 assert_contains(out, "name: testgroup1") 385 386 out = provision.succeed("kanidm group get supergroup1") 387 assert_contains(out, "name: supergroup1") 388 assert_lacks(out, "member: testgroup1") 389 390 out = provision.succeed("kanidm person get testuser1") 391 assert_contains(out, "name: testuser1") 392 assert_contains(out, "displayname: Test User (changed)") 393 assert_contains(out, "legalname: Jane Doe (changed)") 394 assert_contains(out, "mail: jane.doe@example.com") 395 assert_contains(out, "mail: second.doe@example.com") 396 assert_lacks(out, "memberof: testgroup1") 397 assert_contains(out, "memberof: service1-access") 398 399 out = provision.succeed("kanidm person get testuser2") 400 assert_contains(out, "name: testuser2") 401 assert_contains(out, "displayname: Powerful Test User (changed)") 402 assert_contains(out, "legalname: Ryouiki Tenkai (changed)") 403 assert_contains(out, "memberof: service1-admin") 404 assert_lacks(out, "mail:") 405 406 out = provision.succeed("kanidm group get service1-access") 407 assert_contains(out, "name: service1-access") 408 409 out = provision.succeed("kanidm group get service1-admin") 410 assert_contains(out, "name: service1-admin") 411 412 out = provision.succeed("kanidm system oauth2 get service1") 413 assert_contains(out, "name: service1") 414 assert_contains(out, "displayname: Service One (changed)") 415 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.com/") 416 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.org/") 417 assert_contains(out, "oauth2_rs_origin_landing: https://changed-one.example.com/landing") 418 assert_matches(out, 'oauth2_rs_scope_map: service1-access.*{"email", "openid"}') 419 assert_matches(out, 'oauth2_rs_sup_scope_map: service1-admin.*{"adminchanged"}') 420 assert_matches(out, 'oauth2_rs_claim_map: groups:.*"adminchanged"') 421 422 out = provision.succeed("kanidm system oauth2 show-basic-secret service1") 423 assert_contains(out, "changed-very-strong-secret-for-service1") 424 425 out = provision.succeed("kanidm system oauth2 get service2") 426 assert_contains(out, "name: service2") 427 assert_contains(out, "displayname: Service Two (changed)") 428 assert_contains(out, "oauth2_rs_origin: https://changed-two.example.com/") 429 assert_contains(out, "oauth2_rs_origin_landing: https://changed-landing2.example.com/") 430 assert_lacks(out, "oauth2_allow_insecure_client_disable_pkce: true") 431 assert_lacks(out, "oauth2_prefer_short_username: true") 432 433 provision.succeed("kanidm logout -D idm_admin") 434 435 with subtest("Test Provisioning - removeAttributes"): 436 provision.succeed('${specialisations}/removeAttributes/bin/switch-to-configuration test') 437 provision_login("${provisionIdmAdminPassword}") 438 439 out = provision.succeed("kanidm group get testgroup1") 440 assert_lacks(out, "name: testgroup1") 441 442 out = provision.succeed("kanidm group get supergroup1") 443 assert_contains(out, "name: supergroup1") 444 assert_lacks(out, "member: testgroup1") 445 446 out = provision.succeed("kanidm person get testuser1") 447 assert_contains(out, "name: testuser1") 448 assert_contains(out, "displayname: Test User (changed)") 449 assert_lacks(out, "legalname: Jane Doe (changed)") 450 assert_lacks(out, "mail: jane.doe@example.com") 451 assert_lacks(out, "mail: second.doe@example.com") 452 assert_lacks(out, "memberof: testgroup1") 453 assert_lacks(out, "memberof: service1-access") 454 455 out = provision.succeed("kanidm person get testuser2") 456 assert_contains(out, "name: testuser2") 457 assert_contains(out, "displayname: Powerful Test User (changed)") 458 assert_lacks(out, "legalname: Ryouiki Tenkai (changed)") 459 assert_contains(out, "memberof: service1-admin") 460 assert_lacks(out, "mail:") 461 462 out = provision.succeed("kanidm group get service1-access") 463 assert_contains(out, "name: service1-access") 464 465 out = provision.succeed("kanidm group get service1-admin") 466 assert_contains(out, "name: service1-admin") 467 468 out = provision.succeed("kanidm system oauth2 get service1") 469 assert_contains(out, "name: service1") 470 assert_contains(out, "displayname: Service One (changed)") 471 assert_contains(out, "oauth2_rs_origin: https://changed-one.example.com/") 472 assert_lacks(out, "oauth2_rs_origin: https://changed-one.example.org/") 473 assert_contains(out, "oauth2_rs_origin_landing: https://changed-one.example.com/landing") 474 assert_lacks(out, "oauth2_rs_scope_map") 475 assert_lacks(out, "oauth2_rs_sup_scope_map") 476 assert_lacks(out, "oauth2_rs_claim_map") 477 478 out = provision.succeed("kanidm system oauth2 show-basic-secret service1") 479 assert_contains(out, "changed-very-strong-secret-for-service1") 480 481 out = provision.succeed("kanidm system oauth2 get service2") 482 assert_contains(out, "name: service2") 483 assert_contains(out, "displayname: Service Two (changed)") 484 assert_contains(out, "oauth2_rs_origin: https://changed-two.example.com/") 485 assert_contains(out, "oauth2_rs_origin_landing: https://changed-landing2.example.com/") 486 assert_lacks(out, "oauth2_allow_insecure_client_disable_pkce: true") 487 assert_lacks(out, "oauth2_prefer_short_username: true") 488 489 provision.succeed("kanidm logout -D idm_admin") 490 491 with subtest("Test Provisioning - removeEntities"): 492 provision.succeed('${specialisations}/removeEntities/bin/switch-to-configuration test') 493 provision_login("${provisionIdmAdminPassword}") 494 495 out = provision.succeed("kanidm group get testgroup1") 496 assert_lacks(out, "name: testgroup1") 497 498 out = provision.succeed("kanidm group get supergroup1") 499 assert_lacks(out, "name: supergroup1") 500 501 out = provision.succeed("kanidm person get testuser1") 502 assert_lacks(out, "name: testuser1") 503 504 out = provision.succeed("kanidm person get testuser2") 505 assert_lacks(out, "name: testuser2") 506 507 out = provision.succeed("kanidm group get service1-access") 508 assert_lacks(out, "name: service1-access") 509 510 out = provision.succeed("kanidm group get service1-admin") 511 assert_lacks(out, "name: service1-admin") 512 513 out = provision.succeed("kanidm system oauth2 get service1") 514 assert_lacks(out, "name: service1") 515 516 out = provision.succeed("kanidm system oauth2 get service2") 517 assert_lacks(out, "name: service2") 518 519 provision.succeed("kanidm logout -D idm_admin") 520 ''; 521 } 522)