From f96b31a8a9fc0d8c4d62a29ca23a7f152a33c8b6 Mon Sep 17 00:00:00 2001 From: b12f Date: Fri, 23 Aug 2024 22:50:22 +0200 Subject: [PATCH 01/21] tests: add working test for website --- flake.nix | 11 +++++++++++ modules/core/nix.nix | 4 +++- tests/website.nix | 23 ++++++++++++++++------- 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/flake.nix b/flake.nix index b5473e1..063589c 100644 --- a/flake.nix +++ b/flake.nix @@ -65,6 +65,7 @@ system, pkgs, config, + lib, ... }: { @@ -77,6 +78,16 @@ unstable = import inputs.unstable { inherit system; }; master = import inputs.master { inherit system; }; }; + + packages = let + nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; + in builtins.listToAttrs ( + map (x: { + name = "test-${lib.strings.removeSuffix ".nix" x}"; + value = nixos-lib.runTest (import (./tests + "/${x}") { inherit self; inherit pkgs; inherit lib; inherit config; }); + }) (builtins.attrNames (builtins.readDir ./tests)) + ); + devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ deploy-rs diff --git a/modules/core/nix.nix b/modules/core/nix.nix index 338cdd1..936be64 100644 --- a/modules/core/nix.nix +++ b/modules/core/nix.nix @@ -6,7 +6,9 @@ ... }: { - nixpkgs.config.allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ ]; + nixpkgs.config = lib.mkDefault { + allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ ]; + }; nix = { # Use default version alias for nix package diff --git a/tests/website.nix b/tests/website.nix index dc33aff..10e96a5 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -8,17 +8,26 @@ { name = "website"; - nodes.nachtigall-test = self.nixosConfigurations.nachtigall-test; - - node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; hostPkgs = pkgs; + node.pkgs = pkgs; + node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; + + nodes.nachtigall-test = { + imports = [ + self.nixosModules.home-manager + self.nixosModules.core + self.nixosModules.nginx + self.nixosModules.nginx-website + ]; + }; + enableOCR = true; testScript = '' - machine.wait_for_unit("system.slice") - machine.succeed("ping 127.0.0.1 -c 2") - machine.wait_for_unit("nginx.service") - machine.succeed("curl -H 'Host:pub.solar' http://127.0.0.1/") + nachtigall_test.wait_for_unit("system.slice") + nachtigall_test.succeed("ping 127.0.0.1 -c 2") + nachtigall_test.wait_for_unit("nginx.service") + nachtigall_test.succeed("curl -H 'Host:pub.solar' http://127.0.0.1/") ''; } -- 2.44.2 From eb337ddd478003150d15876fdf4572ea4a562eff Mon Sep 17 00:00:00 2001 From: b12f Date: Sat, 24 Aug 2024 16:21:48 +0200 Subject: [PATCH 02/21] tests/keycloak: certificate fetching with step-ca works --- flake.nix | 13 +- hosts/default.nix | 4 + hosts/nachtigall/configuration.nix | 16 +- modules/backups/default.nix | 253 ++++++++++++++++++ modules/keycloak/default.nix | 29 +- tests/keycloak.nix | 79 ++++++ tests/support/ca.nix | 48 ++++ tests/support/client.nix | 26 ++ tests/support/global.nix | 26 ++ tests/support/step/certs/intermediate_ca.crt | 13 + tests/support/step/certs/root_ca.crt | 12 + tests/support/step/config/ca.json | 46 ++++ tests/support/step/config/defaults.json | 6 + .../support/step/secrets/intermediate_ca_key | 8 + tests/support/step/secrets/root_ca_key | 8 + tests/website.nix | 7 +- 16 files changed, 569 insertions(+), 25 deletions(-) create mode 100644 modules/backups/default.nix create mode 100644 tests/keycloak.nix create mode 100644 tests/support/ca.nix create mode 100644 tests/support/client.nix create mode 100644 tests/support/global.nix create mode 100644 tests/support/step/certs/intermediate_ca.crt create mode 100644 tests/support/step/certs/root_ca.crt create mode 100644 tests/support/step/config/ca.json create mode 100644 tests/support/step/config/defaults.json create mode 100644 tests/support/step/secrets/intermediate_ca_key create mode 100644 tests/support/step/secrets/root_ca_key diff --git a/flake.nix b/flake.nix index 063589c..b22f325 100644 --- a/flake.nix +++ b/flake.nix @@ -81,12 +81,12 @@ packages = let nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; - in builtins.listToAttrs ( - map (x: { - name = "test-${lib.strings.removeSuffix ".nix" x}"; - value = nixos-lib.runTest (import (./tests + "/${x}") { inherit self; inherit pkgs; inherit lib; inherit config; }); - }) (builtins.attrNames (builtins.readDir ./tests)) - ); + testDir = builtins.attrNames (builtins.readDir ./tests); + testFiles = builtins.filter (n: builtins.match "^.*.nix$" n != null) testDir; + in builtins.listToAttrs (map (x: { + name = "test-${lib.strings.removeSuffix ".nix" x}"; + value = nixos-lib.runTest (import (./tests + "/${x}") { inherit self; inherit pkgs; inherit lib; inherit config; }); + }) testFiles); devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ @@ -106,6 +106,7 @@ jq ]; }; + devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; }; }; diff --git a/hosts/default.nix b/hosts/default.nix index af64b84..c8aaf1c 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -11,6 +11,7 @@ self.nixosModules.unlock-zfs-on-boot self.nixosModules.core self.nixosModules.docker + self.nixosModules.backups self.nixosModules.nginx self.nixosModules.collabora @@ -49,6 +50,7 @@ ./flora-6 self.nixosModules.overlays self.nixosModules.core + self.nixosModules.backups self.nixosModules.keycloak self.nixosModules.caddy @@ -68,6 +70,7 @@ self.nixosModules.overlays self.nixosModules.unlock-zfs-on-boot self.nixosModules.core + self.nixosModules.backups self.nixosModules.mail self.nixosModules.prometheus-exporters self.nixosModules.promtail @@ -83,6 +86,7 @@ ./tankstelle self.nixosModules.overlays self.nixosModules.core + self.nixosModules.backups self.nixosModules.prometheus-exporters self.nixosModules.promtail ]; diff --git a/hosts/nachtigall/configuration.nix b/hosts/nachtigall/configuration.nix index 324755e..bc7e76d 100644 --- a/hosts/nachtigall/configuration.nix +++ b/hosts/nachtigall/configuration.nix @@ -48,9 +48,21 @@ owner = "root"; }; - pub-solar-os.auth.enable = true; + age.secrets.keycloak-database-password = { + file = "${flake.self}/secrets/keycloak-database-password.age"; + mode = "600"; + #owner = "keycloak"; + }; - nixpkgs.config.permittedInsecurePackages = [ "keycloak-23.0.6" ]; + pub-solar-os.auth = { + enable = true; + database-password-file = config.age.secrets.keycloak-database-password.path; + }; + + pub-solar-os.backups.stores.storagebox = { + passwordFile = config.age.secrets."restic-repo-storagebox".path; + repository = "sftp:u377325@u377325.your-storagebox.de:/backups"; + }; # This value determines the NixOS release with which your system is to be # compatible, in order to avoid breaking some software such as database diff --git a/modules/backups/default.nix b/modules/backups/default.nix new file mode 100644 index 0000000..50ffef8 --- /dev/null +++ b/modules/backups/default.nix @@ -0,0 +1,253 @@ +{ + flake, + config, + lib, + pkgs, + ... +}: +{ + options.pub-solar-os.backups = { + stores = with lib; mkOption { + description = '' + Periodic backups to create with Restic. + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + passwordFile = mkOption { + type = types.str; + description = '' + Read the repository password from a file. + ''; + example = "/etc/nixos/restic-password"; + }; + + repository = mkOption { + type = with types; nullOr str; + default = null; + description = '' + repository to backup to. + ''; + example = "sftp:backup@192.168.1.100:/backups/${name}"; + }; + }; + })); + + default = { }; + example = { + remotebackup = { + repository = "sftp:backup@host:/backups/home"; + passwordFile = "/etc/nixos/secrets/restic-password"; + }; + }; + }; + + backups = with lib; mkOption { + description = '' + Periodic backups to create with Restic. + ''; + type = types.attrsOf (types.submodule ({ name, ... }: { + options = { + paths = mkOption { + # This is nullable for legacy reasons only. We should consider making it a pure listOf + # after some time has passed since this comment was added. + type = types.nullOr (types.listOf types.str); + default = [ ]; + description = '' + Which paths to backup, in addition to ones specified via + `dynamicFilesFrom`. If null or an empty array and + `dynamicFilesFrom` is also null, no backup command will be run. + This can be used to create a prune-only job. + ''; + example = [ + "/var/lib/postgresql" + "/home/user/backup" + ]; + }; + + exclude = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Patterns to exclude when backing up. See + https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for + details on syntax. + ''; + example = [ + "/var/cache" + "/home/*/.cache" + ".git" + ]; + }; + + timerConfig = mkOption { + type = types.nullOr (types.attrsOf unitOption); + default = { + OnCalendar = "daily"; + Persistent = true; + }; + description = '' + When to run the backup. See {manpage}`systemd.timer(5)` for + details. If null no timer is created and the backup will only + run when explicitly started. + ''; + example = { + OnCalendar = "00:05"; + RandomizedDelaySec = "5h"; + Persistent = true; + }; + }; + + user = mkOption { + type = types.str; + default = "root"; + description = '' + As which user the backup should run. + ''; + example = "postgresql"; + }; + + extraBackupArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Extra arguments passed to restic backup. + ''; + example = [ + "--exclude-file=/etc/nixos/restic-ignore" + ]; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Extra extended options to be passed to the restic --option flag. + ''; + example = [ + "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'" + ]; + }; + + initialize = mkOption { + type = types.bool; + default = false; + description = '' + Create the repository if it doesn't exist. + ''; + }; + + pruneOpts = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + A list of options (--keep-\* et al.) for 'restic forget + --prune', to automatically prune old snapshots. The + 'forget' command is run *after* the 'backup' command, so + keep that in mind when constructing the --keep-\* options. + ''; + example = [ + "--keep-daily 7" + "--keep-weekly 5" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; + + runCheck = mkOption { + type = types.bool; + default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0); + defaultText = literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0''; + description = "Whether to run the `check` command with the provided `checkOpts` options."; + example = true; + }; + + checkOpts = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + A list of options for 'restic check'. + ''; + example = [ + "--with-cache" + ]; + }; + + dynamicFilesFrom = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that produces a list of files to back up. The + results of this command are given to the '--files-from' + option. The result is merged with paths specified via `paths`. + ''; + example = "find /home/matt/git -type d -name .git"; + }; + + backupPrepareCommand = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that must run before starting the backup process. + ''; + }; + + backupCleanupCommand = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that must run after finishing the backup process. + ''; + }; + + package = mkPackageOption pkgs "restic" { }; + + createWrapper = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to generate and add a script to the system path, that has the same environment variables set + as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without + having to manually specify most options. + ''; + }; + }; + })); + default = { }; + example = { + localbackup = { + paths = [ "/home" ]; + exclude = [ "/home/*/.cache" ]; + initialize = true; + }; + remotebackup = { + paths = [ "/home" ]; + extraOptions = [ + "sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'" + ]; + timerConfig = { + OnCalendar = "00:05"; + RandomizedDelaySec = "5h"; + }; + }; + }; + }; + }; + + config = { + services.restic.backups = let + stores = config.pub-solar-os.backups.stores; + backups = config.pub-solar-os.backups.backups; + + storeNames = builtins.attrNames stores; + backupNames = builtins.attrNames backups; + + createBackups = backupName: map + (storeName: { + name = "${backupName}-${storeName}"; + value = stores."${storeName}" // backups."${backupName}"; + }) + storeNames; + + in builtins.listToAttrs (lib.lists.flatten (map createBackups backupNames)); + }; +} diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 3c9316e..d2b1975 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -6,23 +6,22 @@ ... }: { - options.pub-solar-os.auth = { - enable = lib.mkEnableOption "Enable keycloak to run on the node"; + options.pub-solar-os.auth = with lib; { + enable = mkEnableOption "Enable keycloak to run on the node"; - realm = lib.mkOption { + realm = mkOption { description = "Name of the realm"; - type = lib.types.str; + type = types.str; default = config.pub-solar-os.networking.domain; }; + + database-password-file = mkOption { + description = "Database password file path"; + type = types.str; + }; }; config = lib.mkIf config.pub-solar-os.auth.enable { - age.secrets.keycloak-database-password = { - file = "${flake.self}/secrets/keycloak-database-password.age"; - mode = "600"; - #owner = "keycloak"; - }; - services.nginx.virtualHosts."auth.${config.pub-solar-os.networking.domain}" = { enableACME = true; forceSSL = true; @@ -43,10 +42,14 @@ }; }; + nixpkgs.config = lib.mkDefault { + permittedInsecurePackages = [ "keycloak-23.0.6" ]; + }; + # keycloak services.keycloak = { enable = true; - database.passwordFile = config.age.secrets.keycloak-database-password.path; + database.passwordFile = config.pub-solar-os.auth.database-password-file; settings = { hostname = "auth.${config.pub-solar-os.networking.domain}"; http-host = "127.0.0.1"; @@ -59,14 +62,12 @@ }; }; - services.restic.backups.keycloak-storagebox = { + pub-solar-os.backups.backups.keycloak = { paths = [ "/tmp/keycloak-backup.sql" ]; timerConfig = { OnCalendar = "*-*-* 03:00:00 Etc/UTC"; }; initialize = true; - passwordFile = config.age.secrets."restic-repo-storagebox".path; - repository = "sftp:u377325@u377325.your-storagebox.de:/backups"; backupPrepareCommand = '' ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql ''; diff --git a/tests/keycloak.nix b/tests/keycloak.nix new file mode 100644 index 0000000..4de464d --- /dev/null +++ b/tests/keycloak.nix @@ -0,0 +1,79 @@ +{ + self, + pkgs, + lib, + config, + ... +}: let +in { + name = "keycloak"; + + hostPkgs = pkgs; + + node.pkgs = pkgs; + node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; + + nodes = { + acme-server = { + imports = [ + self.nixosModules.home-manager + self.nixosModules.core + ./support/ca.nix + ]; + }; + + client = { + imports = [ + self.nixosModules.home-manager + self.nixosModules.core + ./support/client.nix + ]; + }; + + nachtigall = { + imports = [ + self.inputs.agenix.nixosModules.default + self.nixosModules.home-manager + self.nixosModules.core + self.nixosModules.backups + self.nixosModules.nginx + self.nixosModules.keycloak + self.nixosModules.postgresql + ./support/global.nix + ]; + + systemd.tmpfiles.rules = [ + "f /tmp/dbf 1777 root root 10d" + ]; + + pub-solar-os.auth = { + enable = true; + database-password-file = "/tmp/dbf"; + }; + + networking.interfaces.eth0.ipv4.addresses = [ + { + address = "192.168.1.3"; + prefixLength = 32; + } + ]; + }; + }; + + enableOCR = true; + + testScript = '' + start_all() + + nachtigall.wait_for_unit("system.slice") + nachtigall.succeed("ping 127.0.0.1 -c 2") + nachtigall.wait_for_unit("nginx.service") + nachtigall.wait_for_unit("keycloak.service") + nachtigall.succeed("curl https://auth.test.pub.solar/") + + client.wait_for_unit("system.slice") + client.wait_until_succeeds("swaymsg -t get_tree | grep -q 'firefox'") + client.sleep(20) + client.screenshot("screen") + ''; +} diff --git a/tests/support/ca.nix b/tests/support/ca.nix new file mode 100644 index 0000000..c364deb --- /dev/null +++ b/tests/support/ca.nix @@ -0,0 +1,48 @@ +{ + pkgs, + lib, + config, + ... +}: { + imports = [ + ./global.nix + ]; + + systemd.tmpfiles.rules = [ + "f /tmp/step-ca-intermediate-pw 1777 root root 10d password" + ]; + + networking.interfaces.eth0.ipv4.addresses = [ + { + address = "192.168.1.1"; + prefixLength = 32; + } + ]; + + services.step-ca = let + certificates = pkgs.stdenv.mkDerivation { + name = "certificates"; + src = ./step; + installPhase = '' + mkdir -p $out; + cp -r certs $out/ + cp -r secrets $out/ + ''; + }; + in { + enable = true; + openFirewall = true; + intermediatePasswordFile = "/tmp/step-ca-intermediate-pw"; + port = 443; + address = "0.0.0.0"; + settings = (builtins.fromJSON (builtins.readFile ./step/config/ca.json)) // { + root = "${certificates}/certs/root_ca.crt"; + crt = "${certificates}/certs/intermediate_ca.crt"; + key = "${certificates}/secrets/intermediate_ca_key"; + db = { + type = "badgerv2"; + dataSource = "/var/lib/step-ca/db"; + }; + }; + }; +} diff --git a/tests/support/client.nix b/tests/support/client.nix new file mode 100644 index 0000000..728e26e --- /dev/null +++ b/tests/support/client.nix @@ -0,0 +1,26 @@ +{ + pkgs, + lib, + config, + ... +}: +{ + imports = [ + ./global.nix + ]; + + programs.sway = { + enable = true; + }; + + programs.bash.shellInit = '' + exec sway + ''; + + networking.interfaces.eth0.ipv4.addresses = [ + { + address = "192.168.1.2"; + prefixLength = 32; + } + ]; +} diff --git a/tests/support/global.nix b/tests/support/global.nix new file mode 100644 index 0000000..f5ae1dc --- /dev/null +++ b/tests/support/global.nix @@ -0,0 +1,26 @@ +{ + pkgs, + lib, + config, + ... +}: { + pub-solar-os.networking.domain = "test.pub.solar"; + + security.acme.defaults.server = "https://ca.${config.pub-solar-os.networking.domain}/acme/acme/directory"; + + security.pki.certificates = [ + (builtins.readFile ./step/certs/root_ca.crt) + ]; + + networking.interfaces.eth0.useDHCP = false; + + networking.hosts = { + "192.168.1.1" = [ "ca.${config.pub-solar-os.networking.domain}" ]; + "192.168.1.2" = [ "client.${config.pub-solar-os.networking.domain}" ]; + "192.168.1.3" = [ + "${config.pub-solar-os.networking.domain}" + "www.${config.pub-solar-os.networking.domain}" + "auth.${config.pub-solar-os.networking.domain}" + ]; + }; +} diff --git a/tests/support/step/certs/intermediate_ca.crt b/tests/support/step/certs/intermediate_ca.crt new file mode 100644 index 0000000..3220838 --- /dev/null +++ b/tests/support/step/certs/intermediate_ca.crt @@ -0,0 +1,13 @@ +-----BEGIN CERTIFICATE----- +MIIB4DCCAYagAwIBAgIQVR/3c0swvc/ifeYqLQn3HTAKBggqhkjOPQQDAjA6MRcw +FQYDVQQKEw5wdWIuc29sYXItdGVzdDEfMB0GA1UEAxMWcHViLnNvbGFyLXRlc3Qg +Um9vdCBDQTAeFw0yNDA4MjQwMTI3MTBaFw0zNDA4MjIwMTI3MTBaMEIxFzAVBgNV +BAoTDnB1Yi5zb2xhci10ZXN0MScwJQYDVQQDEx5wdWIuc29sYXItdGVzdCBJbnRl +cm1lZGlhdGUgQ0EwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATpCjy3PAiawAeb +47ZZ9kPXuuV0EavOfFlgnlZBkOc2AXY0R6P1jK06US0SiPo17rqyNgUWH0oV4v8i +/HbZYNXYo2YwZDAOBgNVHQ8BAf8EBAMCAQYwEgYDVR0TAQH/BAgwBgEB/wIBADAd +BgNVHQ4EFgQU1hueYsLAH6+wxjArqCM3IHFqnIEwHwYDVR0jBBgwFoAUxg/BmKK7 +9Zs+b1bvlpYwggy5lnswCgYIKoZIzj0EAwIDSAAwRQIgfxkjyC4HHADRmNDLqZ5L +0po+JD5/9b1L//JoXG+vgXECIQDgkRe8r8/0Ep/NWgBtbkA3oTYq8vCwo1FewBZZ +43fo5w== +-----END CERTIFICATE----- diff --git a/tests/support/step/certs/root_ca.crt b/tests/support/step/certs/root_ca.crt new file mode 100644 index 0000000..71f5cea --- /dev/null +++ b/tests/support/step/certs/root_ca.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBuDCCAV2gAwIBAgIQMXg7xoEIrVjgvKcrRaxo0DAKBggqhkjOPQQDAjA6MRcw +FQYDVQQKEw5wdWIuc29sYXItdGVzdDEfMB0GA1UEAxMWcHViLnNvbGFyLXRlc3Qg +Um9vdCBDQTAeFw0yNDA4MjQwMTI3MDlaFw0zNDA4MjIwMTI3MDlaMDoxFzAVBgNV +BAoTDnB1Yi5zb2xhci10ZXN0MR8wHQYDVQQDExZwdWIuc29sYXItdGVzdCBSb290 +IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEYNxMcHclQP/zv2y6LJIGx9pg +Q2337Zb8TuPY+DnL1MjuCMoeTaMwngzjU/DSbKL0Vx/y+I+PBjhHmPrYcGDcSKNF +MEMwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwHQYDVR0OBBYE +FMYPwZiiu/WbPm9W75aWMIIMuZZ7MAoGCCqGSM49BAMCA0kAMEYCIQDcgr9WyR1C +806aEQ38alYgGg3PhQdT14q47tWIUOnpygIhAM0x/QK/mm7VvQxBLAA4DT6X730m +k/tBvh9SHNbwPxCt +-----END CERTIFICATE----- diff --git a/tests/support/step/config/ca.json b/tests/support/step/config/ca.json new file mode 100644 index 0000000..ab7753a --- /dev/null +++ b/tests/support/step/config/ca.json @@ -0,0 +1,46 @@ +{ + "federatedRoots": null, + "address": ":443", + "insecureAddress": "", + "dnsNames": [ + "ca.test.pub.solar" + ], + "logger": { + "format": "text" + }, + "db": { + "type": "badgerv2", + "badgerFileLoadingMode": "" + }, + "authority": { + "provisioners": [ + { + "name": "acme", + "type": "ACME" + }, + { + "type": "JWK", + "name": "test.pub.solar", + "key": { + "use": "sig", + "kty": "EC", + "kid": "lM-BJXRwwQcdgxLqAS4Za23A2YatZpwXx-PP5NIt8JM", + "crv": "P-256", + "alg": "ES256", + "x": "ouB2mP04Kt8rDa10C8ZzYyzA36rrz-k0c4_ud1hVjyg", + "y": "RbXKcudQRPEFqjG_5AxuqCQXn7pyRToQCwC4MrwLVUQ" + }, + "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjYwMDAwMCwicDJzIjoiNWR5T2puR2Y5aFFNRlc1U25fRWhzUSJ9.a3xtSBuMmzZCMsdfHAXMgFpe9bq8A6bGGOoW9F2Gw7AhxL4bG-AlgA.IA68rSJSGTAKnaVS.XDQc4da-8D9Ykfw-8S4uphsauq5gsEm4qp7zKQUIvcjUlnPAtiHP3xiiBie29ncdg8rKmyzprEEOpTNvXtQl7LsPsHXyKV3SqsTnJecvim9YXGDneAHyWe-XF6hyCZAfSoFbFMgLDKR6d44hMht3ueazL_TPlkFUBLrJbsW782MfdfF3nzcaDf_JDuhKsKHDmKqZyNXDzwf6rINe8adrf5gqaLM2_sGhk7i3XyXygn8HHVw1Dj_w2gPOVm4MS7CO_NgikPqAtGuXDhpWZfXte-FlnMO6d9xQF67b0cwB8kmColPSp1zRiCKPAk9vof8Nn-gGE_aw8zxPi0CJkoY.xbuqSSspgLc_Uw17uiRF7Q" + } + ] + }, + "tls": { + "cipherSuites": [ + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + ], + "minVersion": 1.2, + "maxVersion": 1.3, + "renegotiation": false + } +} diff --git a/tests/support/step/config/defaults.json b/tests/support/step/config/defaults.json new file mode 100644 index 0000000..f3e3488 --- /dev/null +++ b/tests/support/step/config/defaults.json @@ -0,0 +1,6 @@ +{ + "ca-url": "https://ca.test.pub.solar", + "ca-config": "/home/b12f/.step/config/ca.json", + "fingerprint": "4d6a1a918355380acbd0256a2203d0a0da8436bb788e8f19326589045c3cd842", + "root": "/home/b12f/.step/certs/root_ca.crt" +} \ No newline at end of file diff --git a/tests/support/step/secrets/intermediate_ca_key b/tests/support/step/secrets/intermediate_ca_key new file mode 100644 index 0000000..a8eb8b2 --- /dev/null +++ b/tests/support/step/secrets/intermediate_ca_key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,0b34c00cb76ffc16441f5fe762d8d915 + +xJQ5r5kGiaG6rCsmVnONxm99sqceb62dO8/YvgdZ/ouHAxz1OlXYpTJNd2GvezAc +XA6Zx6eGzNCOyhgMNJTXEn8QmcJcMd6OjVLxQ9Tr2Mi3LShcBzMPs30/X2XYsM22 +5G4fRhQD0L4nQ08B3GG6FjPe/HYmkRNZmAeDc2wE5Fg= +-----END EC PRIVATE KEY----- diff --git a/tests/support/step/secrets/root_ca_key b/tests/support/step/secrets/root_ca_key new file mode 100644 index 0000000..19605f9 --- /dev/null +++ b/tests/support/step/secrets/root_ca_key @@ -0,0 +1,8 @@ +-----BEGIN EC PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-256-CBC,48f59a57e5a2b81359e0a3668161b61e + +jMZbpiHSFa74ns30QrAnIlcguqWp+FE20cXbiIVPpLAJpzGskc3k5vRFTpPM8geg +sZ6bVvq3APbKmkopxZHWpd4ly6uHkolbtR1NFxTNKymaJZuSuKspUmDohkIyZN6c +KG0upERMZIOg6Ky1JiM5pLJMHBTsCmzJBmdFCW7GSww= +-----END EC PRIVATE KEY----- diff --git a/tests/website.nix b/tests/website.nix index 10e96a5..bfa6c60 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -13,12 +13,12 @@ node.pkgs = pkgs; node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; - nodes.nachtigall-test = { + nodes.nachtigall_test = { imports = [ self.nixosModules.home-manager self.nixosModules.core self.nixosModules.nginx - self.nixosModules.nginx-website + self.nixosModules.keycloak ]; }; @@ -28,6 +28,7 @@ nachtigall_test.wait_for_unit("system.slice") nachtigall_test.succeed("ping 127.0.0.1 -c 2") nachtigall_test.wait_for_unit("nginx.service") - nachtigall_test.succeed("curl -H 'Host:pub.solar' http://127.0.0.1/") + nachtigall_test.succeed("curl https://test.pub.solar/") + nachtigall_test.succeed("curl https://www.test.pub.solar/") ''; } -- 2.44.2 From c469a8a2dc3b53265bd1e9a79bf72ee9a2a1431c Mon Sep 17 00:00:00 2001 From: b12f Date: Sat, 24 Aug 2024 23:49:31 +0200 Subject: [PATCH 03/21] tests/keycloak: add non-working ssh config for clients --- tests/keycloak.nix | 1 + tests/support/global.nix | 23 +++++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 4de464d..3313584 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -64,6 +64,7 @@ in { testScript = '' start_all() + join_all() nachtigall.wait_for_unit("system.slice") nachtigall.succeed("ping 127.0.0.1 -c 2") diff --git a/tests/support/global.nix b/tests/support/global.nix index f5ae1dc..2e68163 100644 --- a/tests/support/global.nix +++ b/tests/support/global.nix @@ -12,6 +12,29 @@ (builtins.readFile ./step/certs/root_ca.crt) ]; + services.openssh = { + enable = true; + openFirewall = true; + settings = { + PermitRootLogin = lib.mkForce "yes"; + PermitEmptyPasswords = lib.mkForce "yes"; + PasswordAuthentication = lib.mkForce true; + }; + }; + + security.pam.services.sshd.allowNullPassword = true; + + virtualisation.forwardPorts = let + address = (builtins.elemAt config.networking.interfaces.eth0.ipv4.addresses 0).address; + lastAddressPart = builtins.elemAt (lib.strings.splitString "." address) 3; + in [ + { + from = "host"; + host.port = 2000 + (lib.strings.toInt lastAddressPart); + guest.port = 22; + } + ]; + networking.interfaces.eth0.useDHCP = false; networking.hosts = { -- 2.44.2 From 3451e9dead95dd0808120c0d3e2ce5e69a21a130 Mon Sep 17 00:00:00 2001 From: Hendrik Sokolowski Date: Sun, 25 Aug 2024 00:44:28 +0200 Subject: [PATCH 04/21] drop explicit dependencies on postgres mounts --- modules/postgresql/default.nix | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/postgresql/default.nix b/modules/postgresql/default.nix index 580701d..fa0550d 100644 --- a/modules/postgresql/default.nix +++ b/modules/postgresql/default.nix @@ -25,9 +25,4 @@ full_page_writes = false; }; }; - - systemd.services.postgresql = { - after = [ "var-lib-postgresql.mount" ]; - requisite = [ "var-lib-postgresql.mount" ]; - }; } -- 2.44.2 From 6c9434d3a0f9748985619cf27cb6eba7d7150139 Mon Sep 17 00:00:00 2001 From: Hendrik Sokolowski Date: Sun, 25 Aug 2024 00:45:38 +0200 Subject: [PATCH 05/21] disable DHCP entirely --- tests/support/global.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/support/global.nix b/tests/support/global.nix index 2e68163..5bdf165 100644 --- a/tests/support/global.nix +++ b/tests/support/global.nix @@ -35,7 +35,7 @@ } ]; - networking.interfaces.eth0.useDHCP = false; + networking.useDHCP = false; networking.hosts = { "192.168.1.1" = [ "ca.${config.pub-solar-os.networking.domain}" ]; -- 2.44.2 From 31eb82a4e7f185835b452f9e0de3f2eb6bc43f07 Mon Sep 17 00:00:00 2001 From: Hendrik Sokolowski Date: Sun, 25 Aug 2024 00:46:31 +0200 Subject: [PATCH 06/21] raise memory assigned to test-nachtigall, auto-create database user for keycloak --- tests/keycloak.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 3313584..9dc1c08 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -39,6 +39,7 @@ in { self.nixosModules.nginx self.nixosModules.keycloak self.nixosModules.postgresql + { virtualisation.memorySize = 4096; services.keycloak.database.createLocally = true; } ./support/global.nix ]; -- 2.44.2 From e1dadd17ab6cc845652a805f0a7dccae75a74a9b Mon Sep 17 00:00:00 2001 From: b12f Date: Sun, 25 Aug 2024 02:28:02 +0200 Subject: [PATCH 07/21] tests/keycloak: working base test without client --- tests/keycloak.nix | 24 +++++++++++++++++------- tests/support/client.nix | 23 +++++++++++++++++------ tests/support/global.nix | 2 +- 3 files changed, 35 insertions(+), 14 deletions(-) diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 9dc1c08..3657ecd 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -39,18 +39,20 @@ in { self.nixosModules.nginx self.nixosModules.keycloak self.nixosModules.postgresql - { virtualisation.memorySize = 4096; services.keycloak.database.createLocally = true; } ./support/global.nix ]; systemd.tmpfiles.rules = [ - "f /tmp/dbf 1777 root root 10d" + "f /tmp/dbf 1777 root root 10d password" ]; + virtualisation.memorySize = 4096; + pub-solar-os.auth = { enable = true; database-password-file = "/tmp/dbf"; }; + services.keycloak.database.createLocally = true; networking.interfaces.eth0.ipv4.addresses = [ { @@ -63,19 +65,27 @@ in { enableOCR = true; - testScript = '' + testScript = {nodes, ...}: let + user = nodes.client.users.users.${nodes.client.pub-solar-os.authentication.username}; + #uid = toString user.uid; + bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${user.name})/bus"; + gdbus = "${bus} gdbus"; + su = command: "su - ${user.name} -c '${command}'"; + gseval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; + wmClass = su "${gdbus} ${gseval} global.display.focus_window.wm_class"; + in '' start_all() - join_all() nachtigall.wait_for_unit("system.slice") nachtigall.succeed("ping 127.0.0.1 -c 2") nachtigall.wait_for_unit("nginx.service") nachtigall.wait_for_unit("keycloak.service") - nachtigall.succeed("curl https://auth.test.pub.solar/") + nachtigall.wait_until_succeeds("curl http://127.0.0.1:8080/") + nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") client.wait_for_unit("system.slice") - client.wait_until_succeeds("swaymsg -t get_tree | grep -q 'firefox'") - client.sleep(20) + client.sleep(30) + # client.wait_until_succeeds("${wmClass} | grep -q 'firefox'") client.screenshot("screen") ''; } diff --git a/tests/support/client.nix b/tests/support/client.nix index 728e26e..5e1f4f4 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -9,13 +9,24 @@ ./global.nix ]; - programs.sway = { - enable = true; - }; + services.xserver.enable = true; + services.xserver.displayManager.gdm.enable = true; + services.xserver.desktopManager.gnome.enable = true; + services.xserver.displayManager.autoLogin.enable = true; + services.xserver.displayManager.autoLogin.user = config.pub-solar-os.authentication.username; - programs.bash.shellInit = '' - exec sway - ''; + systemd.user.services = { + "org.gnome.Shell@wayland" = { + serviceConfig = { + ExecStart = [ + # Clear the list before overriding it. + "" + # Eval API is now internal so Shell needs to run in unsafe mode. + "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode" + ]; + }; + }; + }; networking.interfaces.eth0.ipv4.addresses = [ { diff --git a/tests/support/global.nix b/tests/support/global.nix index 5bdf165..2e68163 100644 --- a/tests/support/global.nix +++ b/tests/support/global.nix @@ -35,7 +35,7 @@ } ]; - networking.useDHCP = false; + networking.interfaces.eth0.useDHCP = false; networking.hosts = { "192.168.1.1" = [ "ca.${config.pub-solar-os.networking.domain}" ]; -- 2.44.2 From 74f03555e53bd86da1ee81ec6d6c875ad0d20674 Mon Sep 17 00:00:00 2001 From: teutat3s Date: Sun, 25 Aug 2024 02:41:29 +0200 Subject: [PATCH 08/21] style: format using nixfmt-rfc-style --- flake.nix | 27 +- modules/backups/default.nix | 456 ++++++++++++------------ modules/core/nix.nix | 4 +- modules/keycloak/default.nix | 4 +- tests/keycloak.nix | 55 +-- tests/support/ca.nix | 61 ++-- tests/support/client.nix | 4 +- tests/support/global.nix | 29 +- tests/support/step/config/ca.json | 82 +++-- tests/support/step/config/defaults.json | 10 +- 10 files changed, 374 insertions(+), 358 deletions(-) diff --git a/flake.nix b/flake.nix index b22f325..a86c7a9 100644 --- a/flake.nix +++ b/flake.nix @@ -79,14 +79,25 @@ master = import inputs.master { inherit system; }; }; - packages = let - nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; - testDir = builtins.attrNames (builtins.readDir ./tests); - testFiles = builtins.filter (n: builtins.match "^.*.nix$" n != null) testDir; - in builtins.listToAttrs (map (x: { - name = "test-${lib.strings.removeSuffix ".nix" x}"; - value = nixos-lib.runTest (import (./tests + "/${x}") { inherit self; inherit pkgs; inherit lib; inherit config; }); - }) testFiles); + packages = + let + nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; + testDir = builtins.attrNames (builtins.readDir ./tests); + testFiles = builtins.filter (n: builtins.match "^.*.nix$" n != null) testDir; + in + builtins.listToAttrs ( + map (x: { + name = "test-${lib.strings.removeSuffix ".nix" x}"; + value = nixos-lib.runTest ( + import (./tests + "/${x}") { + inherit self; + inherit pkgs; + inherit lib; + inherit config; + } + ); + }) testFiles + ); devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ diff --git a/modules/backups/default.nix b/modules/backups/default.nix index 50ffef8..fd1a9fc 100644 --- a/modules/backups/default.nix +++ b/modules/backups/default.nix @@ -7,247 +7,257 @@ }: { options.pub-solar-os.backups = { - stores = with lib; mkOption { - description = '' - Periodic backups to create with Restic. - ''; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - passwordFile = mkOption { - type = types.str; - description = '' - Read the repository password from a file. - ''; - example = "/etc/nixos/restic-password"; - }; + stores = + with lib; + mkOption { + description = '' + Periodic backups to create with Restic. + ''; + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + passwordFile = mkOption { + type = types.str; + description = '' + Read the repository password from a file. + ''; + example = "/etc/nixos/restic-password"; + }; - repository = mkOption { - type = with types; nullOr str; - default = null; - description = '' - repository to backup to. - ''; - example = "sftp:backup@192.168.1.100:/backups/${name}"; - }; - }; - })); + repository = mkOption { + type = with types; nullOr str; + default = null; + description = '' + repository to backup to. + ''; + example = "sftp:backup@192.168.1.100:/backups/${name}"; + }; + }; + } + ) + ); - default = { }; - example = { - remotebackup = { - repository = "sftp:backup@host:/backups/home"; - passwordFile = "/etc/nixos/secrets/restic-password"; + default = { }; + example = { + remotebackup = { + repository = "sftp:backup@host:/backups/home"; + passwordFile = "/etc/nixos/secrets/restic-password"; + }; }; }; - }; - backups = with lib; mkOption { - description = '' - Periodic backups to create with Restic. - ''; - type = types.attrsOf (types.submodule ({ name, ... }: { - options = { - paths = mkOption { - # This is nullable for legacy reasons only. We should consider making it a pure listOf - # after some time has passed since this comment was added. - type = types.nullOr (types.listOf types.str); - default = [ ]; - description = '' - Which paths to backup, in addition to ones specified via - `dynamicFilesFrom`. If null or an empty array and - `dynamicFilesFrom` is also null, no backup command will be run. - This can be used to create a prune-only job. - ''; - example = [ - "/var/lib/postgresql" - "/home/user/backup" - ]; + backups = + with lib; + mkOption { + description = '' + Periodic backups to create with Restic. + ''; + type = types.attrsOf ( + types.submodule ( + { name, ... }: + { + options = { + paths = mkOption { + # This is nullable for legacy reasons only. We should consider making it a pure listOf + # after some time has passed since this comment was added. + type = types.nullOr (types.listOf types.str); + default = [ ]; + description = '' + Which paths to backup, in addition to ones specified via + `dynamicFilesFrom`. If null or an empty array and + `dynamicFilesFrom` is also null, no backup command will be run. + This can be used to create a prune-only job. + ''; + example = [ + "/var/lib/postgresql" + "/home/user/backup" + ]; + }; + + exclude = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Patterns to exclude when backing up. See + https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for + details on syntax. + ''; + example = [ + "/var/cache" + "/home/*/.cache" + ".git" + ]; + }; + + timerConfig = mkOption { + type = types.nullOr (types.attrsOf unitOption); + default = { + OnCalendar = "daily"; + Persistent = true; + }; + description = '' + When to run the backup. See {manpage}`systemd.timer(5)` for + details. If null no timer is created and the backup will only + run when explicitly started. + ''; + example = { + OnCalendar = "00:05"; + RandomizedDelaySec = "5h"; + Persistent = true; + }; + }; + + user = mkOption { + type = types.str; + default = "root"; + description = '' + As which user the backup should run. + ''; + example = "postgresql"; + }; + + extraBackupArgs = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Extra arguments passed to restic backup. + ''; + example = [ "--exclude-file=/etc/nixos/restic-ignore" ]; + }; + + extraOptions = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + Extra extended options to be passed to the restic --option flag. + ''; + example = [ "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'" ]; + }; + + initialize = mkOption { + type = types.bool; + default = false; + description = '' + Create the repository if it doesn't exist. + ''; + }; + + pruneOpts = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + A list of options (--keep-\* et al.) for 'restic forget + --prune', to automatically prune old snapshots. The + 'forget' command is run *after* the 'backup' command, so + keep that in mind when constructing the --keep-\* options. + ''; + example = [ + "--keep-daily 7" + "--keep-weekly 5" + "--keep-monthly 12" + "--keep-yearly 75" + ]; + }; + + runCheck = mkOption { + type = types.bool; + default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0); + defaultText = literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0''; + description = "Whether to run the `check` command with the provided `checkOpts` options."; + example = true; + }; + + checkOpts = mkOption { + type = types.listOf types.str; + default = [ ]; + description = '' + A list of options for 'restic check'. + ''; + example = [ "--with-cache" ]; + }; + + dynamicFilesFrom = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that produces a list of files to back up. The + results of this command are given to the '--files-from' + option. The result is merged with paths specified via `paths`. + ''; + example = "find /home/matt/git -type d -name .git"; + }; + + backupPrepareCommand = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that must run before starting the backup process. + ''; + }; + + backupCleanupCommand = mkOption { + type = with types; nullOr str; + default = null; + description = '' + A script that must run after finishing the backup process. + ''; + }; + + package = mkPackageOption pkgs "restic" { }; + + createWrapper = lib.mkOption { + type = lib.types.bool; + default = true; + description = '' + Whether to generate and add a script to the system path, that has the same environment variables set + as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without + having to manually specify most options. + ''; + }; + }; + } + ) + ); + default = { }; + example = { + localbackup = { + paths = [ "/home" ]; + exclude = [ "/home/*/.cache" ]; + initialize = true; }; - - exclude = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - Patterns to exclude when backing up. See - https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files for - details on syntax. - ''; - example = [ - "/var/cache" - "/home/*/.cache" - ".git" + remotebackup = { + paths = [ "/home" ]; + extraOptions = [ + "sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'" ]; - }; - - timerConfig = mkOption { - type = types.nullOr (types.attrsOf unitOption); - default = { - OnCalendar = "daily"; - Persistent = true; - }; - description = '' - When to run the backup. See {manpage}`systemd.timer(5)` for - details. If null no timer is created and the backup will only - run when explicitly started. - ''; - example = { + timerConfig = { OnCalendar = "00:05"; RandomizedDelaySec = "5h"; - Persistent = true; }; }; - - user = mkOption { - type = types.str; - default = "root"; - description = '' - As which user the backup should run. - ''; - example = "postgresql"; - }; - - extraBackupArgs = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - Extra arguments passed to restic backup. - ''; - example = [ - "--exclude-file=/etc/nixos/restic-ignore" - ]; - }; - - extraOptions = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - Extra extended options to be passed to the restic --option flag. - ''; - example = [ - "sftp.command='ssh backup@192.168.1.100 -i /home/user/.ssh/id_rsa -s sftp'" - ]; - }; - - initialize = mkOption { - type = types.bool; - default = false; - description = '' - Create the repository if it doesn't exist. - ''; - }; - - pruneOpts = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - A list of options (--keep-\* et al.) for 'restic forget - --prune', to automatically prune old snapshots. The - 'forget' command is run *after* the 'backup' command, so - keep that in mind when constructing the --keep-\* options. - ''; - example = [ - "--keep-daily 7" - "--keep-weekly 5" - "--keep-monthly 12" - "--keep-yearly 75" - ]; - }; - - runCheck = mkOption { - type = types.bool; - default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0); - defaultText = literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0''; - description = "Whether to run the `check` command with the provided `checkOpts` options."; - example = true; - }; - - checkOpts = mkOption { - type = types.listOf types.str; - default = [ ]; - description = '' - A list of options for 'restic check'. - ''; - example = [ - "--with-cache" - ]; - }; - - dynamicFilesFrom = mkOption { - type = with types; nullOr str; - default = null; - description = '' - A script that produces a list of files to back up. The - results of this command are given to the '--files-from' - option. The result is merged with paths specified via `paths`. - ''; - example = "find /home/matt/git -type d -name .git"; - }; - - backupPrepareCommand = mkOption { - type = with types; nullOr str; - default = null; - description = '' - A script that must run before starting the backup process. - ''; - }; - - backupCleanupCommand = mkOption { - type = with types; nullOr str; - default = null; - description = '' - A script that must run after finishing the backup process. - ''; - }; - - package = mkPackageOption pkgs "restic" { }; - - createWrapper = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Whether to generate and add a script to the system path, that has the same environment variables set - as the systemd service. This can be used to e.g. mount snapshots or perform other opterations, without - having to manually specify most options. - ''; - }; - }; - })); - default = { }; - example = { - localbackup = { - paths = [ "/home" ]; - exclude = [ "/home/*/.cache" ]; - initialize = true; - }; - remotebackup = { - paths = [ "/home" ]; - extraOptions = [ - "sftp.command='ssh backup@host -i /etc/nixos/secrets/backup-private-key -s sftp'" - ]; - timerConfig = { - OnCalendar = "00:05"; - RandomizedDelaySec = "5h"; - }; }; }; - }; }; config = { - services.restic.backups = let - stores = config.pub-solar-os.backups.stores; - backups = config.pub-solar-os.backups.backups; + services.restic.backups = + let + stores = config.pub-solar-os.backups.stores; + backups = config.pub-solar-os.backups.backups; - storeNames = builtins.attrNames stores; - backupNames = builtins.attrNames backups; + storeNames = builtins.attrNames stores; + backupNames = builtins.attrNames backups; - createBackups = backupName: map - (storeName: { - name = "${backupName}-${storeName}"; - value = stores."${storeName}" // backups."${backupName}"; - }) - storeNames; + createBackups = + backupName: + map (storeName: { + name = "${backupName}-${storeName}"; + value = stores."${storeName}" // backups."${backupName}"; + }) storeNames; - in builtins.listToAttrs (lib.lists.flatten (map createBackups backupNames)); + in + builtins.listToAttrs (lib.lists.flatten (map createBackups backupNames)); }; } diff --git a/modules/core/nix.nix b/modules/core/nix.nix index 936be64..ece11ae 100644 --- a/modules/core/nix.nix +++ b/modules/core/nix.nix @@ -6,9 +6,7 @@ ... }: { - nixpkgs.config = lib.mkDefault { - allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ ]; - }; + nixpkgs.config = lib.mkDefault { allowUnfreePredicate = pkg: builtins.elem (lib.getName pkg) [ ]; }; nix = { # Use default version alias for nix package diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index d2b1975..2550df6 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -42,9 +42,7 @@ }; }; - nixpkgs.config = lib.mkDefault { - permittedInsecurePackages = [ "keycloak-23.0.6" ]; - }; + nixpkgs.config = lib.mkDefault { permittedInsecurePackages = [ "keycloak-23.0.6" ]; }; # keycloak services.keycloak = { diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 3657ecd..a4bff1f 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -4,8 +4,10 @@ lib, config, ... -}: let -in { +}: +let +in +{ name = "keycloak"; hostPkgs = pkgs; @@ -42,9 +44,7 @@ in { ./support/global.nix ]; - systemd.tmpfiles.rules = [ - "f /tmp/dbf 1777 root root 10d password" - ]; + systemd.tmpfiles.rules = [ "f /tmp/dbf 1777 root root 10d password" ]; virtualisation.memorySize = 4096; @@ -65,27 +65,30 @@ in { enableOCR = true; - testScript = {nodes, ...}: let - user = nodes.client.users.users.${nodes.client.pub-solar-os.authentication.username}; - #uid = toString user.uid; - bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${user.name})/bus"; - gdbus = "${bus} gdbus"; - su = command: "su - ${user.name} -c '${command}'"; - gseval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; - wmClass = su "${gdbus} ${gseval} global.display.focus_window.wm_class"; - in '' - start_all() + testScript = + { nodes, ... }: + let + user = nodes.client.users.users.${nodes.client.pub-solar-os.authentication.username}; + #uid = toString user.uid; + bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${user.name})/bus"; + gdbus = "${bus} gdbus"; + su = command: "su - ${user.name} -c '${command}'"; + gseval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; + wmClass = su "${gdbus} ${gseval} global.display.focus_window.wm_class"; + in + '' + start_all() - nachtigall.wait_for_unit("system.slice") - nachtigall.succeed("ping 127.0.0.1 -c 2") - nachtigall.wait_for_unit("nginx.service") - nachtigall.wait_for_unit("keycloak.service") - nachtigall.wait_until_succeeds("curl http://127.0.0.1:8080/") - nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") + nachtigall.wait_for_unit("system.slice") + nachtigall.succeed("ping 127.0.0.1 -c 2") + nachtigall.wait_for_unit("nginx.service") + nachtigall.wait_for_unit("keycloak.service") + nachtigall.wait_until_succeeds("curl http://127.0.0.1:8080/") + nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") - client.wait_for_unit("system.slice") - client.sleep(30) - # client.wait_until_succeeds("${wmClass} | grep -q 'firefox'") - client.screenshot("screen") - ''; + client.wait_for_unit("system.slice") + client.sleep(30) + # client.wait_until_succeeds("${wmClass} | grep -q 'firefox'") + client.screenshot("screen") + ''; } diff --git a/tests/support/ca.nix b/tests/support/ca.nix index c364deb..632c610 100644 --- a/tests/support/ca.nix +++ b/tests/support/ca.nix @@ -3,14 +3,11 @@ lib, config, ... -}: { - imports = [ - ./global.nix - ]; +}: +{ + imports = [ ./global.nix ]; - systemd.tmpfiles.rules = [ - "f /tmp/step-ca-intermediate-pw 1777 root root 10d password" - ]; + systemd.tmpfiles.rules = [ "f /tmp/step-ca-intermediate-pw 1777 root root 10d password" ]; networking.interfaces.eth0.ipv4.addresses = [ { @@ -19,30 +16,32 @@ } ]; - services.step-ca = let - certificates = pkgs.stdenv.mkDerivation { - name = "certificates"; - src = ./step; - installPhase = '' - mkdir -p $out; - cp -r certs $out/ - cp -r secrets $out/ - ''; - }; - in { - enable = true; - openFirewall = true; - intermediatePasswordFile = "/tmp/step-ca-intermediate-pw"; - port = 443; - address = "0.0.0.0"; - settings = (builtins.fromJSON (builtins.readFile ./step/config/ca.json)) // { - root = "${certificates}/certs/root_ca.crt"; - crt = "${certificates}/certs/intermediate_ca.crt"; - key = "${certificates}/secrets/intermediate_ca_key"; - db = { - type = "badgerv2"; - dataSource = "/var/lib/step-ca/db"; + services.step-ca = + let + certificates = pkgs.stdenv.mkDerivation { + name = "certificates"; + src = ./step; + installPhase = '' + mkdir -p $out; + cp -r certs $out/ + cp -r secrets $out/ + ''; + }; + in + { + enable = true; + openFirewall = true; + intermediatePasswordFile = "/tmp/step-ca-intermediate-pw"; + port = 443; + address = "0.0.0.0"; + settings = (builtins.fromJSON (builtins.readFile ./step/config/ca.json)) // { + root = "${certificates}/certs/root_ca.crt"; + crt = "${certificates}/certs/intermediate_ca.crt"; + key = "${certificates}/secrets/intermediate_ca_key"; + db = { + type = "badgerv2"; + dataSource = "/var/lib/step-ca/db"; + }; }; }; - }; } diff --git a/tests/support/client.nix b/tests/support/client.nix index 5e1f4f4..41e97f0 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -5,9 +5,7 @@ ... }: { - imports = [ - ./global.nix - ]; + imports = [ ./global.nix ]; services.xserver.enable = true; services.xserver.displayManager.gdm.enable = true; diff --git a/tests/support/global.nix b/tests/support/global.nix index 2e68163..f5e68c9 100644 --- a/tests/support/global.nix +++ b/tests/support/global.nix @@ -3,14 +3,13 @@ lib, config, ... -}: { +}: +{ pub-solar-os.networking.domain = "test.pub.solar"; security.acme.defaults.server = "https://ca.${config.pub-solar-os.networking.domain}/acme/acme/directory"; - security.pki.certificates = [ - (builtins.readFile ./step/certs/root_ca.crt) - ]; + security.pki.certificates = [ (builtins.readFile ./step/certs/root_ca.crt) ]; services.openssh = { enable = true; @@ -24,16 +23,18 @@ security.pam.services.sshd.allowNullPassword = true; - virtualisation.forwardPorts = let - address = (builtins.elemAt config.networking.interfaces.eth0.ipv4.addresses 0).address; - lastAddressPart = builtins.elemAt (lib.strings.splitString "." address) 3; - in [ - { - from = "host"; - host.port = 2000 + (lib.strings.toInt lastAddressPart); - guest.port = 22; - } - ]; + virtualisation.forwardPorts = + let + address = (builtins.elemAt config.networking.interfaces.eth0.ipv4.addresses 0).address; + lastAddressPart = builtins.elemAt (lib.strings.splitString "." address) 3; + in + [ + { + from = "host"; + host.port = 2000 + (lib.strings.toInt lastAddressPart); + guest.port = 22; + } + ]; networking.interfaces.eth0.useDHCP = false; diff --git a/tests/support/step/config/ca.json b/tests/support/step/config/ca.json index ab7753a..6735a00 100644 --- a/tests/support/step/config/ca.json +++ b/tests/support/step/config/ca.json @@ -1,46 +1,44 @@ { - "federatedRoots": null, - "address": ":443", - "insecureAddress": "", - "dnsNames": [ - "ca.test.pub.solar" - ], - "logger": { - "format": "text" - }, - "db": { - "type": "badgerv2", - "badgerFileLoadingMode": "" - }, - "authority": { - "provisioners": [ + "federatedRoots": null, + "address": ":443", + "insecureAddress": "", + "dnsNames": ["ca.test.pub.solar"], + "logger": { + "format": "text" + }, + "db": { + "type": "badgerv2", + "badgerFileLoadingMode": "" + }, + "authority": { + "provisioners": [ { - "name": "acme", - "type": "ACME" + "name": "acme", + "type": "ACME" }, - { - "type": "JWK", - "name": "test.pub.solar", - "key": { - "use": "sig", - "kty": "EC", - "kid": "lM-BJXRwwQcdgxLqAS4Za23A2YatZpwXx-PP5NIt8JM", - "crv": "P-256", - "alg": "ES256", - "x": "ouB2mP04Kt8rDa10C8ZzYyzA36rrz-k0c4_ud1hVjyg", - "y": "RbXKcudQRPEFqjG_5AxuqCQXn7pyRToQCwC4MrwLVUQ" - }, - "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjYwMDAwMCwicDJzIjoiNWR5T2puR2Y5aFFNRlc1U25fRWhzUSJ9.a3xtSBuMmzZCMsdfHAXMgFpe9bq8A6bGGOoW9F2Gw7AhxL4bG-AlgA.IA68rSJSGTAKnaVS.XDQc4da-8D9Ykfw-8S4uphsauq5gsEm4qp7zKQUIvcjUlnPAtiHP3xiiBie29ncdg8rKmyzprEEOpTNvXtQl7LsPsHXyKV3SqsTnJecvim9YXGDneAHyWe-XF6hyCZAfSoFbFMgLDKR6d44hMht3ueazL_TPlkFUBLrJbsW782MfdfF3nzcaDf_JDuhKsKHDmKqZyNXDzwf6rINe8adrf5gqaLM2_sGhk7i3XyXygn8HHVw1Dj_w2gPOVm4MS7CO_NgikPqAtGuXDhpWZfXte-FlnMO6d9xQF67b0cwB8kmColPSp1zRiCKPAk9vof8Nn-gGE_aw8zxPi0CJkoY.xbuqSSspgLc_Uw17uiRF7Q" - } - ] - }, - "tls": { - "cipherSuites": [ - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" - ], - "minVersion": 1.2, - "maxVersion": 1.3, - "renegotiation": false - } + { + "type": "JWK", + "name": "test.pub.solar", + "key": { + "use": "sig", + "kty": "EC", + "kid": "lM-BJXRwwQcdgxLqAS4Za23A2YatZpwXx-PP5NIt8JM", + "crv": "P-256", + "alg": "ES256", + "x": "ouB2mP04Kt8rDa10C8ZzYyzA36rrz-k0c4_ud1hVjyg", + "y": "RbXKcudQRPEFqjG_5AxuqCQXn7pyRToQCwC4MrwLVUQ" + }, + "encryptedKey": "eyJhbGciOiJQQkVTMi1IUzI1NitBMTI4S1ciLCJjdHkiOiJqd2sranNvbiIsImVuYyI6IkEyNTZHQ00iLCJwMmMiOjYwMDAwMCwicDJzIjoiNWR5T2puR2Y5aFFNRlc1U25fRWhzUSJ9.a3xtSBuMmzZCMsdfHAXMgFpe9bq8A6bGGOoW9F2Gw7AhxL4bG-AlgA.IA68rSJSGTAKnaVS.XDQc4da-8D9Ykfw-8S4uphsauq5gsEm4qp7zKQUIvcjUlnPAtiHP3xiiBie29ncdg8rKmyzprEEOpTNvXtQl7LsPsHXyKV3SqsTnJecvim9YXGDneAHyWe-XF6hyCZAfSoFbFMgLDKR6d44hMht3ueazL_TPlkFUBLrJbsW782MfdfF3nzcaDf_JDuhKsKHDmKqZyNXDzwf6rINe8adrf5gqaLM2_sGhk7i3XyXygn8HHVw1Dj_w2gPOVm4MS7CO_NgikPqAtGuXDhpWZfXte-FlnMO6d9xQF67b0cwB8kmColPSp1zRiCKPAk9vof8Nn-gGE_aw8zxPi0CJkoY.xbuqSSspgLc_Uw17uiRF7Q" + } + ] + }, + "tls": { + "cipherSuites": [ + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256" + ], + "minVersion": 1.2, + "maxVersion": 1.3, + "renegotiation": false + } } diff --git a/tests/support/step/config/defaults.json b/tests/support/step/config/defaults.json index f3e3488..40f7606 100644 --- a/tests/support/step/config/defaults.json +++ b/tests/support/step/config/defaults.json @@ -1,6 +1,6 @@ { - "ca-url": "https://ca.test.pub.solar", - "ca-config": "/home/b12f/.step/config/ca.json", - "fingerprint": "4d6a1a918355380acbd0256a2203d0a0da8436bb788e8f19326589045c3cd842", - "root": "/home/b12f/.step/certs/root_ca.crt" -} \ No newline at end of file + "ca-url": "https://ca.test.pub.solar", + "ca-config": "/home/b12f/.step/config/ca.json", + "fingerprint": "4d6a1a918355380acbd0256a2203d0a0da8436bb788e8f19326589045c3cd842", + "root": "/home/b12f/.step/certs/root_ca.crt" +} -- 2.44.2 From 6ac401da212254b8b2c21a8c945518346c5b043e Mon Sep 17 00:00:00 2001 From: teutat3s Date: Sun, 25 Aug 2024 02:42:56 +0200 Subject: [PATCH 09/21] keycloak: remove unneeded insecure exception --- modules/keycloak/default.nix | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 2550df6..59d924b 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -42,8 +42,6 @@ }; }; - nixpkgs.config = lib.mkDefault { permittedInsecurePackages = [ "keycloak-23.0.6" ]; }; - # keycloak services.keycloak = { enable = true; -- 2.44.2 From fbb9dd8f174eb7cd783a9dc744a7149918390428 Mon Sep 17 00:00:00 2001 From: b12f Date: Sun, 25 Aug 2024 04:19:17 +0200 Subject: [PATCH 10/21] tests: fix website test --- modules/nginx-website/default.nix | 2 +- tests/keycloak.nix | 2 -- tests/website.nix | 53 +++++++++++++++++++++++-------- 3 files changed, 40 insertions(+), 17 deletions(-) diff --git a/modules/nginx-website/default.nix b/modules/nginx-website/default.nix index ebf1e8d..26c944e 100644 --- a/modules/nginx-website/default.nix +++ b/modules/nginx-website/default.nix @@ -7,7 +7,7 @@ services.nginx.virtualHosts = { "www.${config.pub-solar-os.networking.domain}" = { enableACME = true; - addSSL = true; + forceSSL = true; extraConfig = '' error_log /dev/null; diff --git a/tests/keycloak.nix b/tests/keycloak.nix index a4bff1f..5e735fd 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -63,8 +63,6 @@ in }; }; - enableOCR = true; - testScript = { nodes, ... }: let diff --git a/tests/website.nix b/tests/website.nix index bfa6c60..452262b 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -13,22 +13,47 @@ node.pkgs = pkgs; node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; - nodes.nachtigall_test = { - imports = [ - self.nixosModules.home-manager - self.nixosModules.core - self.nixosModules.nginx - self.nixosModules.keycloak - ]; + nodes = { + acme-server = { + imports = [ + self.nixosModules.home-manager + self.nixosModules.core + ./support/ca.nix + ]; + }; + + nachtigall = { + imports = [ + self.nixosModules.home-manager + self.nixosModules.core + self.nixosModules.nginx + self.nixosModules.nginx-website + ./support/global.nix + ]; + + virtualisation.memorySize = 4096; + + networking.interfaces.eth0.ipv4.addresses = [ + { + address = "192.168.1.3"; + prefixLength = 32; + } + ]; + }; }; - enableOCR = true; - testScript = '' - nachtigall_test.wait_for_unit("system.slice") - nachtigall_test.succeed("ping 127.0.0.1 -c 2") - nachtigall_test.wait_for_unit("nginx.service") - nachtigall_test.succeed("curl https://test.pub.solar/") - nachtigall_test.succeed("curl https://www.test.pub.solar/") + start_all() + + acme_server.wait_for_unit("system.slice") + acme_server.wait_for_unit("step-ca.service") + acme_server.succeed("ping ca.test.pub.solar -c 2") + acme_server.wait_until_succeeds("curl 127.0.0.1:443") + + nachtigall.wait_for_unit("system.slice") + nachtigall.succeed("ping test.pub.solar -c 2") + nachtigall.succeed("ping ca.test.pub.solar -c 2") + nachtigall.wait_for_unit("nginx.service") + nachtigall.wait_until_succeeds("curl https://test.pub.solar/") ''; } -- 2.44.2 From 45b0500f0fd6ff73a4ebac2372f5a3c11091a5e7 Mon Sep 17 00:00:00 2001 From: b12f Date: Sun, 25 Aug 2024 04:36:14 +0200 Subject: [PATCH 11/21] modules/backup: fix unitOptions usage --- modules/backups/default.nix | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/backups/default.nix b/modules/backups/default.nix index fd1a9fc..f3695af 100644 --- a/modules/backups/default.nix +++ b/modules/backups/default.nix @@ -4,8 +4,11 @@ lib, pkgs, ... -}: -{ +}: let + utils = import "${flake.inputs.nixpkgs}/nixos/lib/utils.nix" { inherit lib; inherit config; inherit pkgs; }; + # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers" + inherit (utils.systemdUtils.unitOptions) unitOption; +in { options.pub-solar-os.backups = { stores = with lib; @@ -163,7 +166,7 @@ runCheck = mkOption { type = types.bool; - default = (builtins.length config.services.restic.backups.${name}.checkOpts > 0); + default = (builtins.length config.pub-solar-os.backups.backups.${name}.checkOpts > 0); defaultText = literalExpression ''builtins.length config.services.backups.${name}.checkOpts > 0''; description = "Whether to run the `check` command with the provided `checkOpts` options."; example = true; -- 2.44.2 From 47c2e94e91700c6abe17012a82f72bbe1f69df46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Yule=20B=C3=A4dorf?= Date: Sun, 28 Apr 2024 02:44:14 +0200 Subject: [PATCH 12/21] auth: add last login to keycloak, add docs --- docs/automated-account-deletion.md | 14 +++ flake.lock | 158 ++++++++++++++++++++++++++--- flake.nix | 3 + modules/keycloak/default.nix | 3 + 4 files changed, 163 insertions(+), 15 deletions(-) create mode 100644 docs/automated-account-deletion.md diff --git a/docs/automated-account-deletion.md b/docs/automated-account-deletion.md new file mode 100644 index 0000000..b7c2cb7 --- /dev/null +++ b/docs/automated-account-deletion.md @@ -0,0 +1,14 @@ +# Automated account deletion + +Per GDPR legislation, accounts should be automatically deleted after a period of inactivity. We discern between two different types of accounts: + +1. Without verified email: should be deleted after 30 days without being activated +2. With verified email: should be deleted after 2 years of inactivity + +Some services hold on to a session for a very long time. We'll have to query their APIs to see if the account is still in use: + +* Matrix via the admin api: https://matrix-org.github.io/synapse/v1.48/admin_api/user_admin_api.html#query-current-sessions-for-a-user +* Mastodon via the admin api: https://docs.joinmastodon.org/methods/admin/accounts/#200-ok +* Nextcloud only gives the last login, not the last active time like a sync via `nextcloud-occ user:lastseen` +* Keycloak +* We can ignore Forgejo, since the sessions there are valid for a maximum of one year, regardless of how they got created diff --git a/flake.lock b/flake.lock index 44c855a..1f71268 100644 --- a/flake.lock +++ b/flake.lock @@ -68,6 +68,25 @@ "devshell": { "inputs": { "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1713532798, + "narHash": "sha256-wtBhsdMJA3Wa32Wtm1eeo84GejtI43pMrFrmwLXrsEc=", + "owner": "numtide", + "repo": "devshell", + "rev": "12e914740a25ea1891ec619bb53cf5e6ca922e40", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "devshell", + "type": "github" + } + }, + "devshell_2": { + "inputs": { + "flake-utils": "flake-utils_2", "nixpkgs": [ "keycloak-theme-pub-solar", "nixpkgs" @@ -178,6 +197,24 @@ "type": "github" } }, + "flake-parts_2": { + "inputs": { + "nixpkgs-lib": "nixpkgs-lib_2" + }, + "locked": { + "lastModified": 1712014858, + "narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=", + "owner": "hercules-ci", + "repo": "flake-parts", + "rev": "9126214d0a59633752a136528f5f3b9aa8565b7d", + "type": "github" + }, + "original": { + "owner": "hercules-ci", + "repo": "flake-parts", + "type": "github" + } + }, "flake-utils": { "inputs": { "systems": "systems_3" @@ -200,6 +237,24 @@ "inputs": { "systems": "systems_4" }, + "locked": { + "lastModified": 1701680307, + "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "flake-utils_3": { + "inputs": { + "systems": "systems_5" + }, "locked": { "lastModified": 1705309234, "narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=", @@ -214,7 +269,7 @@ "type": "github" } }, - "flake-utils_3": { + "flake-utils_4": { "locked": { "lastModified": 1653893745, "narHash": "sha256-0jntwV3Z8//YwuOjzhV2sgJJPt+HY6KhU7VZUL0fKZQ=", @@ -250,10 +305,33 @@ "type": "github" } }, - "keycloak-theme-pub-solar": { + "keycloak-event-listener": { "inputs": { "devshell": "devshell", - "flake-utils": "flake-utils_2", + "flake-parts": "flake-parts_2", + "nixpkgs": [ + "unstable" + ] + }, + "locked": { + "lastModified": 1714263025, + "narHash": "sha256-Uesrz49RwbG7sHgiHkkb5o364BN9WbuwroWxVXdcfvo=", + "ref": "main", + "rev": "fb569f474698b5711c208fd5b4b5880d64863587", + "revCount": 2, + "type": "git", + "url": "https://git.pub.solar/pub-solar/keycloak-event-listener" + }, + "original": { + "ref": "main", + "type": "git", + "url": "https://git.pub.solar/pub-solar/keycloak-event-listener" + } + }, + "keycloak-theme-pub-solar": { + "inputs": { + "devshell": "devshell_2", + "flake-utils": "flake-utils_3", "nixpkgs": [ "nixpkgs" ] @@ -299,11 +377,11 @@ ] }, "locked": { - "lastModified": 1724299755, - "narHash": "sha256-P5zMA17kD9tqiqMuNXwupkM7buM3gMNtoZ1VuJTRDE4=", + "lastModified": 1724561770, + "narHash": "sha256-zv8C9RNa86CIpyHwPIVO/k+5TfM8ZbjGwOOpTe1grls=", "owner": "lnl7", "repo": "nix-darwin", - "rev": "a8968d88e5a537b0491f68ce910749cd870bdbef", + "rev": "ac5694a0b855a981e81b4d9f14052e3ff46ca39e", "type": "github" }, "original": { @@ -330,16 +408,16 @@ }, "nixpkgs": { "locked": { - "lastModified": 1724242322, - "narHash": "sha256-HMpK7hNjhEk4z5SFg5UtxEio9OWFocHdaQzCfW1pE7w=", - "owner": "nixos", + "lastModified": 1704161960, + "narHash": "sha256-QGua89Pmq+FBAro8NriTuoO/wNaUtugt29/qqA8zeeM=", + "owner": "NixOS", "repo": "nixpkgs", - "rev": "224042e9a3039291f22f4f2ded12af95a616cca0", + "rev": "63143ac2c9186be6d9da6035fa22620018c85932", "type": "github" }, "original": { - "owner": "nixos", - "ref": "nixos-24.05", + "owner": "NixOS", + "ref": "nixpkgs-unstable", "repo": "nixpkgs", "type": "github" } @@ -372,6 +450,40 @@ "url": "https://github.com/NixOS/nixpkgs/archive/a5d394176e64ab29c852d03346c1fc9b0b7d33eb.tar.gz" } }, + "nixpkgs-lib_2": { + "locked": { + "dir": "lib", + "lastModified": 1711703276, + "narHash": "sha256-iMUFArF0WCatKK6RzfUJknjem0H9m4KgorO/p3Dopkk=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d8fe5e6c92d0d190646fb9f1056741a229980089", + "type": "github" + }, + "original": { + "dir": "lib", + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1724316499, + "narHash": "sha256-Qb9MhKBUTCfWg/wqqaxt89Xfi6qTD3XpTzQ9eXi3JmE=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "797f7dc49e0bc7fab4b57c021cdf68f595e47841", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-24.05", + "repo": "nixpkgs", + "type": "github" + } + }, "root": { "inputs": { "agenix": "agenix", @@ -380,11 +492,12 @@ "element-themes": "element-themes", "flake-parts": "flake-parts", "home-manager": "home-manager", + "keycloak-event-listener": "keycloak-event-listener", "keycloak-theme-pub-solar": "keycloak-theme-pub-solar", "maunium-stickerpicker": "maunium-stickerpicker", "nix-darwin": "nix-darwin", "nixos-flake": "nixos-flake", - "nixpkgs": "nixpkgs", + "nixpkgs": "nixpkgs_2", "nixpkgs-2205": "nixpkgs-2205", "simple-nixos-mailserver": "simple-nixos-mailserver", "triton-vmtools": "triton-vmtools", @@ -493,9 +606,24 @@ "type": "github" } }, + "systems_6": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + }, "triton-vmtools": { "inputs": { - "flake-utils": "flake-utils_3", + "flake-utils": "flake-utils_4", "nixpkgs": [ "nixpkgs" ] @@ -553,7 +681,7 @@ }, "utils_2": { "inputs": { - "systems": "systems_5" + "systems": "systems_6" }, "locked": { "lastModified": 1709126324, diff --git a/flake.nix b/flake.nix index a86c7a9..4ac9e41 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,9 @@ keycloak-theme-pub-solar.url = "git+https://git.pub.solar/pub-solar/keycloak-theme?ref=main"; keycloak-theme-pub-solar.inputs.nixpkgs.follows = "nixpkgs"; + keycloak-event-listener.url = "git+https://git.pub.solar/pub-solar/keycloak-event-listener?ref=main"; + keycloak-event-listener.inputs.nixpkgs.follows = "unstable"; + triton-vmtools.url = "git+https://git.pub.solar/pub-solar/infra-vintage?ref=main&dir=vmtools"; triton-vmtools.inputs.nixpkgs.follows = "nixpkgs"; diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 59d924b..b567f59 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -56,6 +56,9 @@ "pub.solar" = flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; }; + plugins = [ + flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener + ]; }; pub-solar-os.backups.backups.keycloak = { -- 2.44.2 From ec01fe5eea5d2996cd1f4d8a82a5ddb204dc2bd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20Yule=20B=C3=A4dorf?= Date: Sun, 28 Apr 2024 16:04:15 +0200 Subject: [PATCH 13/21] test: add initial e2e test for nachtigall --- flake.nix | 25 +------- hosts/default.nix | 11 ++++ hosts/nachtigall/apps/keycloak.nix | 98 ++++++++++++++++++++++++++++++ hosts/nachtigall/test-vm.nix | 54 ++++++++++++++++ lib/default.nix | 3 +- modules/test-vm.nix | 5 ++ tests/default.nix | 19 ++++++ tests/website.nix | 22 +++++++ 8 files changed, 213 insertions(+), 24 deletions(-) create mode 100644 hosts/nachtigall/apps/keycloak.nix create mode 100644 hosts/nachtigall/test-vm.nix create mode 100644 modules/test-vm.nix create mode 100644 tests/default.nix diff --git a/flake.nix b/flake.nix index 4ac9e41..4fd8be2 100644 --- a/flake.nix +++ b/flake.nix @@ -64,7 +64,7 @@ ]; perSystem = - { + args@{ system, pkgs, config, @@ -82,25 +82,7 @@ master = import inputs.master { inherit system; }; }; - packages = - let - nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; - testDir = builtins.attrNames (builtins.readDir ./tests); - testFiles = builtins.filter (n: builtins.match "^.*.nix$" n != null) testDir; - in - builtins.listToAttrs ( - map (x: { - name = "test-${lib.strings.removeSuffix ".nix" x}"; - value = nixos-lib.runTest ( - import (./tests + "/${x}") { - inherit self; - inherit pkgs; - inherit lib; - inherit config; - } - ); - }) testFiles - ); + packages = import ./tests ({ inherit inputs self; } // args); devShells.default = pkgs.mkShell { buildInputs = with pkgs; [ @@ -124,8 +106,7 @@ devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; }; }; - flake = - let + flake = let username = "barkeeper"; in { diff --git a/hosts/default.nix b/hosts/default.nix index c8aaf1c..764662d 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -43,6 +43,17 @@ ]; }; + nachtigall-test = { + imports = [ + self.inputs.agenix.nixosModules.default + self.nixosModules.home-manager + ./nachtigall/test-vm.nix + self.nixosModules.overlays + self.nixosModules.core + self.nixosModules.docker + ]; + }; + flora-6 = self.nixos-flake.lib.mkLinuxSystem { imports = [ self.inputs.agenix.nixosModules.default diff --git a/hosts/nachtigall/apps/keycloak.nix b/hosts/nachtigall/apps/keycloak.nix new file mode 100644 index 0000000..7f1452d --- /dev/null +++ b/hosts/nachtigall/apps/keycloak.nix @@ -0,0 +1,98 @@ +{ flake +, config +, lib +, pkgs +, ... +}: { + age.secrets.keycloak-database-password = { + file = "${flake.self}/secrets/keycloak-database-password.age"; + mode = "600"; + #owner = "keycloak"; + }; + + services.nginx.virtualHosts."auth.pub.solar" = { + enableACME = true; + forceSSL = true; + + locations = { + "= /" = { + extraConfig = '' + return 302 /realms/pub.solar/account; + ''; + }; + + "/" = { + extraConfig = '' + proxy_pass http://127.0.0.1:8080; + proxy_buffer_size 8k; + ''; + }; + }; + }; + + services.keycloak = { + enable = true; + database.passwordFile = config.age.secrets.keycloak-database-password.path; + settings = { + hostname = "auth.pub.solar"; + http-host = "127.0.0.1"; + http-port = 8080; + proxy = "edge"; + features = "declarative-user-profile"; + }; + themes = { + "pub.solar" = flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; + }; + plugins = [ + flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener + ]; + }; + + services.restic.backups.keycloak-droppie = { + paths = [ + "/tmp/keycloak-backup.sql" + ]; + timerConfig = { + OnCalendar = "*-*-* 02:00:00 Etc/UTC"; + # droppie will be offline if nachtigall misses the timer + Persistent = false; + }; + initialize = true; + passwordFile = config.age.secrets."restic-repo-droppie".path; + repository = "sftp:yule@droppie.b12f.io:/media/internal/pub.solar"; + backupPrepareCommand = '' + ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql + ''; + backupCleanupCommand = '' + rm /tmp/keycloak-backup.sql + ''; + pruneOpts = [ + "--keep-daily 7" + "--keep-weekly 4" + "--keep-monthly 3" + ]; + }; + + services.restic.backups.keycloak-storagebox = { + paths = [ + "/tmp/keycloak-backup.sql" + ]; + timerConfig = { + OnCalendar = "*-*-* 04:10:00 Etc/UTC"; + }; + initialize = true; + passwordFile = config.age.secrets."restic-repo-storagebox".path; + repository = "sftp:u377325@u377325.your-storagebox.de:/backups"; + backupPrepareCommand = '' + ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql + ''; + backupCleanupCommand = '' + rm /tmp/keycloak-backup.sql + ''; + pruneOpts = [ + "--keep-daily 7" + "--keep-weekly 4" + "--keep-monthly 3" + ]; + }; +} diff --git a/hosts/nachtigall/test-vm.nix b/hosts/nachtigall/test-vm.nix new file mode 100644 index 0000000..038df61 --- /dev/null +++ b/hosts/nachtigall/test-vm.nix @@ -0,0 +1,54 @@ +{ flake, lib, ... }: + +{ + imports = + [ + ./backups.nix + ./apps/nginx.nix + + ./apps/collabora.nix + ./apps/coturn.nix + ./apps/forgejo.nix + ./apps/keycloak.nix + ./apps/mailman.nix + ./apps/mastodon.nix + ./apps/mediawiki.nix + ./apps/nextcloud.nix + ./apps/nginx-mastodon.nix + ./apps/nginx-mastodon-files.nix + ./apps/nginx-prometheus-exporters.nix + ./apps/nginx-website.nix + ./apps/nginx-website-miom.nix + ./apps/opensearch.nix + ./apps/owncast.nix + ./apps/postgresql.nix + ./apps/prometheus-exporters.nix + ./apps/promtail.nix + ./apps/searx.nix + ./apps/tmate.nix + + ./apps/matrix/irc.nix + ./apps/matrix/mautrix-telegram.nix + ./apps/matrix/synapse.nix + ./apps/nginx-matrix.nix + ]; + + nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; + + security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; + security.acme.preliminarySelfsigned = true; + + networking.useDHCP = true; + networking.interfaces."enp35s0".ipv4.addresses = [ + { + address = "10.0.0.1"; + prefixLength = 26; + } + ]; + networking.interfaces."enp35s0".ipv6.addresses = [ + { + address = "2a01:4f8:172:1c25::1"; + prefixLength = 64; + } + ]; +} diff --git a/lib/default.nix b/lib/default.nix index 3f14bf6..e839748 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -7,8 +7,7 @@ { # Configuration common to all Linux systems flake = { - lib = - let + lib = let callLibs = file: import file { inherit lib; }; in rec { diff --git a/modules/test-vm.nix b/modules/test-vm.nix new file mode 100644 index 0000000..b6e9a13 --- /dev/null +++ b/modules/test-vm.nix @@ -0,0 +1,5 @@ +{ ... }: +{ + security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; + security.acme.preliminarySelfsigned = true; +} diff --git a/tests/default.nix b/tests/default.nix new file mode 100644 index 0000000..9de3431 --- /dev/null +++ b/tests/default.nix @@ -0,0 +1,19 @@ +args@{ + self, + lib, + system, + pkgs, + inputs, + ... +}: let + nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; + + loadTestFiles = with lib; dir: mapAttrs' (name: _: let + test = ((import (dir + "/${name}")) args); + in { + name = "test-" + (lib.strings.removeSuffix ".nix" name); + value = nixos-lib.runTest test; + }) + (filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") + (builtins.readDir dir)); +in loadTestFiles ./. diff --git a/tests/website.nix b/tests/website.nix index 452262b..15459d0 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -1,4 +1,5 @@ { +<<<<<<< HEAD self, pkgs, lib, @@ -55,5 +56,26 @@ nachtigall.succeed("ping ca.test.pub.solar -c 2") nachtigall.wait_for_unit("nginx.service") nachtigall.wait_until_succeeds("curl https://test.pub.solar/") +======= + self, + pkgs, + lib, + config, + ... +}: { + name = "website"; + + nodes.nachtigall-test = self.nixosConfigurations.nachtigall-test; + + node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; + hostPkgs = pkgs; + + enableOCR = true; + + testScript = '' + machine.wait_for_unit("system.slice") + machine.succeed("ping 127.0.0.1 -c 2") + machine.succeed("ping iregendeinscheiss.de -c 2") +>>>>>>> af599c9 (test: add initial e2e test for nachtigall) ''; } -- 2.44.2 From b30dc0f7bdf14b40d17e5bb1765fb4a1ec5e5bc5 Mon Sep 17 00:00:00 2001 From: b12f Date: Mon, 26 Aug 2024 19:00:57 +0200 Subject: [PATCH 14/21] test: puppeteering puppeteer from host python testScript --- .gitignore | 1 + flake.lock | 8 +- flake.nix | 5 + hosts/nachtigall/apps/keycloak.nix | 98 -- tests/keycloak.nix | 6 +- tests/support/client.nix | 46 +- tests/support/puppeteer-socket/.gitignore | 1 + tests/support/puppeteer-socket/.npmignore | 1 + .../puppeteer-socket/package-lock.json | 926 ++++++++++++++++++ tests/support/puppeteer-socket/package.json | 17 + .../puppeteer-socket/puppeteer-run.nix | 8 + .../puppeteer-socket/puppeteer-socket.nix | 11 + tests/support/puppeteer-socket/src/index.mjs | 57 ++ tests/website.nix | 22 - 14 files changed, 1066 insertions(+), 141 deletions(-) delete mode 100644 hosts/nachtigall/apps/keycloak.nix create mode 100644 tests/support/puppeteer-socket/.gitignore create mode 100644 tests/support/puppeteer-socket/.npmignore create mode 100644 tests/support/puppeteer-socket/package-lock.json create mode 100644 tests/support/puppeteer-socket/package.json create mode 100644 tests/support/puppeteer-socket/puppeteer-run.nix create mode 100644 tests/support/puppeteer-socket/puppeteer-socket.nix create mode 100644 tests/support/puppeteer-socket/src/index.mjs diff --git a/.gitignore b/.gitignore index 27fd9e3..eba9541 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ *.plan result results +.nixos-test-history diff --git a/flake.lock b/flake.lock index 1f71268..43c99af 100644 --- a/flake.lock +++ b/flake.lock @@ -314,11 +314,11 @@ ] }, "locked": { - "lastModified": 1714263025, - "narHash": "sha256-Uesrz49RwbG7sHgiHkkb5o364BN9WbuwroWxVXdcfvo=", + "lastModified": 1724595780, + "narHash": "sha256-c6XxFH+qo3SbstKAFLcvGn3GHVJxbuXE2VtBnrjBk10=", "ref": "main", - "rev": "fb569f474698b5711c208fd5b4b5880d64863587", - "revCount": 2, + "rev": "f2a3da5f2637a859897c136e650b88046a89f9fd", + "revCount": 4, "type": "git", "url": "https://git.pub.solar/pub-solar/keycloak-event-listener" }, diff --git a/flake.nix b/flake.nix index 4fd8be2..d747be5 100644 --- a/flake.nix +++ b/flake.nix @@ -100,6 +100,11 @@ nixos-generators inputs.nixpkgs-2205.legacyPackages.${system}.terraform jq + + # For the tests puppeteer-socket pkg + nodejs + nodePackages.typescript + nodePackages.typescript-language-server ]; }; diff --git a/hosts/nachtigall/apps/keycloak.nix b/hosts/nachtigall/apps/keycloak.nix deleted file mode 100644 index 7f1452d..0000000 --- a/hosts/nachtigall/apps/keycloak.nix +++ /dev/null @@ -1,98 +0,0 @@ -{ flake -, config -, lib -, pkgs -, ... -}: { - age.secrets.keycloak-database-password = { - file = "${flake.self}/secrets/keycloak-database-password.age"; - mode = "600"; - #owner = "keycloak"; - }; - - services.nginx.virtualHosts."auth.pub.solar" = { - enableACME = true; - forceSSL = true; - - locations = { - "= /" = { - extraConfig = '' - return 302 /realms/pub.solar/account; - ''; - }; - - "/" = { - extraConfig = '' - proxy_pass http://127.0.0.1:8080; - proxy_buffer_size 8k; - ''; - }; - }; - }; - - services.keycloak = { - enable = true; - database.passwordFile = config.age.secrets.keycloak-database-password.path; - settings = { - hostname = "auth.pub.solar"; - http-host = "127.0.0.1"; - http-port = 8080; - proxy = "edge"; - features = "declarative-user-profile"; - }; - themes = { - "pub.solar" = flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; - }; - plugins = [ - flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener - ]; - }; - - services.restic.backups.keycloak-droppie = { - paths = [ - "/tmp/keycloak-backup.sql" - ]; - timerConfig = { - OnCalendar = "*-*-* 02:00:00 Etc/UTC"; - # droppie will be offline if nachtigall misses the timer - Persistent = false; - }; - initialize = true; - passwordFile = config.age.secrets."restic-repo-droppie".path; - repository = "sftp:yule@droppie.b12f.io:/media/internal/pub.solar"; - backupPrepareCommand = '' - ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql - ''; - backupCleanupCommand = '' - rm /tmp/keycloak-backup.sql - ''; - pruneOpts = [ - "--keep-daily 7" - "--keep-weekly 4" - "--keep-monthly 3" - ]; - }; - - services.restic.backups.keycloak-storagebox = { - paths = [ - "/tmp/keycloak-backup.sql" - ]; - timerConfig = { - OnCalendar = "*-*-* 04:10:00 Etc/UTC"; - }; - initialize = true; - passwordFile = config.age.secrets."restic-repo-storagebox".path; - repository = "sftp:u377325@u377325.your-storagebox.de:/backups"; - backupPrepareCommand = '' - ${pkgs.sudo}/bin/sudo -u postgres ${pkgs.postgresql}/bin/pg_dump -d keycloak > /tmp/keycloak-backup.sql - ''; - backupCleanupCommand = '' - rm /tmp/keycloak-backup.sql - ''; - pruneOpts = [ - "--keep-daily 7" - "--keep-weekly 4" - "--keep-monthly 3" - ]; - }; -} diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 5e735fd..f333e1d 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -85,8 +85,10 @@ in nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") client.wait_for_unit("system.slice") - client.sleep(30) - # client.wait_until_succeeds("${wmClass} | grep -q 'firefox'") + client.wait_for_file("/tmp/puppeteer.sock") + client.succeed("puppeteer-run 'console.log(1234)'") + client.succeed("puppeteer-run 'page.goto(\"https://auth.test.pub.solar\")'") + client.succeed("puppeteer-run 'page.waitForSelector(\"body\")'") client.screenshot("screen") ''; } diff --git a/tests/support/client.nix b/tests/support/client.nix index 41e97f0..fab5631 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -3,24 +3,40 @@ lib, config, ... -}: -{ +}: let + puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) {}); + puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) {}); +in { imports = [ ./global.nix ]; - services.xserver.enable = true; - services.xserver.displayManager.gdm.enable = true; - services.xserver.desktopManager.gnome.enable = true; - services.xserver.displayManager.autoLogin.enable = true; - services.xserver.displayManager.autoLogin.user = config.pub-solar-os.authentication.username; + security.polkit.enable = true; - systemd.user.services = { - "org.gnome.Shell@wayland" = { - serviceConfig = { - ExecStart = [ - # Clear the list before overriding it. - "" - # Eval API is now internal so Shell needs to run in unsafe mode. - "${pkgs.gnome.gnome-shell}/bin/gnome-shell --unsafe-mode" + environment.systemPackages = [ + puppeteer-run + pkgs.alacritty + ]; + + services.getty.autologinUser = config.pub-solar-os.authentication.username; + + virtualisation.qemu.options = [ + "-vga std" + ]; + + home-manager.users.${config.pub-solar-os.authentication.username} = { + programs.bash.profileExtra = '' + [ "$(tty)" = "/dev/tty1" ] && exec systemd-cat --identifier=sway ${pkgs.sway}/bin/sway + ''; + + wayland.windowManager.sway = { + enable = true; + extraSessionCommands = '' + export WLR_RENDERER=pixman + ''; + config = { + modifier = "Mod4"; + terminal = "${pkgs.alacritty}/bin/alacritty"; + startup = [ + {command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket";} ]; }; }; diff --git a/tests/support/puppeteer-socket/.gitignore b/tests/support/puppeteer-socket/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/tests/support/puppeteer-socket/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/tests/support/puppeteer-socket/.npmignore b/tests/support/puppeteer-socket/.npmignore new file mode 100644 index 0000000..82368a4 --- /dev/null +++ b/tests/support/puppeteer-socket/.npmignore @@ -0,0 +1 @@ +*.nix diff --git a/tests/support/puppeteer-socket/package-lock.json b/tests/support/puppeteer-socket/package-lock.json new file mode 100644 index 0000000..cae7947 --- /dev/null +++ b/tests/support/puppeteer-socket/package-lock.json @@ -0,0 +1,926 @@ +{ + "name": "puppeteer-socket", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "puppeteer-socket", + "version": "1.0.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "puppeteer-core": "^23.1.1" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/node": { + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", + "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", + "optional": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.4.tgz", + "integrity": "sha512-8zoq6ogmhQQkAKZVKO2ObFTl4uOkqoX1PlKQX3hZQ5E9cbUotcAb7h4pTNVAGGv8Z36PF3CtdOriEp/Rz82JqQ==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "23.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.1.1.tgz", + "integrity": "sha512-OeTqNiYGF9qZtwZU4Yc88DDqFJs4TJ4rnK81jkillh6MwDeQodyisM9xe5lBmPhwiDy92s5J5DQtQLjCKHFQ3g==", + "dependencies": { + "@puppeteer/browsers": "2.3.1", + "chromium-bidi": "0.6.4", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/streamx": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.19.0.tgz", + "integrity": "sha512-5z6CNR4gtkPbwlxyEqoDGDmWIzoNJqCBt4Eac1ICP9YaIT08ct712cFj0u1rx4F8luAuL+3Qc+RFIdI4OX00kg==", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "optional": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tests/support/puppeteer-socket/package.json b/tests/support/puppeteer-socket/package.json new file mode 100644 index 0000000..f0254c5 --- /dev/null +++ b/tests/support/puppeteer-socket/package.json @@ -0,0 +1,17 @@ +{ + "name": "puppeteer-socket", + "version": "1.0.0", + "main": "src/index.mjs", + "scripts": { + "start": "node src/index.mjs" + }, + "bin": { + "puppeteer-socket": "src/index.mjs" + }, + "author": "", + "license": "AGPL-3.0-or-later", + "description": "", + "dependencies": { + "puppeteer-core": "^23.1.1" + } +} diff --git a/tests/support/puppeteer-socket/puppeteer-run.nix b/tests/support/puppeteer-socket/puppeteer-run.nix new file mode 100644 index 0000000..dc1dabe --- /dev/null +++ b/tests/support/puppeteer-socket/puppeteer-run.nix @@ -0,0 +1,8 @@ +{ + writeShellScriptBin, + curl +}: writeShellScriptBin "puppeteer-run" '' +set -e + +exec ${curl}/bin/curl -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket +'' diff --git a/tests/support/puppeteer-socket/puppeteer-socket.nix b/tests/support/puppeteer-socket/puppeteer-socket.nix new file mode 100644 index 0000000..5193914 --- /dev/null +++ b/tests/support/puppeteer-socket/puppeteer-socket.nix @@ -0,0 +1,11 @@ +{ + buildNpmPackage, + nodejs, +}: +buildNpmPackage rec { + src = ./.; + name = "puppeteer-socket"; + nativeBuildInputs = [ nodejs ]; + npmDepsHash = "sha256-d+mbHdwt9V5JIBUw/2NyTMBlZ3D5UNE8TpVXJm8rcnU="; + dontNpmBuild = true; +} diff --git a/tests/support/puppeteer-socket/src/index.mjs b/tests/support/puppeteer-socket/src/index.mjs new file mode 100644 index 0000000..53e3a36 --- /dev/null +++ b/tests/support/puppeteer-socket/src/index.mjs @@ -0,0 +1,57 @@ +#!/usr/bin/env node + +import http from 'node:http'; +import puppeteer from 'puppeteer-core'; + +const PUPPETEER_SOCKET = '/tmp/puppeteer.sock'; +const EXECUTABLE = process.env.EXECUTABLE || 'firefox'; + +(async () => { + const firefoxBrowser = await puppeteer.launch({ + executablePath: EXECUTABLE, + headless: false, + devtools: true, + browser: 'firefox', + extraPrefsFirefox: {}, + }); + + const page = await firefoxBrowser.newPage(); + const actions = []; + + const server = http.createServer({}, (req, res) => { + const chunks = []; + req.on('data', (chunk) => { + console.log(`got data ${chunk}`); + chunks.push(chunk); + }); + + req.on('end', async () => { + try { + const content = chunks.join(''); + + console.log(`Executing ${content}`); + eval(`actions.push(${content})`); + + const val = await actions[actions.length - 1]; + + res.writeHead(200, { 'Content-Type': 'application/json' }); + let responseText; + try { + responseText = JSON.stringify({ data: val }); + } catch (err) { + responseText = val.toString(); + } + + res.end(responseText); + } catch (err) { + console.error(err); + res.writeHead(500, { 'Content-Type': 'application/json' }); + res.end(JSON.stringify({ error: err.toString() })); + } + }); + }); + + server.listen(PUPPETEER_SOCKET, () => { + console.log('Listening!'); + }); +})(); diff --git a/tests/website.nix b/tests/website.nix index 15459d0..452262b 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -1,5 +1,4 @@ { -<<<<<<< HEAD self, pkgs, lib, @@ -56,26 +55,5 @@ nachtigall.succeed("ping ca.test.pub.solar -c 2") nachtigall.wait_for_unit("nginx.service") nachtigall.wait_until_succeeds("curl https://test.pub.solar/") -======= - self, - pkgs, - lib, - config, - ... -}: { - name = "website"; - - nodes.nachtigall-test = self.nixosConfigurations.nachtigall-test; - - node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; - hostPkgs = pkgs; - - enableOCR = true; - - testScript = '' - machine.wait_for_unit("system.slice") - machine.succeed("ping 127.0.0.1 -c 2") - machine.succeed("ping iregendeinscheiss.de -c 2") ->>>>>>> af599c9 (test: add initial e2e test for nachtigall) ''; } -- 2.44.2 From d3f5308eaf657241f3ab6a8f93ce6a1f3185d874 Mon Sep 17 00:00:00 2001 From: b12f Date: Mon, 26 Aug 2024 22:49:46 +0200 Subject: [PATCH 15/21] tests: add better keycloak and puppeteer support --- modules/keycloak/default.nix | 3 + modules/keycloak/keycloak.nix | 705 ++++ tests/keycloak.nix | 32 +- .../support/keycloak-realm-export/.gitignore | 1 + .../support/keycloak-realm-export/.npmignore | 1 + tests/support/keycloak-realm-export/README.md | 5 + .../keycloak-realm-export/package-lock.json | 942 +++++ .../keycloak-realm-export/package.json | 18 + .../keycloak-realm-export/realm-export.json | 3215 +++++++++++++++++ .../keycloak-realm-export/src/index.mjs | 69 + .../puppeteer-socket/puppeteer-run.nix | 2 +- tests/support/puppeteer-socket/src/index.mjs | 7 + 12 files changed, 4995 insertions(+), 5 deletions(-) create mode 100644 modules/keycloak/keycloak.nix create mode 100644 tests/support/keycloak-realm-export/.gitignore create mode 100644 tests/support/keycloak-realm-export/.npmignore create mode 100644 tests/support/keycloak-realm-export/README.md create mode 100644 tests/support/keycloak-realm-export/package-lock.json create mode 100644 tests/support/keycloak-realm-export/package.json create mode 100644 tests/support/keycloak-realm-export/realm-export.json create mode 100644 tests/support/keycloak-realm-export/src/index.mjs diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index b567f59..226c278 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -6,6 +6,9 @@ ... }: { + disabledModules = [ "services/web-apps/keycloak.nix" ]; + imports = [ ./keycloak.nix ]; + options.pub-solar-os.auth = with lib; { enable = mkEnableOption "Enable keycloak to run on the node"; diff --git a/modules/keycloak/keycloak.nix b/modules/keycloak/keycloak.nix new file mode 100644 index 0000000..98901a6 --- /dev/null +++ b/modules/keycloak/keycloak.nix @@ -0,0 +1,705 @@ +{ config, options, pkgs, lib, ... }: + +let + cfg = config.services.keycloak; + opt = options.services.keycloak; + + inherit (lib) + types + mkMerge + mkOption + mkChangedOptionModule + mkRenamedOptionModule + mkRemovedOptionModule + mkPackageOption + concatStringsSep + mapAttrsToList + escapeShellArg + mkIf + optionalString + optionals + mkDefault + literalExpression + isAttrs + literalMD + maintainers + catAttrs + collect + hasPrefix + ; + + inherit (builtins) + elem + typeOf + isInt + isString + hashString + isPath + ; + + prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}"; +in +{ + imports = + [ + (mkRenamedOptionModule + [ "services" "keycloak" "bindAddress" ] + [ "services" "keycloak" "settings" "http-host" ]) + (mkRenamedOptionModule + [ "services" "keycloak" "forceBackendUrlToFrontendUrl"] + [ "services" "keycloak" "settings" "hostname-strict-backchannel"]) + (mkChangedOptionModule + [ "services" "keycloak" "httpPort" ] + [ "services" "keycloak" "settings" "http-port" ] + (config: + builtins.fromJSON config.services.keycloak.httpPort)) + (mkChangedOptionModule + [ "services" "keycloak" "httpsPort" ] + [ "services" "keycloak" "settings" "https-port" ] + (config: + builtins.fromJSON config.services.keycloak.httpsPort)) + (mkRemovedOptionModule + [ "services" "keycloak" "frontendUrl" ] + '' + Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. + NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. + See its description for more information. + '') + (mkRemovedOptionModule + [ "services" "keycloak" "extraConfig" ] + "Use `services.keycloak.settings' instead.") + ]; + + options.services.keycloak = + let + inherit (types) + bool + str + int + nullOr + attrsOf + oneOf + path + enum + package + port; + + assertStringPath = optionName: value: + if isPath value then + throw '' + services.keycloak.${optionName}: + ${toString value} + is a Nix path, but should be a string, since Nix + paths are copied into the world-readable Nix store. + '' + else value; + in + { + enable = mkOption { + type = bool; + default = false; + example = true; + description = '' + Whether to enable the Keycloak identity and access management + server. + ''; + }; + + sslCertificate = mkOption { + type = nullOr path; + default = null; + example = "/run/keys/ssl_cert"; + apply = assertStringPath "sslCertificate"; + description = '' + The path to a PEM formatted certificate to use for TLS/SSL + connections. + ''; + }; + + sslCertificateKey = mkOption { + type = nullOr path; + default = null; + example = "/run/keys/ssl_key"; + apply = assertStringPath "sslCertificateKey"; + description = '' + The path to a PEM formatted private key to use for TLS/SSL + connections. + ''; + }; + + plugins = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + description = '' + Keycloak plugin jar, ear files or derivations containing + them. Packaged plugins are available through + `pkgs.keycloak.plugins`. + ''; + }; + + database = { + type = mkOption { + type = enum [ "mysql" "mariadb" "postgresql" ]; + default = "postgresql"; + example = "mariadb"; + description = '' + The type of database Keycloak should connect to. + ''; + }; + + host = mkOption { + type = str; + default = "localhost"; + description = '' + Hostname of the database to connect to. + ''; + }; + + port = + let + dbPorts = { + postgresql = 5432; + mariadb = 3306; + mysql = 3306; + }; + in + mkOption { + type = port; + default = dbPorts.${cfg.database.type}; + defaultText = literalMD "default port of selected database"; + description = '' + Port of the database to connect to. + ''; + }; + + useSSL = mkOption { + type = bool; + default = cfg.database.host != "localhost"; + defaultText = literalExpression ''config.${opt.database.host} != "localhost"''; + description = '' + Whether the database connection should be secured by SSL / + TLS. + ''; + }; + + caCert = mkOption { + type = nullOr path; + default = null; + description = '' + The SSL / TLS CA certificate that verifies the identity of the + database server. + + Required when PostgreSQL is used and SSL is turned on. + + For MySQL, if left at `null`, the default + Java keystore is used, which should suffice if the server + certificate is issued by an official CA. + ''; + }; + + createLocally = mkOption { + type = bool; + default = true; + description = '' + Whether a database should be automatically created on the + local host. Set this to false if you plan on provisioning a + local database yourself. This has no effect if + services.keycloak.database.host is customized. + ''; + }; + + name = mkOption { + type = str; + default = "keycloak"; + description = '' + Database name to use when connecting to an external or + manually provisioned database; has no effect when a local + database is automatically provisioned. + + To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to + `false` and create the database and user + manually. + ''; + }; + + username = mkOption { + type = str; + default = "keycloak"; + description = '' + Username to use when connecting to an external or manually + provisioned database; has no effect when a local database is + automatically provisioned. + + To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to + `false` and create the database and user + manually. + ''; + }; + + passwordFile = mkOption { + type = path; + example = "/run/keys/db_password"; + apply = assertStringPath "passwordFile"; + description = '' + The path to a file containing the database password. + ''; + }; + }; + + package = mkPackageOption pkgs "keycloak" { }; + + initialAdminPassword = mkOption { + type = str; + default = "changeme"; + description = '' + Initial password set for the `admin` + user. The password is not stored safely and should be changed + immediately in the admin panel. + ''; + }; + + themes = mkOption { + type = attrsOf package; + default = { }; + description = '' + Additional theme packages for Keycloak. Each theme is linked into + subdirectory with a corresponding attribute name. + + Theme packages consist of several subdirectories which provide + different theme types: for example, `account`, + `login` etc. After adding a theme to this option you + can select it by its name in Keycloak administration console. + ''; + }; + + extraStartupFlags = lib.mkOption { + type = lib.types.listOf str; + default = [ ]; + description = '' + Extra flags to be added to the startup command kc.sh. + This can be used to import a realm during startup or to + set configuration variables, see . + + --verbose and --optimized are always added. + ''; + }; + + settings = mkOption { + type = lib.types.submodule { + freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ])); + + options = { + http-host = mkOption { + type = str; + default = "0.0.0.0"; + example = "127.0.0.1"; + description = '' + On which address Keycloak should accept new connections. + ''; + }; + + http-port = mkOption { + type = port; + default = 80; + example = 8080; + description = '' + On which port Keycloak should listen for new HTTP connections. + ''; + }; + + https-port = mkOption { + type = port; + default = 443; + example = 8443; + description = '' + On which port Keycloak should listen for new HTTPS connections. + ''; + }; + + http-relative-path = mkOption { + type = str; + default = "/"; + example = "/auth"; + apply = x: if !(hasPrefix "/") x then "/" + x else x; + description = '' + The path relative to `/` for serving + resources. + + ::: {.note} + In versions of Keycloak using Wildfly (<17), + this defaulted to `/auth`. If + upgrading from the Wildfly version of Keycloak, + i.e. a NixOS version before 22.05, you'll likely + want to set this to `/auth` to + keep compatibility with your clients. + + See + for more information on migrating from Wildfly to Quarkus. + ::: + ''; + }; + + hostname = mkOption { + type = nullOr str; + example = "keycloak.example.com"; + description = '' + The hostname part of the public URL used as base for + all frontend requests. + + See + for more information about hostname configuration. + ''; + }; + + hostname-backchannel-dynamic = mkOption { + type = bool; + default = false; + example = true; + description = '' + Enables dynamic resolving of backchannel URLs, + including hostname, scheme, port and context path. + + See + for more information about hostname configuration. + ''; + }; + + proxy = mkOption { + type = enum [ "edge" "reencrypt" "passthrough" "none" ]; + default = "none"; + example = "edge"; + description = '' + The proxy address forwarding mode if the server is + behind a reverse proxy. + + - `edge`: + Enables communication through HTTP between the + proxy and Keycloak. + - `reencrypt`: + Requires communication through HTTPS between the + proxy and Keycloak. + - `passthrough`: + Enables communication through HTTP or HTTPS between + the proxy and Keycloak. + + See for more information. + ''; + }; + }; + }; + + example = literalExpression '' + { + hostname = "keycloak.example.com"; + proxy = "reencrypt"; + https-key-store-file = "/path/to/file"; + https-key-store-password = { _secret = "/run/keys/store_password"; }; + } + ''; + + description = '' + Configuration options corresponding to parameters set in + {file}`conf/keycloak.conf`. + + Most available options are documented at . + + Options containing secret data should be set to an attribute + set containing the attribute `_secret` - a + string pointing to a file containing the value the option + should be set to. See the example to get a better picture of + this: in the resulting + {file}`conf/keycloak.conf` file, the + `https-key-store-password` key will be set + to the contents of the + {file}`/run/keys/store_password` file. + ''; + }; + }; + + config = + let + # We only want to create a database if we're actually going to + # connect to it. + databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost"; + createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql"; + createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ]; + + mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } '' + ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt + ''; + + # Both theme and theme type directories need to be actual + # directories in one hierarchy to pass Keycloak checks. + themesBundle = pkgs.runCommand "keycloak-themes" { } '' + linkTheme() { + theme="$1" + name="$2" + + mkdir "$out/$name" + for typeDir in "$theme"/*; do + if [ -d "$typeDir" ]; then + type="$(basename "$typeDir")" + mkdir "$out/$name/$type" + for file in "$typeDir"/*; do + ln -sn "$file" "$out/$name/$type/$(basename "$file")" + done + fi + done + } + + mkdir -p "$out" + for theme in ${keycloakBuild}/themes/*; do + if [ -d "$theme" ]; then + linkTheme "$theme" "$(basename "$theme")" + fi + done + + ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)} + ''; + + keycloakConfig = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { + mkValueString = v: + if isInt v then toString v + else if isString v then v + else if true == v then "true" + else if false == v then "false" + else if isSecret v then hashString "sha256" v._secret + else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + }; + }; + + isSecret = v: isAttrs v && v ? _secret && isString v._secret; + filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings; + confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig); + keycloakBuild = cfg.package.override { + inherit confFile; + plugins = cfg.package.enabledPlugins ++ cfg.plugins ++ + (with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]); + }; + in + mkIf cfg.enable + { + assertions = [ + { + assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); + message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; + } + { + assertion = createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; + message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; + } + { + assertion = cfg.settings.hostname != null || ! cfg.settings.hostname-strict or true; + message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; + } + { + assertion = cfg.settings.hostname-url or null == null; + message = '' + The option `services.keycloak.settings.hostname-url' has been removed. + Set `services.keycloak.settings.hostname' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + { + assertion = cfg.settings.hostname-strict-backchannel or null == null; + message = '' + The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. + Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + ]; + + environment.systemPackages = [ keycloakBuild ]; + + services.keycloak.settings = + let + postgresParams = concatStringsSep "&" ( + optionals cfg.database.useSSL [ + "ssl=true" + ] ++ optionals (cfg.database.caCert != null) [ + "sslrootcert=${cfg.database.caCert}" + "sslmode=verify-ca" + ] + ); + mariadbParams = concatStringsSep "&" ([ + "characterEncoding=UTF-8" + ] ++ optionals cfg.database.useSSL [ + "useSSL=true" + "requireSSL=true" + "verifyServerCertificate=true" + ] ++ optionals (cfg.database.caCert != null) [ + "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}" + "trustCertificateKeyStorePassword=notsosecretpassword" + ]); + dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; + in + mkMerge [ + { + db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; + db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; + db-password._secret = cfg.database.passwordFile; + db-url-host = cfg.database.host; + db-url-port = toString cfg.database.port; + db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; + db-url-properties = prefixUnlessEmpty "?" dbProps; + db-url = null; + } + (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { + https-certificate-file = "/run/keycloak/ssl/ssl_cert"; + https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; + }) + ]; + + systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { + after = [ "postgresql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "postgresql.service" ]; + path = [ config.services.postgresql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; + }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + create_role="$(mktemp)" + trap 'rm -f "$create_role"' EXIT + + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" + + echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" + psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" + psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' + ''; + }; + + systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { + after = [ "mysql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "mysql.service" ]; + path = [ config.services.mysql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = config.services.mysql.user; + Group = config.services.mysql.group; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; + }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" + + ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" + echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" + echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" + echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" + ) | mysql -N + ''; + }; + + systemd.services.keycloak = + let + databaseServices = + if createLocalPostgreSQL then [ + "keycloakPostgreSQLInit.service" + "postgresql.service" + ] + else if createLocalMySQL then [ + "keycloakMySQLInit.service" + "mysql.service" + ] + else [ ]; + secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); + mkSecretReplacement = file: '' + replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; + in + { + after = databaseServices; + bindsTo = databaseServices; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + keycloakBuild + openssl + replace-secret + ]; + environment = { + KC_HOME_DIR = "/run/keycloak"; + KC_CONF_DIR = "/run/keycloak/conf"; + }; + serviceConfig = { + LoadCredential = + map (p: "${baseNameOf p}:${p}") secretPaths + ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ + "ssl_cert:${cfg.sslCertificate}" + "ssl_key:${cfg.sslCertificateKey}" + ]; + User = "keycloak"; + Group = "keycloak"; + DynamicUser = true; + RuntimeDirectory = "keycloak"; + RuntimeDirectoryMode = "0700"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + Type = "notify"; # Requires quarkus-systemd-notify plugin + NotifyAccess = "all"; + }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit + + umask u=rwx,g=,o= + + ln -s ${themesBundle} /run/keycloak/themes + ln -s ${keycloakBuild}/providers /run/keycloak/ + + install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf + + ${secretReplacements} + + # Escape any backslashes in the db parameters, since + # they're otherwise unexpectedly read as escape + # sequences. + sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf + + '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' + mkdir -p /run/keycloak/ssl + cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ + '' + '' + export KEYCLOAK_ADMIN=admin + export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} + kc.sh --verbose start --optimized ${extraStartupFlags} + ''; + }; + + services.postgresql.enable = mkDefault createLocalPostgreSQL; + services.mysql.enable = mkDefault createLocalMySQL; + services.mysql.package = + let + dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; + in + mkIf createLocalMySQL (mkDefault dbPkg); + }; + + meta.doc = ./keycloak.md; + meta.maintainers = [ maintainers.talyz ]; +} diff --git a/tests/keycloak.nix b/tests/keycloak.nix index f333e1d..7c02565 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -6,6 +6,10 @@ ... }: let + realm-export = pkgs.writeTextFile { + name = "realm-export.json"; + text = builtins.readFile ./support/keycloak-realm-export/realm-export.json; + }; in { name = "keycloak"; @@ -53,6 +57,10 @@ in database-password-file = "/tmp/dbf"; }; services.keycloak.database.createLocally = true; + services.keycloak.extraStartupFlags = [ + "--import-realm" + "--file=${realm-export}" + ]; networking.interfaces.eth0.ipv4.addresses = [ { @@ -75,6 +83,9 @@ in wmClass = su "${gdbus} ${gseval} global.display.focus_window.wm_class"; in '' + def puppeteer_run(cmd): + client.succeed(f'puppeteer-run \'{cmd}\' ') + start_all() nachtigall.wait_for_unit("system.slice") @@ -86,9 +97,22 @@ in client.wait_for_unit("system.slice") client.wait_for_file("/tmp/puppeteer.sock") - client.succeed("puppeteer-run 'console.log(1234)'") - client.succeed("puppeteer-run 'page.goto(\"https://auth.test.pub.solar\")'") - client.succeed("puppeteer-run 'page.waitForSelector(\"body\")'") - client.screenshot("screen") + puppeteer_run('page.goto("https://auth.test.pub.solar")') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("initial") + puppeteer_run('page.locator("::-p-text(Sign in)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("sign-in") + puppeteer_run('page.locator("::-p-text(Register)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("register") + puppeteer_run('page.locator("[name=username]").fill("test-user")') + puppeteer_run('page.locator("[name=email]").fill("test-user@test.pub.solar")') + puppeteer_run('page.locator("[name=password]").fill("Password1234")') + puppeteer_run('page.locator("[name=password-confirm]").fill("Password1234")') + client.screenshot("register-filled-in") + puppeteer_run('page.locator("button::-p-text(Register)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("after-register") ''; } diff --git a/tests/support/keycloak-realm-export/.gitignore b/tests/support/keycloak-realm-export/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/tests/support/keycloak-realm-export/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/tests/support/keycloak-realm-export/.npmignore b/tests/support/keycloak-realm-export/.npmignore new file mode 100644 index 0000000..82368a4 --- /dev/null +++ b/tests/support/keycloak-realm-export/.npmignore @@ -0,0 +1 @@ +*.nix diff --git a/tests/support/keycloak-realm-export/README.md b/tests/support/keycloak-realm-export/README.md new file mode 100644 index 0000000..3edceaa --- /dev/null +++ b/tests/support/keycloak-realm-export/README.md @@ -0,0 +1,5 @@ +# Keycloak realm export anonymizer + +1. Export realm settings from keycloak, you'll get a file called `realm-export.json`. +2. Install dependencies for this package: `npm ci` +3. Clean the exported file: `npm start $downloadedExportJSON > realm-export.json diff --git a/tests/support/keycloak-realm-export/package-lock.json b/tests/support/keycloak-realm-export/package-lock.json new file mode 100644 index 0000000..ee879d3 --- /dev/null +++ b/tests/support/keycloak-realm-export/package-lock.json @@ -0,0 +1,942 @@ +{ + "name": "keycloak-realm-export", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "keycloak-realm-export", + "version": "1.0.0", + "license": "AGPL-3.0-or-later", + "dependencies": { + "puppeteer-core": "^23.1.1", + "uuid": "^10.0.0" + }, + "bin": { + "puppeteer-socket": "src/index.mjs" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dependencies": { + "debug": "^4.3.6", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@tootallnate/quickjs-emscripten": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", + "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==" + }, + "node_modules/@types/node": { + "version": "22.5.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.0.tgz", + "integrity": "sha512-DkFrJOe+rfdHTqqMg0bSNlGlQ85hSoh2TPzZyhHsXnMtligRWpxUySiyw8FY14ITt24HVCiQPWxS3KO/QlGmWg==", + "optional": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@types/yauzl": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", + "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", + "optional": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "dependencies": { + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", + "dependencies": { + "tslib": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==" + }, + "node_modules/bare-events": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", + "optional": true + }, + "node_modules/bare-fs": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", + "optional": true, + "dependencies": { + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^2.0.0" + } + }, + "node_modules/bare-os": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.4.0.tgz", + "integrity": "sha512-v8DTT08AS/G0F9xrhyLtepoo9EJBJ85FRSMbu1pQUlAf6A8T0tEEQGMVObWeqpjhSPXsE0VGlluFBJu2fdoTNg==", + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", + "optional": true, + "dependencies": { + "bare-os": "^2.1.0" + } + }, + "node_modules/bare-stream": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", + "optional": true, + "dependencies": { + "streamx": "^2.18.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "engines": { + "node": "*" + } + }, + "node_modules/chromium-bidi": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.4.tgz", + "integrity": "sha512-8zoq6ogmhQQkAKZVKO2ObFTl4uOkqoX1PlKQX3hZQ5E9cbUotcAb7h4pTNVAGGv8Z36PF3CtdOriEp/Rz82JqQ==", + "dependencies": { + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-uri-to-buffer": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", + "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", + "engines": { + "node": ">= 14" + } + }, + "node_modules/debug": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "dependencies": { + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", + "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "dependencies": { + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==" + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "dependencies": { + "pend": "~1.2.0" + } + }, + "node_modules/fs-extra": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", + "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "dependencies": { + "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-uri": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", + "integrity": "sha512-BzUrJBS9EcUb4cFol8r4W3v1cPsSyajLSthNkz5BxbpDcHN5tIrM10E2eNvfnvBn3DaT3DUgx0OpsBKkaOpanw==", + "dependencies": { + "basic-ftp": "^5.0.2", + "data-uri-to-buffer": "^6.0.2", + "debug": "^4.3.4", + "fs-extra": "^11.2.0" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + }, + "node_modules/http-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", + "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", + "dependencies": { + "agent-base": "^7.1.0", + "debug": "^4.3.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/ip-address": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", + "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", + "dependencies": { + "jsbn": "1.1.0", + "sprintf-js": "^1.1.3" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/jsbn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", + "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==" + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/netmask": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", + "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/pac-proxy-agent": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.0.2.tgz", + "integrity": "sha512-BFi3vZnO9X5Qt6NRz7ZOaPja3ic0PhlsmCRYLOpN11+mWBCR6XJDqW5RF3j8jm4WGGQZtBA+bTfxYzeKW73eHg==", + "dependencies": { + "@tootallnate/quickjs-emscripten": "^0.23.0", + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "get-uri": "^6.0.1", + "http-proxy-agent": "^7.0.0", + "https-proxy-agent": "^7.0.5", + "pac-resolver": "^7.0.1", + "socks-proxy-agent": "^8.0.4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pac-resolver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", + "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", + "dependencies": { + "degenerator": "^5.0.0", + "netmask": "^2.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + }, + "node_modules/progress": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", + "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/puppeteer-core": { + "version": "23.1.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-23.1.1.tgz", + "integrity": "sha512-OeTqNiYGF9qZtwZU4Yc88DDqFJs4TJ4rnK81jkillh6MwDeQodyisM9xe5lBmPhwiDy92s5J5DQtQLjCKHFQ3g==", + "dependencies": { + "@puppeteer/browsers": "2.3.1", + "chromium-bidi": "0.6.4", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "typed-query-selector": "^2.12.0", + "ws": "^8.18.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/queue-tick": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", + "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.3", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", + "integrity": "sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==", + "dependencies": { + "ip-address": "^9.0.5", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.4.tgz", + "integrity": "sha512-GNAq/eg8Udq2x0eNiFkr9gRg5bA7PXEWagQdeRX4cPSG+X/8V38v637gim9bjFptMk1QWsCTr0ttrJEiXbNnRw==", + "dependencies": { + "agent-base": "^7.1.1", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", + "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" + }, + "node_modules/streamx": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.19.0.tgz", + "integrity": "sha512-5z6CNR4gtkPbwlxyEqoDGDmWIzoNJqCBt4Eac1ICP9YaIT08ct712cFj0u1rx4F8luAuL+3Qc+RFIdI4OX00kg==", + "dependencies": { + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" + }, + "optionalDependencies": { + "bare-events": "^2.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", + "dependencies": { + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.1.tgz", + "integrity": "sha512-8zll7REEv4GDD3x4/0pW+ppIxSNs7H1J10IKFZsuOMscumCdM2a+toDGLPA3T+1+fLBql4zbt5z83GEQGGV5VA==", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==" + }, + "node_modules/tslib": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", + "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" + }, + "node_modules/typed-query-selector": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", + "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==" + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "optional": true + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==" + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, + "node_modules/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/tests/support/keycloak-realm-export/package.json b/tests/support/keycloak-realm-export/package.json new file mode 100644 index 0000000..0c54d2b --- /dev/null +++ b/tests/support/keycloak-realm-export/package.json @@ -0,0 +1,18 @@ +{ + "name": "keycloak-realm-export", + "version": "1.0.0", + "main": "src/index.mjs", + "scripts": { + "start": "node src/index.mjs" + }, + "bin": { + "puppeteer-socket": "src/index.mjs" + }, + "author": "", + "license": "AGPL-3.0-or-later", + "description": "", + "dependencies": { + "puppeteer-core": "^23.1.1", + "uuid": "^10.0.0" + } +} diff --git a/tests/support/keycloak-realm-export/realm-export.json b/tests/support/keycloak-realm-export/realm-export.json new file mode 100644 index 0000000..381456a --- /dev/null +++ b/tests/support/keycloak-realm-export/realm-export.json @@ -0,0 +1,3215 @@ +{ + "id": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "realm": "test.pub.solar", + "notBefore": 0, + "defaultSignatureAlgorithm": "RS256", + "revokeRefreshToken": false, + "refreshTokenMaxReuse": 0, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 43200, + "ssoSessionIdleTimeoutRememberMe": 7776000, + "ssoSessionMaxLifespanRememberMe": 31536000, + "offlineSessionIdleTimeout": 2592000, + "offlineSessionMaxLifespanEnabled": false, + "offlineSessionMaxLifespan": 5184000, + "clientSessionIdleTimeout": 0, + "clientSessionMaxLifespan": 0, + "clientOfflineSessionIdleTimeout": 0, + "clientOfflineSessionMaxLifespan": 0, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "actionTokenGeneratedByAdminLifespan": 43200, + "actionTokenGeneratedByUserLifespan": 300, + "oauth2DeviceCodeLifespan": 600, + "oauth2DevicePollingInterval": 5, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": true, + "verifyEmail": true, + "loginWithEmailAllowed": true, + "duplicateEmailsAllowed": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": false, + "bruteForceProtected": false, + "permanentLockout": false, + "maxTemporaryLockouts": 0, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "roles": { + "realm": [ + { + "id": "5e30b340-292f-4c23-982f-936b052634c1", + "name": "offline_access", + "description": "${role_offline-access}", + "composite": false, + "clientRole": false, + "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "attributes": {} + }, + { + "id": "49dd91a4-2176-4a84-aab0-37eb7f41fc1f", + "name": "default-roles-test.pub.solar", + "description": "${role_default-roles}", + "composite": true, + "composites": { + "realm": [ + "offline_access", + "uma_authorization" + ], + "client": { + "account": [ + "view-profile", + "manage-account" + ] + } + }, + "clientRole": false, + "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "attributes": {} + }, + { + "id": "541db75b-d73a-478c-bfbc-942b64d6286d", + "name": "admin", + "description": "Grafana admin role", + "composite": false, + "clientRole": false, + "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "attributes": {} + }, + { + "id": "ca6ef8b3-aeca-420a-86d5-edb6698d83ef", + "name": "uma_authorization", + "description": "${role_uma_authorization}", + "composite": false, + "clientRole": false, + "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "attributes": {} + } + ], + "client": { + "nextcloud": [], + "realm-management": [ + { + "id": "ae0cb0ed-998f-476d-b688-ac087a6ddc5a", + "name": "manage-users", + "description": "${role_manage-users}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "53b294e4-ab83-4c7f-ae21-e5df0d47d76d", + "name": "query-realms", + "description": "${role_query-realms}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "fce40cde-1df9-48b7-b18b-f61a95569f03", + "name": "view-events", + "description": "${role_view-events}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "471acf51-59c9-4e74-a470-8b9d650d7043", + "name": "view-users", + "description": "${role_view-users}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-users", + "query-groups" + ] + } + }, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "e2217f23-e8bf-44ab-ab43-6f3c6951b1ca", + "name": "manage-events", + "description": "${role_manage-events}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "07648931-6258-4276-ab5c-4b7f1aa66e44", + "name": "manage-realm", + "description": "${role_manage-realm}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "a3b51cd8-9a25-4361-9251-52dabdbf3af0", + "name": "view-clients", + "description": "${role_view-clients}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "query-clients" + ] + } + }, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "e5db750b-6f51-41ac-885d-054300c072b2", + "name": "view-realm", + "description": "${role_view-realm}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "cfd61589-7ed6-4fc2-83d0-27f3ca1e6bbd", + "name": "impersonation", + "description": "${role_impersonation}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "434e0ec3-9e6e-4358-8814-dc5b783ae2b3", + "name": "view-authorization", + "description": "${role_view-authorization}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "32988bf3-3f8d-4150-b3a2-e342ec9a0587", + "name": "query-groups", + "description": "${role_query-groups}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "fa821c09-19a3-48da-9980-c093ba931902", + "name": "manage-authorization", + "description": "${role_manage-authorization}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "317528d1-b1f5-43f9-b88b-6afdc53fd975", + "name": "create-client", + "description": "${role_create-client}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "c446519c-24d0-4d60-b4c0-401bf6dd80d6", + "name": "realm-admin", + "description": "${role_realm-admin}", + "composite": true, + "composites": { + "client": { + "realm-management": [ + "manage-users", + "query-realms", + "view-events", + "view-users", + "manage-realm", + "manage-events", + "view-clients", + "view-realm", + "impersonation", + "view-authorization", + "query-groups", + "manage-authorization", + "create-client", + "query-users", + "query-clients", + "view-identity-providers", + "manage-clients", + "manage-identity-providers" + ] + } + }, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "c197af85-bdb6-4caf-9e77-1631479e51db", + "name": "query-clients", + "description": "${role_query-clients}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "c5865ad3-936b-4506-b4eb-33b154b4837c", + "name": "query-users", + "description": "${role_query-users}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "90a4b005-4ecd-479d-9a8e-824a15735045", + "name": "view-identity-providers", + "description": "${role_view-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "56875e67-b1f4-49e2-b120-8ce33b5f4460", + "name": "manage-clients", + "description": "${role_manage-clients}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + }, + { + "id": "4d7dc40e-66b8-4712-8bde-8d8c504c39b7", + "name": "manage-identity-providers", + "description": "${role_manage-identity-providers}", + "composite": false, + "clientRole": true, + "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "attributes": {} + } + ], + "matrix-authentication-service": [], + "security-admin-console": [], + "account-console": [], + "tailscale": [], + "broker": [ + { + "id": "100f0a26-618b-4de8-a4f5-4dabbb6c034c", + "name": "read-token", + "description": "${role_read-token}", + "composite": false, + "clientRole": true, + "containerId": "2321d398-262d-4fd7-aef8-e6cc0ee017d7", + "attributes": {} + } + ], + "matrix": [ + { + "id": "8730c207-c839-4766-86f6-2e7006867ac9", + "name": "uma_protection", + "composite": false, + "clientRole": true, + "containerId": "cb5a2e5c-2c4a-4acd-9389-3d63c77e1011", + "attributes": {} + } + ], + "tt-rss": [], + "mediawiki": [], + "gitea": [], + "grafana": [], + "admin-cli": [], + "mastodon": [], + "openbikesensor-portal": [], + "account": [ + { + "id": "53cb4bb7-ad4f-4cb6-b19b-60c367a9fca0", + "name": "manage-account", + "description": "${role_manage-account}", + "composite": true, + "composites": { + "client": { + "account": [ + "manage-account-links" + ] + } + }, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "22e2c8e7-3a1e-4681-9584-77f375255072", + "name": "view-profile", + "description": "${role_view-profile}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "c2da86e7-0c40-4202-b01f-711f115444ac", + "name": "delete-account", + "description": "${role_delete-account}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "4a8aa5fd-e4e5-4533-8886-6b0d54b10516", + "name": "manage-account-links", + "description": "${role_manage-account-links}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "518f2427-8d18-4960-b958-2477fdfdae90", + "name": "view-applications", + "description": "${role_view-applications}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "e29e2d62-1992-4437-ae33-b47346fcd59a", + "name": "manage-consent", + "description": "${role_manage-consent}", + "composite": true, + "composites": { + "client": { + "account": [ + "view-consent" + ] + } + }, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "96e61a70-2586-4c90-b2ea-52987b3894e1", + "name": "view-groups", + "description": "${role_view-groups}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + }, + { + "id": "f7531a5f-0b66-481e-8b6a-546ca6dff284", + "name": "view-consent", + "description": "${role_view-consent}", + "composite": false, + "clientRole": true, + "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "attributes": {} + } + ] + } + }, + "groups": [], + "defaultRole": { + "id": "49dd91a4-2176-4a84-aab0-37eb7f41fc1f", + "name": "default-roles-test.pub.solar", + "description": "${role_default-roles}", + "composite": true, + "clientRole": false, + "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686" + }, + "requiredCredentials": [ + "password" + ], + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "otpPolicyCodeReusable": false, + "otpSupportedApplications": [ + "totpAppFreeOTPName", + "totpAppGoogleName", + "totpAppMicrosoftAuthenticatorName" + ], + "localizationTexts": {}, + "webAuthnPolicyRpEntityName": "keycloak", + "webAuthnPolicySignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyRpId": "", + "webAuthnPolicyAttestationConveyancePreference": "not specified", + "webAuthnPolicyAuthenticatorAttachment": "not specified", + "webAuthnPolicyRequireResidentKey": "not specified", + "webAuthnPolicyUserVerificationRequirement": "not specified", + "webAuthnPolicyCreateTimeout": 0, + "webAuthnPolicyAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyAcceptableAaguids": [], + "webAuthnPolicyExtraOrigins": [], + "webAuthnPolicyPasswordlessRpEntityName": "keycloak", + "webAuthnPolicyPasswordlessSignatureAlgorithms": [ + "ES256" + ], + "webAuthnPolicyPasswordlessRpId": "", + "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", + "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", + "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", + "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", + "webAuthnPolicyPasswordlessCreateTimeout": 0, + "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, + "webAuthnPolicyPasswordlessAcceptableAaguids": [], + "webAuthnPolicyPasswordlessExtraOrigins": [], + "users": [ + { + "id": "eeecbf5f-4671-4f1b-9fa1-1cba5c7f5f7a", + "username": "service-account-admin-cli", + "emailVerified": true, + "createdTimestamp": 1714175492873, + "enabled": true, + "totp": false, + "serviceAccountClientId": "admin-cli", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-test.pub.solar" + ], + "clientRoles": { + "realm-management": [ + "query-realms", + "manage-users", + "view-events", + "view-users", + "manage-events", + "manage-realm", + "view-clients", + "view-realm", + "impersonation", + "view-authorization", + "query-groups", + "manage-authorization", + "realm-admin", + "create-client", + "query-users", + "query-clients", + "view-identity-providers", + "manage-identity-providers", + "manage-clients" + ] + }, + "notBefore": 0, + "groups": [] + }, + { + "id": "1237f773-ea8a-4db1-8fe5-5ec7924e6a10", + "username": "service-account-matrix", + "emailVerified": true, + "createdTimestamp": 1669426534368, + "enabled": true, + "totp": false, + "serviceAccountClientId": "matrix", + "disableableCredentialTypes": [], + "requiredActions": [], + "realmRoles": [ + "default-roles-test.pub.solar" + ], + "clientRoles": { + "matrix": [ + "uma_protection" + ] + }, + "notBefore": 0, + "groups": [] + } + ], + "scopeMappings": [ + { + "clientScope": "offline_access", + "roles": [ + "offline_access" + ] + } + ], + "clientScopeMappings": { + "account": [ + { + "client": "account-console", + "roles": [ + "manage-account", + "view-groups" + ] + } + ] + }, + "clients": [ + { + "id": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "clientId": "account", + "name": "${client_account}", + "description": "", + "rootUrl": "${authBaseUrl}", + "adminUrl": "", + "baseUrl": "/realms/test.pub.solar/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/test.pub.solar/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "token.response.type.bearer.lower-case": "false", + "use.refresh.tokens": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "16e24154-8351-4862-866e-ccb326d3143a", + "clientId": "account-console", + "name": "${client_account-console}", + "description": "", + "rootUrl": "${authBaseUrl}", + "adminUrl": "", + "baseUrl": "/realms/test.pub.solar/account/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/realms/test.pub.solar/account/*" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "tls.client.certificate.bound.access.tokens": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "a076f7e4-08b2-4804-8784-526bcbcbf293", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "43795547-9881-429e-86f3-94cbb2961f4e", + "clientId": "admin-cli", + "name": "${client_admin-cli}", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": false, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": true, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "ba37bbed-bf37-433e-a87c-17be807bebef", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "client_id", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "client_id", + "jsonType.label": "String" + } + }, + { + "id": "223f12dc-ea4e-415f-b219-579af08f077e", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "197639ae-6f64-41fb-88db-30e02507ee2a", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "introspection.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2321d398-262d-4fd7-aef8-e6cc0ee017d7", + "clientId": "broker", + "name": "${client_broker}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "eb879c6d-d130-4eac-82c2-abb0c3b90eb1", + "clientId": "gitea", + "name": "", + "description": "", + "rootUrl": "https://git.test.pub.solar", + "adminUrl": "https://git.test.pub.solar", + "baseUrl": "https://git.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://git.test.pub.solar/*" + ], + "webOrigins": [ + "https://git.test.pub.solar" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": 1724701666039, + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "8f4a114b-d41c-4942-b6a8-0d306ed84edf", + "clientId": "grafana", + "name": "", + "description": "https://grafana.test.pub.solar", + "rootUrl": "https://grafana.test.pub.solar", + "adminUrl": "https://grafana.test.pub.solar", + "baseUrl": "/login/generic_oauth", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://grafana.test.pub.solar/login/generic_oauth" + ], + "webOrigins": [ + "https://grafana.test.pub.solar" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "212cab9b-cf2c-4bfd-8a1a-1e0533c430f6", + "clientId": "mastodon", + "name": "mastodon", + "description": "", + "rootUrl": "https://mastodon.test.pub.solar", + "adminUrl": "", + "baseUrl": "https://mastodon.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "", + "https://mastodon.test.pub.solar/auth/auth/openid_connect/callback" + ], + "webOrigins": [ + "https://mastodon.test.pub.solar/auth/openid_connect/callback" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "token.response.type.bearer.lower-case": "false", + "use.refresh.tokens": "true" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "cb5a2e5c-2c4a-4acd-9389-3d63c77e1011", + "clientId": "matrix", + "name": "", + "description": "", + "rootUrl": "https://chat.test.pub.solar", + "adminUrl": "", + "baseUrl": "https://chat.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://matrix.test.pub.solar/_synapse/client/oidc/callback", + "https://matrix.test.test.pub.solar/_synapse/client/oidc/callback" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": true, + "authorizationServicesEnabled": true, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": 1724701666039, + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "backchannel.logout.url": "https://chat.test.pub.solar/_synapse/client/oidc/backchannel_logout", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "protocolMappers": [ + { + "id": "895d5d35-d9c9-489d-bddc-37c40a337188", + "name": "Client Host", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientHost", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientHost", + "jsonType.label": "String" + } + }, + { + "id": "969c7760-7d2a-4117-8505-53bd4d0c10b1", + "name": "Client IP Address", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientAddress", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientAddress", + "jsonType.label": "String" + } + }, + { + "id": "63d3be07-5ef2-4b84-92ec-1a739b2f58e4", + "name": "Client ID", + "protocol": "openid-connect", + "protocolMapper": "oidc-usersessionmodel-note-mapper", + "consentRequired": false, + "config": { + "user.session.note": "clientId", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "clientId", + "jsonType.label": "String" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ], + "authorizationSettings": { + "allowRemoteResourceManagement": true, + "policyEnforcementMode": "ENFORCING", + "resources": [ + { + "name": "Default Resource", + "type": "urn:matrix:resources:default", + "ownerManagedAccess": false, + "attributes": {}, + "_id": "559732a1-23b5-4af2-b14f-32b0ae2afa6e", + "uris": [ + "/*" + ] + } + ], + "policies": [ + { + "id": "95abcad9-b9ff-416e-8ab1-706bf6a7f406", + "name": "Default Policy", + "description": "A policy that grants access only for users within this realm", + "type": "js", + "logic": "POSITIVE", + "decisionStrategy": "AFFIRMATIVE", + "config": { + "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n" + } + }, + { + "id": "26997def-9683-47e4-a6c3-c7d5b69e4a38", + "name": "Default Permission", + "description": "A permission that applies to the default resource type", + "type": "resource", + "logic": "POSITIVE", + "decisionStrategy": "UNANIMOUS", + "config": { + "defaultResourceType": "urn:matrix:resources:default", + "applyPolicies": "[\"Default Policy\"]" + } + } + ], + "scopes": [], + "decisionStrategy": "UNANIMOUS" + } + }, + { + "id": "0bc9fc84-2636-4bc3-9394-61ec4b804939", + "clientId": "matrix-authentication-service", + "name": "", + "description": "Used for our hosted https://github.com/matrix-org/matrix-authentication-service", + "rootUrl": "https://matrix.test.pub.solar/", + "adminUrl": "https://matrix.test.pub.solar/", + "baseUrl": "https://matrix.test.pub.solar/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "http://[::]:8080/upstream/callback/01HHWGFGBGGCT7HFHD0R4K0AZF" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "f4fb631d-de88-48b2-be28-8ee74190c743", + "clientId": "mediawiki", + "name": "", + "description": "", + "rootUrl": "https://wiki.test.pub.solar", + "adminUrl": "https://wiki.test.pub.solar", + "baseUrl": "https://wiki.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://wiki.test.pub.solar/*" + ], + "webOrigins": [ + "https://wiki.test.pub.solar" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "post.logout.redirect.uris": "+", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "d830160a-1c09-4dfd-b984-cd9e69e72649", + "clientId": "nextcloud", + "name": "", + "description": "", + "rootUrl": "https://cloud.test.pub.solar", + "adminUrl": "https://cloud.test.pub.solar", + "baseUrl": "https://cloud.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://cloud.test.pub.solar/apps/user_oidc/code" + ], + "webOrigins": [ + "https://cloud.test.pub.solar" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": 1724701666039, + "post.logout.redirect.uris": "https://cloud.test.pub.solar##https://cloud.test.pub.solar/##https://cloud.test.pub.solar/*", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "backchannel.logout.url": "https://cloud.test.pub.solar/apps/user_oidc/backchannel-logout/test.pub.solar%20ID", + "client_credentials.use_refresh_token": "false", + "require.pushed.authorization.requests": "false", + "acr.loa.map": "{}", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "49bc30c2-6e4c-4c57-a1ea-91073ee099e3", + "clientId": "openbikesensor-portal", + "name": "", + "description": "", + "rootUrl": "https://obs-portal.test.pub.solar", + "adminUrl": "", + "baseUrl": "https://obs-portal.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://obs-portal.test.pub.solar/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "client.secret.creation.time": 1724701666039, + "post.logout.redirect.uris": "+", + "oauth2.device.authorization.grant.enabled": "false", + "backchannel.logout.revoke.offline.tokens": "false", + "use.refresh.tokens": "true", + "tls-client-certificate-bound-access-tokens": "false", + "oidc.ciba.grant.enabled": "false", + "backchannel.logout.session.required": "true", + "client_credentials.use_refresh_token": "false", + "acr.loa.map": "{}", + "require.pushed.authorization.requests": "false", + "display.on.consent.screen": "false", + "token.response.type.bearer.lower-case": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "9c267669-4de5-4203-a1c2-5b2de0003635", + "clientId": "realm-management", + "name": "${client_realm-management}", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": true, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": {}, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "50e53a35-6c81-4c2d-8207-54f4a3ac4c78", + "clientId": "security-admin-console", + "name": "${client_security-admin-console}", + "rootUrl": "${authAdminUrl}", + "baseUrl": "/admin/test.pub.solar/console/", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "/admin/test.pub.solar/console/*" + ], + "webOrigins": [ + "+" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": false, + "serviceAccountsEnabled": false, + "publicClient": true, + "frontchannelLogout": false, + "protocol": "openid-connect", + "attributes": { + "post.logout.redirect.uris": "+", + "pkce.code.challenge.method": "S256" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": false, + "nodeReRegistrationTimeout": 0, + "protocolMappers": [ + { + "id": "9bdb45b8-f97c-442d-8ee3-769229817926", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ], + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "92afe526-965a-45f3-9222-e410ec4b8be4", + "clientId": "tailscale", + "name": "", + "description": "", + "rootUrl": "", + "adminUrl": "", + "baseUrl": "", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://login.tailscale.com/a/oauth_response" + ], + "webOrigins": [], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + }, + { + "id": "2d56c796-877e-46d8-8b3a-c3040cdbe615", + "clientId": "tt-rss", + "name": "tt-rss", + "description": "", + "rootUrl": "https://rss.test.pub.solar", + "adminUrl": "https://rss.test.pub.solar", + "baseUrl": "https://rss.test.pub.solar", + "surrogateAuthRequired": false, + "enabled": true, + "alwaysDisplayInConsole": false, + "clientAuthenticatorType": "client-secret", + "secret": "secret", + "redirectUris": [ + "https://rss.test.pub.solar" + ], + "webOrigins": [ + "https://rss.test.pub.solar" + ], + "notBefore": 0, + "bearerOnly": false, + "consentRequired": false, + "standardFlowEnabled": true, + "implicitFlowEnabled": false, + "directAccessGrantsEnabled": true, + "serviceAccountsEnabled": false, + "publicClient": false, + "frontchannelLogout": true, + "protocol": "openid-connect", + "attributes": { + "oidc.ciba.grant.enabled": "false", + "display.on.consent.screen": "false", + "oauth2.device.authorization.grant.enabled": "false", + "client.secret.creation.time": 1724701666039, + "backchannel.logout.session.required": "true", + "backchannel.logout.revoke.offline.tokens": "false" + }, + "authenticationFlowBindingOverrides": {}, + "fullScopeAllowed": true, + "nodeReRegistrationTimeout": -1, + "defaultClientScopes": [ + "web-origins", + "acr", + "roles", + "profile", + "email" + ], + "optionalClientScopes": [ + "address", + "phone", + "offline_access", + "microprofile-jwt" + ] + } + ], + "clientScopes": [ + { + "id": "7a97955f-1df4-4521-a57d-b19a038b5008", + "name": "microprofile-jwt", + "description": "Microprofile - JWT built-in scope", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "b222f3ee-2b6e-4bd4-8250-c1690b457262", + "name": "groups", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "groups", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "931ce4b0-3f94-409d-b28d-ce75a1d46676", + "name": "upn", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "upn", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "6d0fe6eb-b776-4c3e-9468-763abec48df2", + "name": "acr", + "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "b7d3f70f-b57f-44fe-9454-8f02aa7f8fe5", + "name": "acr loa level", + "protocol": "openid-connect", + "protocolMapper": "oidc-acr-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true" + } + } + ] + }, + { + "id": "57645a5b-ce73-4e39-9c0b-76b92dca0ced", + "name": "roles", + "description": "OpenID Connect scope for add user roles to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "${rolesScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "92a37264-4062-4cae-a935-d8dc2bef141d", + "name": "roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "roles", + "jsonType.label": "String", + "multivalued": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "2bf1a28e-db9f-4aac-b9aa-3fe13bb135fb", + "name": "client roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-client-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "resource_access.${client_id}.roles", + "jsonType.label": "String", + "multivalued": "true" + } + }, + { + "id": "d390481c-37a5-492f-bb9e-670fdc9b2a09", + "name": "audience resolve", + "protocol": "openid-connect", + "protocolMapper": "oidc-audience-resolve-mapper", + "consentRequired": false, + "config": {} + }, + { + "id": "71823193-58b0-474c-bdca-c369035fa572", + "name": "realm roles", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-realm-role-mapper", + "consentRequired": false, + "config": { + "user.attribute": "foo", + "access.token.claim": "true", + "claim.name": "realm_access.roles", + "jsonType.label": "String", + "multivalued": "true" + } + } + ] + }, + { + "id": "1768debd-6e76-488a-a46d-4f5eda32a10e", + "name": "web-origins", + "description": "OpenID Connect scope for add allowed web origins to the access token", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "false", + "consent.screen.text": "", + "display.on.consent.screen": "false" + }, + "protocolMappers": [ + { + "id": "91eaf891-9a35-4e8f-a17a-8827498729d8", + "name": "allowed web origins", + "protocol": "openid-connect", + "protocolMapper": "oidc-allowed-origins-mapper", + "consentRequired": false, + "config": {} + } + ] + }, + { + "id": "9ad3b314-4926-4fb9-9dad-bc2912739ece", + "name": "profile", + "description": "OpenID Connect built-in scope: profile", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${profileScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "9b4a04cc-34e3-4f6c-89c2-eb0c46a84c53", + "name": "given name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "firstName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "given_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "327f25d5-98d6-4355-b1bf-6d51f0add59e", + "name": "username", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "username", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "preferred_username", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "a0d8ba01-3158-4200-a0ed-b472971e1e10", + "name": "website", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "website", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "website", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "f2257f8c-700d-425f-8cf2-e1d6795f2b01", + "name": "nickname", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "nickname", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "nickname", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "0143f9a9-384c-4124-9e64-4cafb53eaf4f", + "name": "gender", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "gender", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "gender", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "fc84b9a0-2505-4295-829b-5c0fd70378b2", + "name": "middle name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "middleName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "middle_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "3a1a616f-9388-42b3-b8a1-ee08f158ec99", + "name": "full name", + "protocol": "openid-connect", + "protocolMapper": "oidc-full-name-mapper", + "consentRequired": false, + "config": { + "id.token.claim": "true", + "access.token.claim": "true", + "userinfo.token.claim": "true" + } + }, + { + "id": "927ff720-aa71-4c04-9d28-e32cd2937fd3", + "name": "profile", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "profile", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "profile", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "01d095b6-e644-4c2f-9fcd-2b18c67a46c5", + "name": "picture", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "picture", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "picture", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "230373d9-d8bb-4f5c-b6a9-aaedcc2a5618", + "name": "zoneinfo", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "zoneinfo", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "zoneinfo", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "6db5cf0c-ecc8-45c7-bc40-425a0ef3a5f6", + "name": "locale", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "locale", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "locale", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "c7cc861c-9dd8-496f-802f-bd6017e7bcbf", + "name": "birthdate", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "birthdate", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "birthdate", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "a64dbb41-3312-4426-b60c-31707a4f7811", + "name": "family name", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "lastName", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "family_name", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + }, + { + "id": "3636403b-8b38-451d-8400-70d2d75ea2a7", + "name": "updated at", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "updatedAt", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "updated_at", + "jsonType.label": "long", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "8f7ce907-4a00-475f-8d4f-5d83448256d6", + "name": "offline_access", + "description": "OpenID Connect built-in scope: offline_access", + "protocol": "openid-connect", + "attributes": { + "consent.screen.text": "${offlineAccessScopeConsentText}", + "display.on.consent.screen": "true" + } + }, + { + "id": "fe3ed7de-cf40-4c3c-921f-c0af091d8a3c", + "name": "role_list", + "description": "SAML role list", + "protocol": "saml", + "attributes": { + "consent.screen.text": "${samlRoleListScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "f5741693-65be-49bc-bf4f-c717ad1c159d", + "name": "role list", + "protocol": "saml", + "protocolMapper": "saml-role-list-mapper", + "consentRequired": false, + "config": { + "single": "true", + "attribute.nameformat": "Basic", + "attribute.name": "Role" + } + } + ] + }, + { + "id": "3dacdfcf-e86d-44fb-be12-e9d05c858121", + "name": "email", + "description": "OpenID Connect built-in scope: email", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${emailScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "3ba989a9-9659-4e1e-ab3e-2cd6357abca5", + "name": "email verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "emailVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email_verified", + "jsonType.label": "boolean", + "userinfo.token.claim": "true" + } + }, + { + "id": "9c727f43-b33d-413a-830f-3640a58e3af7", + "name": "email", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-property-mapper", + "consentRequired": false, + "config": { + "user.attribute": "email", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "email", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "e1a49b03-0235-47bf-8c6d-6f4134f2a627", + "name": "phone", + "description": "OpenID Connect built-in scope: phone", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${phoneScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "c2efaab6-8177-4f16-a27a-3ab93229b60a", + "name": "phone number verified", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "phoneNumberVerified", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number_verified", + "jsonType.label": "boolean", + "userinfo.token.claim": "true" + } + }, + { + "id": "92179260-b057-4bcc-a903-05f937a3254d", + "name": "phone number", + "protocol": "openid-connect", + "protocolMapper": "oidc-usermodel-attribute-mapper", + "consentRequired": false, + "config": { + "user.attribute": "phoneNumber", + "id.token.claim": "true", + "access.token.claim": "true", + "claim.name": "phone_number", + "jsonType.label": "String", + "userinfo.token.claim": "true" + } + } + ] + }, + { + "id": "6721b07c-704b-4ccc-a6b2-995df73c568f", + "name": "address", + "description": "OpenID Connect built-in scope: address", + "protocol": "openid-connect", + "attributes": { + "include.in.token.scope": "true", + "consent.screen.text": "${addressScopeConsentText}", + "display.on.consent.screen": "true" + }, + "protocolMappers": [ + { + "id": "1b28c15b-e6de-4a1d-83a0-58a519033338", + "name": "address", + "protocol": "openid-connect", + "protocolMapper": "oidc-address-mapper", + "consentRequired": false, + "config": { + "user.attribute.formatted": "formatted", + "user.attribute.country": "country", + "user.attribute.postal_code": "postal_code", + "userinfo.token.claim": "true", + "user.attribute.street": "street", + "id.token.claim": "true", + "user.attribute.region": "region", + "access.token.claim": "true", + "user.attribute.locality": "locality" + } + } + ] + } + ], + "defaultDefaultClientScopes": [ + "role_list", + "profile", + "email", + "roles", + "web-origins", + "acr" + ], + "defaultOptionalClientScopes": [ + "offline_access", + "address", + "phone", + "microprofile-jwt" + ], + "browserSecurityHeaders": { + "contentSecurityPolicyReportOnly": "", + "xContentTypeOptions": "nosniff", + "xRobotsTag": "none", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", + "xXSSProtection": "1; mode=block", + "strictTransportSecurity": "max-age=31536000; includeSubDomains" + }, + "smtpServer": { + "password": "**********", + "replyToDisplayName": "test.pub.solar Support", + "starttls": "false", + "auth": "true", + "port": "465", + "replyTo": "admins@test.pub.solar", + "host": "mail.test.pub.solar", + "from": "keycloak@test.pub.solar", + "fromDisplayName": "test.pub.solar ID", + "envelopeFrom": "", + "ssl": "true", + "user": "admins@test.pub.solar" + }, + "loginTheme": "test.pub.solar", + "accountTheme": "test.pub.solar", + "adminTheme": "test.pub.solar", + "emailTheme": "test.pub.solar", + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": true, + "adminEventsDetailsEnabled": false, + "identityProviders": [], + "identityProviderMappers": [], + "components": { + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "id": "89713f44-8fd5-473f-abe9-f4d27fcbbb11", + "name": "Trusted Hosts", + "providerId": "trusted-hosts", + "subType": "anonymous", + "subComponents": {}, + "config": { + "host-sending-registration-request-must-match": [ + "true" + ], + "client-uris-must-match": [ + "true" + ] + } + }, + { + "id": "109840f6-fe6d-413f-a92f-984ec519bace", + "name": "Max Clients Limit", + "providerId": "max-clients", + "subType": "anonymous", + "subComponents": {}, + "config": { + "max-clients": [ + "200" + ] + } + }, + { + "id": "12cd90ef-89e3-411e-8dc9-30b4b360526c", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "93f5007f-4271-4ab5-b055-61bd70789eea", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "oidc-address-mapper", + "saml-role-list-mapper", + "oidc-sha256-pairwise-sub-mapper", + "saml-user-attribute-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "oidc-full-name-mapper" + ] + } + }, + { + "id": "551237c4-bd4a-4e65-ad2b-67adab62f368", + "name": "Full Scope Disabled", + "providerId": "scope", + "subType": "anonymous", + "subComponents": {}, + "config": {} + }, + { + "id": "330eb614-8b38-4414-ad7a-0ae51083044d", + "name": "Allowed Client Scopes", + "providerId": "allowed-client-templates", + "subType": "authenticated", + "subComponents": {}, + "config": { + "allow-default-scopes": [ + "true" + ] + } + }, + { + "id": "ca9bd5bb-21b2-401a-b5d0-0d5764f1b73a", + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "anonymous", + "subComponents": {}, + "config": { + "allowed-protocol-mapper-types": [ + "oidc-usermodel-property-mapper", + "saml-user-attribute-mapper", + "oidc-full-name-mapper", + "saml-user-property-mapper", + "oidc-usermodel-attribute-mapper", + "saml-role-list-mapper", + "oidc-address-mapper", + "oidc-sha256-pairwise-sub-mapper" + ] + } + }, + { + "id": "49561521-b026-4fca-954b-49b7c527dc3a", + "name": "Consent Required", + "providerId": "consent-required", + "subType": "anonymous", + "subComponents": {}, + "config": {} + } + ], + "org.keycloak.userprofile.UserProfileProvider": [ + { + "id": "48ba8848-a3a6-4444-918f-9663abe09391", + "providerId": "declarative-user-profile", + "subComponents": {}, + "config": { + "kc.user.profile.config": [ + "{\"attributes\":[{\"name\":\"username\",\"displayName\":\"${username}\",\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"min\":3,\"max\":255},\"username-prohibited-characters\":{},\"up-username-not-idn-homograph\":{}}},{\"name\":\"email\",\"displayName\":\"${email}\",\"required\":{\"roles\":[\"user\"]},\"permissions\":{\"view\":[\"admin\",\"user\"],\"edit\":[\"admin\",\"user\"]},\"validations\":{\"email\":{},\"length\":{\"max\":255}}},{\"name\":\"firstName\",\"displayName\":\"${firstName}\",\"permissions\":{\"edit\":[\"admin\",\"user\"],\"view\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"annotations\":{},\"group\":null},{\"name\":\"lastName\",\"displayName\":\"${lastName}\",\"permissions\":{\"edit\":[\"admin\",\"user\"],\"view\":[\"admin\",\"user\"]},\"validations\":{\"length\":{\"max\":255},\"person-name-prohibited-characters\":{}},\"selector\":{\"scopes\":[\"microprofile-jwt\",\"acr\",\"roles\",\"web-origins\",\"profile\",\"offline_access\",\"role_list\",\"email\",\"phone\",\"address\"]},\"annotations\":{},\"group\":null}]}" + ] + } + } + ], + "org.keycloak.keys.KeyProvider": [ + { + "id": "27867206-2a90-4889-90eb-2a289a17bba9", + "name": "aes-generated", + "providerId": "aes-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "37c64054-1aa5-4ade-a132-084dfdbbf290", + "name": "hmac-generated", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "HS256" + ] + } + }, + { + "id": "e7e81798-74aa-4232-bced-f8d94af77186", + "name": "rsa-generated", + "providerId": "rsa-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ] + } + }, + { + "id": "1e1ffc41-1c09-4953-bcd7-ac4b0381328a", + "name": "rsa-enc-generated", + "providerId": "rsa-enc-generated", + "subComponents": {}, + "config": { + "priority": [ + "100" + ], + "algorithm": [ + "RSA-OAEP" + ] + } + }, + { + "id": "28bc97a0-1328-4f6a-a98b-64d7fd0de8c3", + "name": "fallback-HS512", + "providerId": "hmac-generated", + "subComponents": {}, + "config": { + "priority": [ + "-100" + ], + "algorithm": [ + "HS512" + ] + } + } + ] + }, + "internationalizationEnabled": true, + "supportedLocales": [ + "de", + "en" + ], + "defaultLocale": "en", + "authenticationFlows": [ + { + "id": "ce72bdaa-3251-44c7-809f-5e246f29fad3", + "alias": "2FA_new", + "description": "", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 0, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "webauthn-authenticator", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 1, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 2, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "3db2c722-66fd-4069-882b-5a9d78688760", + "alias": "Account verification options", + "description": "Method with which to verity the existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-email-verification", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false + } + ] + }, + { + "id": "271b2e17-075d-4aad-9bab-c08e40b7d465", + "alias": "Authentication forms", + "description": "", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "auth-username-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 0, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 1, + "autheticatorFlow": true, + "flowAlias": "Passwordless_or_2FA_new", + "userSetupAllowed": false + } + ] + }, + { + "id": "ad1c9730-eaf3-4e13-9127-02f501b35255", + "alias": "Browser - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "f4b016fc-6074-485e-a4a8-ad139d08de18", + "alias": "Direct Grant - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "222bbd1e-409d-451c-93d1-c0725ff1f6b3", + "alias": "First broker login - Conditional OTP", + "description": "Flow to determine if the OTP is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4a5cf709-4c21-451c-a891-86605e7f3ead", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Account verification options", + "userSetupAllowed": false + } + ] + }, + { + "id": "004c7828-a040-4bc3-b941-de7a284c94b0", + "alias": "Password_and_2FA_new", + "description": "", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "auth-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 0, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 3, + "autheticatorFlow": true, + "flowAlias": "2FA_new", + "userSetupAllowed": false + } + ] + }, + { + "id": "dff9260d-f49e-423d-b821-a5200232e8d0", + "alias": "Passwordless_or_2FA_new", + "description": "", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "webauthn-authenticator-passwordless", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 0, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 1, + "autheticatorFlow": true, + "flowAlias": "Password_and_2FA_new", + "userSetupAllowed": false + } + ] + }, + { + "id": "1722cdb4-38c3-417a-9380-2eda6a33f785", + "alias": "Reset - Conditional OTP", + "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-otp", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "aa454877-1434-4c2e-8545-066b4f3b4054", + "alias": "User creation or linking", + "description": "Flow for the existing/non-existing user alternatives", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false + } + ] + }, + { + "id": "42835c0a-1717-43b8-82bf-5170b67da30f", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "First broker login - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "f36074df-ca57-4156-a946-665b77ef9a98", + "alias": "Webauthn Browser", + "description": "browser based authentication with Webauthn enabled", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorConfig": "Identity Provider Redirector", + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 31, + "autheticatorFlow": true, + "flowAlias": "Authentication forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "84aeccff-bd3f-4432-9c41-6cdfd68ec8e5", + "alias": "Webauthn Browser no required username 2FA", + "description": "", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticator": "conditional-user-configured", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 0, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "webauthn-authenticator", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 1, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-otp-form", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 2, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "9c5ad713-27b7-4dc1-a721-3460fc7ddfe0", + "alias": "Webauthn Browser no required username Password_and_2FA", + "description": "Flow to determine if password + 2FA is required for the authentication", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": false, + "authenticationExecutions": [ + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 23, + "autheticatorFlow": true, + "flowAlias": "Webauthn Browser no required username 2FA", + "userSetupAllowed": false + } + ] + }, + { + "id": "ce06e5fa-237a-46d4-89da-94401f4b42e0", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "auth-spnego", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "identity-provider-redirector", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 25, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "forms", + "userSetupAllowed": false + } + ] + }, + { + "id": "f922a19b-a3ae-4e31-981c-e5e05c48063d", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-secret-jwt", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "client-x509", + "authenticatorFlow": false, + "requirement": "ALTERNATIVE", + "priority": 40, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4d29a72e-cfc1-4a39-be48-5fe985b46244", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "direct-grant-validate-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 30, + "autheticatorFlow": true, + "flowAlias": "Direct Grant - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "2829ac62-1d83-4912-b63b-e8710ae0b4c2", + "alias": "docker auth", + "description": "Used by Docker clients to authenticate against the IDP", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "docker-http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "401235ad-1f4d-4764-afb6-5a8adf244604", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "User creation or linking", + "userSetupAllowed": false + } + ] + }, + { + "id": "d833da39-216f-4400-8e84-db5446a0e651", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 20, + "autheticatorFlow": true, + "flowAlias": "Browser - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "b3edb2a4-48fa-40b6-bcf3-5f178fc1e45e", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "authenticatorFlow": true, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": true, + "flowAlias": "registration form", + "userSetupAllowed": false + } + ] + }, + { + "id": "568f69e7-a69c-4299-ab41-c66473e98d01", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-password-action", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 50, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "registration-recaptcha-action", + "authenticatorFlow": false, + "requirement": "DISABLED", + "priority": 60, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + }, + { + "id": "4ae2919a-2033-4201-b9fc-b9f3320e939f", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-credential-email", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 20, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticator": "reset-password", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 30, + "autheticatorFlow": false, + "userSetupAllowed": false + }, + { + "authenticatorFlow": true, + "requirement": "CONDITIONAL", + "priority": 40, + "autheticatorFlow": true, + "flowAlias": "Reset - Conditional OTP", + "userSetupAllowed": false + } + ] + }, + { + "id": "ff50f985-4ab1-428b-b0c8-2fd99f109198", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "authenticatorFlow": false, + "requirement": "REQUIRED", + "priority": 10, + "autheticatorFlow": false, + "userSetupAllowed": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "9794787b-bc86-4440-b6ae-eed8705e32ae", + "alias": "Identity Provider Redirector", + "config": { + "defaultProvider": "oidc" + } + }, + { + "id": "01d47dfc-83a7-49c6-89a1-ac543fe92f58", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "7dce77a9-dba9-4fca-9aa4-8b78ed48ca4f", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "priority": 10, + "config": {} + }, + { + "alias": "TERMS_AND_CONDITIONS", + "name": "Terms and Conditions", + "providerId": "TERMS_AND_CONDITIONS", + "enabled": false, + "defaultAction": false, + "priority": 20, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "priority": 30, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "priority": 40, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "priority": 50, + "config": {} + }, + { + "alias": "delete_account", + "name": "Delete Account", + "providerId": "delete_account", + "enabled": false, + "defaultAction": false, + "priority": 60, + "config": {} + }, + { + "alias": "webauthn-register", + "name": "Webauthn Register", + "providerId": "webauthn-register", + "enabled": true, + "defaultAction": false, + "priority": 70, + "config": {} + }, + { + "alias": "webauthn-register-passwordless", + "name": "Webauthn Register Passwordless", + "providerId": "webauthn-register-passwordless", + "enabled": true, + "defaultAction": false, + "priority": 80, + "config": {} + }, + { + "alias": "update_user_locale", + "name": "Update User Locale", + "providerId": "update_user_locale", + "enabled": true, + "defaultAction": false, + "priority": 1000, + "config": {} + } + ], + "browserFlow": "Webauthn Browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "dockerAuthenticationFlow": "docker auth", + "attributes": { + "cibaBackchannelTokenDeliveryMode": "poll", + "cibaAuthRequestedUserHint": "login_hint", + "oauth2DevicePollingInterval": "5", + "clientOfflineSessionMaxLifespan": "0", + "clientSessionIdleTimeout": "0", + "userProfileEnabled": "true", + "clientOfflineSessionIdleTimeout": "0", + "cibaInterval": "5", + "realmReusableOtpCode": "false", + "cibaExpiresIn": "120", + "oauth2DeviceCodeLifespan": "600", + "parRequestUriLifespan": "60", + "clientSessionMaxLifespan": "0" + }, + "keycloakVersion": "24.0.5", + "userManagedAccessAllowed": false, + "clientProfiles": { + "profiles": [] + }, + "clientPolicies": { + "policies": [] + } +} diff --git a/tests/support/keycloak-realm-export/src/index.mjs b/tests/support/keycloak-realm-export/src/index.mjs new file mode 100644 index 0000000..92e8c8e --- /dev/null +++ b/tests/support/keycloak-realm-export/src/index.mjs @@ -0,0 +1,69 @@ +#!/usr/bin/env node + +import { readFile } from 'node:fs/promises'; +import { v4 } from 'uuid'; + +const filePath = process.argv[2]; + +const newIds = {}; +const ID_KEYS = [ + 'id', + 'containerId', + '_id', +]; + +const renameDomain = (s) => s.replace(/pub.solar/g, 'test.pub.solar'); + +const changeClientSecrets = (data) => ({ + ...data, + clients: data.clients.map(c => ({ + ...c, + ...(c.secret ? { + secret: 'secret', + attributes: { + ...c.attributes, + "client.secret.creation.time": +(new Date()), + }, + } : {}), + })), +}); + +const shouldChangeId = (node, key) => ID_KEYS.find(name => name === key) && typeof node[key] === "string"; + +const changeIds = (node) => { + if (!node) { + return node; + } + + if (Array.isArray(node)) { + return node.map(n => changeIds(n)); + } + + if (typeof node === "object") { + return Object.keys(node).reduce((acc, key) => ({ + ...acc, + [key]: shouldChangeId(node, key) + ? (() => { + const oldId = node[key]; + if (newIds[oldId]) { + return newIds[oldId]; + } + + newIds[oldId] = v4(); + return newIds[oldId]; + })() + : changeIds(node[key]), + }), {}); + } + + return node; +}; + +(async () => { + const fileContents = await readFile(filePath, { encoding: 'utf8' }); + const data = JSON.parse(renameDomain(fileContents)); + + const newData = changeIds(changeClientSecrets(data)); + + console.log(JSON.stringify(newData, null, 2)); +})(); diff --git a/tests/support/puppeteer-socket/puppeteer-run.nix b/tests/support/puppeteer-socket/puppeteer-run.nix index dc1dabe..296e6e8 100644 --- a/tests/support/puppeteer-socket/puppeteer-run.nix +++ b/tests/support/puppeteer-socket/puppeteer-run.nix @@ -4,5 +4,5 @@ }: writeShellScriptBin "puppeteer-run" '' set -e -exec ${curl}/bin/curl -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket +exec ${curl}/bin/curl --fail-with-body -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket '' diff --git a/tests/support/puppeteer-socket/src/index.mjs b/tests/support/puppeteer-socket/src/index.mjs index 53e3a36..c96b2cf 100644 --- a/tests/support/puppeteer-socket/src/index.mjs +++ b/tests/support/puppeteer-socket/src/index.mjs @@ -16,6 +16,13 @@ const EXECUTABLE = process.env.EXECUTABLE || 'firefox'; }); const page = await firefoxBrowser.newPage(); + page.on('request', request => { + console.log(request.url()); + }); + + page.on('response', response => { + console.log(response.url()); + }); const actions = []; const server = http.createServer({}, (req, res) => { -- 2.44.2 From 3bc699fccfbe3c1d74b50e8fa972c885fb675f98 Mon Sep 17 00:00:00 2001 From: b12f Date: Tue, 27 Aug 2024 13:17:30 +0200 Subject: [PATCH 16/21] chore: run nix fmt --- flake.nix | 3 +- hosts/default.nix | 18 +- hosts/nachtigall/test-vm.nix | 59 +- lib/default.nix | 3 +- modules/backups/default.nix | 12 +- modules/keycloak/default.nix | 4 +- modules/keycloak/keycloak.nix | 552 +++++++++++------- tests/default.nix | 27 +- tests/support/client.nix | 18 +- .../puppeteer-socket/puppeteer-run.nix | 10 +- .../puppeteer-socket/puppeteer-socket.nix | 5 +- 11 files changed, 410 insertions(+), 301 deletions(-) diff --git a/flake.nix b/flake.nix index d747be5..34aeb3b 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,8 @@ devShells.ci = pkgs.mkShell { buildInputs = with pkgs; [ nodejs ]; }; }; - flake = let + flake = + let username = "barkeeper"; in { diff --git a/hosts/default.nix b/hosts/default.nix index 764662d..f52214b 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -44,15 +44,15 @@ }; nachtigall-test = { - imports = [ - self.inputs.agenix.nixosModules.default - self.nixosModules.home-manager - ./nachtigall/test-vm.nix - self.nixosModules.overlays - self.nixosModules.core - self.nixosModules.docker - ]; - }; + imports = [ + self.inputs.agenix.nixosModules.default + self.nixosModules.home-manager + ./nachtigall/test-vm.nix + self.nixosModules.overlays + self.nixosModules.core + self.nixosModules.docker + ]; + }; flora-6 = self.nixos-flake.lib.mkLinuxSystem { imports = [ diff --git a/hosts/nachtigall/test-vm.nix b/hosts/nachtigall/test-vm.nix index 038df61..ff0e4dc 100644 --- a/hosts/nachtigall/test-vm.nix +++ b/hosts/nachtigall/test-vm.nix @@ -1,40 +1,39 @@ { flake, lib, ... }: { - imports = - [ - ./backups.nix - ./apps/nginx.nix + imports = [ + ./backups.nix + ./apps/nginx.nix - ./apps/collabora.nix - ./apps/coturn.nix - ./apps/forgejo.nix - ./apps/keycloak.nix - ./apps/mailman.nix - ./apps/mastodon.nix - ./apps/mediawiki.nix - ./apps/nextcloud.nix - ./apps/nginx-mastodon.nix - ./apps/nginx-mastodon-files.nix - ./apps/nginx-prometheus-exporters.nix - ./apps/nginx-website.nix - ./apps/nginx-website-miom.nix - ./apps/opensearch.nix - ./apps/owncast.nix - ./apps/postgresql.nix - ./apps/prometheus-exporters.nix - ./apps/promtail.nix - ./apps/searx.nix - ./apps/tmate.nix + ./apps/collabora.nix + ./apps/coturn.nix + ./apps/forgejo.nix + ./apps/keycloak.nix + ./apps/mailman.nix + ./apps/mastodon.nix + ./apps/mediawiki.nix + ./apps/nextcloud.nix + ./apps/nginx-mastodon.nix + ./apps/nginx-mastodon-files.nix + ./apps/nginx-prometheus-exporters.nix + ./apps/nginx-website.nix + ./apps/nginx-website-miom.nix + ./apps/opensearch.nix + ./apps/owncast.nix + ./apps/postgresql.nix + ./apps/prometheus-exporters.nix + ./apps/promtail.nix + ./apps/searx.nix + ./apps/tmate.nix - ./apps/matrix/irc.nix - ./apps/matrix/mautrix-telegram.nix - ./apps/matrix/synapse.nix - ./apps/nginx-matrix.nix - ]; + ./apps/matrix/irc.nix + ./apps/matrix/mautrix-telegram.nix + ./apps/matrix/synapse.nix + ./apps/nginx-matrix.nix + ]; nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - + security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; security.acme.preliminarySelfsigned = true; diff --git a/lib/default.nix b/lib/default.nix index e839748..3f14bf6 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -7,7 +7,8 @@ { # Configuration common to all Linux systems flake = { - lib = let + lib = + let callLibs = file: import file { inherit lib; }; in rec { diff --git a/modules/backups/default.nix b/modules/backups/default.nix index f3695af..996392c 100644 --- a/modules/backups/default.nix +++ b/modules/backups/default.nix @@ -4,11 +4,17 @@ lib, pkgs, ... -}: let - utils = import "${flake.inputs.nixpkgs}/nixos/lib/utils.nix" { inherit lib; inherit config; inherit pkgs; }; +}: +let + utils = import "${flake.inputs.nixpkgs}/nixos/lib/utils.nix" { + inherit lib; + inherit config; + inherit pkgs; + }; # Type for a valid systemd unit option. Needed for correctly passing "timerConfig" to "systemd.timers" inherit (utils.systemdUtils.unitOptions) unitOption; -in { +in +{ options.pub-solar-os.backups = { stores = with lib; diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 226c278..5cd5f2d 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -59,9 +59,7 @@ "pub.solar" = flake.inputs.keycloak-theme-pub-solar.legacyPackages.${pkgs.system}.keycloak-theme-pub-solar; }; - plugins = [ - flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener - ]; + plugins = [ flake.inputs.keycloak-event-listener.packages.${pkgs.system}.keycloak-event-listener ]; }; pub-solar-os.backups.backups.keycloak = { diff --git a/modules/keycloak/keycloak.nix b/modules/keycloak/keycloak.nix index 98901a6..283f98c 100644 --- a/modules/keycloak/keycloak.nix +++ b/modules/keycloak/keycloak.nix @@ -1,4 +1,10 @@ -{ config, options, pkgs, lib, ... }: +{ + config, + options, + pkgs, + lib, + ... +}: let cfg = config.services.keycloak; @@ -40,35 +46,79 @@ let prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}"; in { - imports = - [ - (mkRenamedOptionModule - [ "services" "keycloak" "bindAddress" ] - [ "services" "keycloak" "settings" "http-host" ]) - (mkRenamedOptionModule - [ "services" "keycloak" "forceBackendUrlToFrontendUrl"] - [ "services" "keycloak" "settings" "hostname-strict-backchannel"]) - (mkChangedOptionModule - [ "services" "keycloak" "httpPort" ] - [ "services" "keycloak" "settings" "http-port" ] - (config: - builtins.fromJSON config.services.keycloak.httpPort)) - (mkChangedOptionModule - [ "services" "keycloak" "httpsPort" ] - [ "services" "keycloak" "settings" "https-port" ] - (config: - builtins.fromJSON config.services.keycloak.httpsPort)) - (mkRemovedOptionModule - [ "services" "keycloak" "frontendUrl" ] - '' - Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. - NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. - See its description for more information. - '') - (mkRemovedOptionModule - [ "services" "keycloak" "extraConfig" ] - "Use `services.keycloak.settings' instead.") - ]; + imports = [ + (mkRenamedOptionModule + [ + "services" + "keycloak" + "bindAddress" + ] + [ + "services" + "keycloak" + "settings" + "http-host" + ] + ) + (mkRenamedOptionModule + [ + "services" + "keycloak" + "forceBackendUrlToFrontendUrl" + ] + [ + "services" + "keycloak" + "settings" + "hostname-strict-backchannel" + ] + ) + (mkChangedOptionModule + [ + "services" + "keycloak" + "httpPort" + ] + [ + "services" + "keycloak" + "settings" + "http-port" + ] + (config: builtins.fromJSON config.services.keycloak.httpPort) + ) + (mkChangedOptionModule + [ + "services" + "keycloak" + "httpsPort" + ] + [ + "services" + "keycloak" + "settings" + "https-port" + ] + (config: builtins.fromJSON config.services.keycloak.httpsPort) + ) + (mkRemovedOptionModule + [ + "services" + "keycloak" + "frontendUrl" + ] + '' + Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. + NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. + See its description for more information. + '' + ) + (mkRemovedOptionModule [ + "services" + "keycloak" + "extraConfig" + ] "Use `services.keycloak.settings' instead.") + ]; options.services.keycloak = let @@ -82,9 +132,11 @@ in path enum package - port; + port + ; - assertStringPath = optionName: value: + assertStringPath = + optionName: value: if isPath value then throw '' services.keycloak.${optionName}: @@ -92,7 +144,8 @@ in is a Nix path, but should be a string, since Nix paths are copied into the world-readable Nix store. '' - else value; + else + value; in { enable = mkOption { @@ -139,7 +192,11 @@ in database = { type = mkOption { - type = enum [ "mysql" "mariadb" "postgresql" ]; + type = enum [ + "mysql" + "mariadb" + "postgresql" + ]; default = "postgresql"; example = "mariadb"; description = '' @@ -286,7 +343,14 @@ in settings = mkOption { type = lib.types.submodule { - freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ])); + freeformType = attrsOf ( + nullOr (oneOf [ + str + int + bool + (attrsOf path) + ]) + ); options = { http-host = mkOption { @@ -365,7 +429,12 @@ in }; proxy = mkOption { - type = enum [ "edge" "reencrypt" "passthrough" "none" ]; + type = enum [ + "edge" + "reencrypt" + "passthrough" + "none" + ]; default = "none"; example = "edge"; description = '' @@ -422,7 +491,12 @@ in # connect to it. databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost"; createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql"; - createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ]; + createLocalMySQL = + databaseActuallyCreateLocally + && elem cfg.database.type [ + "mysql" + "mariadb" + ]; mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } '' ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt @@ -454,216 +528,242 @@ in fi done - ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)} + ${concatStringsSep "\n" ( + mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes + )} ''; keycloakConfig = lib.generators.toKeyValue { mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { - mkValueString = v: - if isInt v then toString v - else if isString v then v - else if true == v then "true" - else if false == v then "false" - else if isSecret v then hashString "sha256" v._secret - else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}"; + mkValueString = + v: + if isInt v then + toString v + else if isString v then + v + else if true == v then + "true" + else if false == v then + "false" + else if isSecret v then + hashString "sha256" v._secret + else + throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty { }) v}"; }; }; isSecret = v: isAttrs v && v ? _secret && isString v._secret; - filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings; + filteredConfig = lib.converge (lib.filterAttrsRecursive ( + _: v: + !elem v [ + { } + null + ] + )) cfg.settings; confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig); keycloakBuild = cfg.package.override { inherit confFile; - plugins = cfg.package.enabledPlugins ++ cfg.plugins ++ - (with cfg.package.plugins; [quarkus-systemd-notify quarkus-systemd-notify-deployment]); + plugins = + cfg.package.enabledPlugins + ++ cfg.plugins + ++ (with cfg.package.plugins; [ + quarkus-systemd-notify + quarkus-systemd-notify-deployment + ]); }; in - mkIf cfg.enable - { - assertions = [ - { - assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); - message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; - } - { - assertion = createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; - message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; - } - { - assertion = cfg.settings.hostname != null || ! cfg.settings.hostname-strict or true; - message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; - } - { - assertion = cfg.settings.hostname-url or null == null; - message = '' - The option `services.keycloak.settings.hostname-url' has been removed. - Set `services.keycloak.settings.hostname' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - { - assertion = cfg.settings.hostname-strict-backchannel or null == null; - message = '' - The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. - Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - ]; + mkIf cfg.enable { + assertions = [ + { + assertion = + (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); + message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; + } + { + assertion = + createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; + message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; + } + { + assertion = cfg.settings.hostname != null || !cfg.settings.hostname-strict or true; + message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; + } + { + assertion = cfg.settings.hostname-url or null == null; + message = '' + The option `services.keycloak.settings.hostname-url' has been removed. + Set `services.keycloak.settings.hostname' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + { + assertion = cfg.settings.hostname-strict-backchannel or null == null; + message = '' + The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. + Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. + See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. + ''; + } + ]; - environment.systemPackages = [ keycloakBuild ]; + environment.systemPackages = [ keycloakBuild ]; - services.keycloak.settings = - let - postgresParams = concatStringsSep "&" ( - optionals cfg.database.useSSL [ - "ssl=true" - ] ++ optionals (cfg.database.caCert != null) [ - "sslrootcert=${cfg.database.caCert}" - "sslmode=verify-ca" - ] - ); - mariadbParams = concatStringsSep "&" ([ - "characterEncoding=UTF-8" - ] ++ optionals cfg.database.useSSL [ + services.keycloak.settings = + let + postgresParams = concatStringsSep "&" ( + optionals cfg.database.useSSL [ "ssl=true" ] + ++ optionals (cfg.database.caCert != null) [ + "sslrootcert=${cfg.database.caCert}" + "sslmode=verify-ca" + ] + ); + mariadbParams = concatStringsSep "&" ( + [ "characterEncoding=UTF-8" ] + ++ optionals cfg.database.useSSL [ "useSSL=true" "requireSSL=true" "verifyServerCertificate=true" - ] ++ optionals (cfg.database.caCert != null) [ + ] + ++ optionals (cfg.database.caCert != null) [ "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}" "trustCertificateKeyStorePassword=notsosecretpassword" - ]); - dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; - in - mkMerge [ - { - db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; - db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; - db-password._secret = cfg.database.passwordFile; - db-url-host = cfg.database.host; - db-url-port = toString cfg.database.port; - db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; - db-url-properties = prefixUnlessEmpty "?" dbProps; - db-url = null; - } - (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { - https-certificate-file = "/run/keycloak/ssl/ssl_cert"; - https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; - }) - ]; + ] + ); + dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; + in + mkMerge [ + { + db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; + db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; + db-password._secret = cfg.database.passwordFile; + db-url-host = cfg.database.host; + db-url-port = toString cfg.database.port; + db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; + db-url-properties = prefixUnlessEmpty "?" dbProps; + db-url = null; + } + (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { + https-certificate-file = "/run/keycloak/ssl/ssl_cert"; + https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; + }) + ]; - systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { - after = [ "postgresql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "postgresql.service" ]; - path = [ config.services.postgresql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = "postgres"; - Group = "postgres"; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit - - create_role="$(mktemp)" - trap 'rm -f "$create_role"' EXIT - - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" - - echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" - psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" - psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' - ''; + systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { + after = [ "postgresql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "postgresql.service" ]; + path = [ config.services.postgresql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = "postgres"; + Group = "postgres"; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit - systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { - after = [ "mysql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "mysql.service" ]; - path = [ config.services.mysql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = config.services.mysql.user; - Group = config.services.mysql.group; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit + create_role="$(mktemp)" + trap 'rm -f "$create_role"' EXIT - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" - ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" - echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" - echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" - echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" - ) | mysql -N - ''; + echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" + psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" + psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' + ''; + }; + + systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { + after = [ "mysql.service" ]; + before = [ "keycloak.service" ]; + bindsTo = [ "mysql.service" ]; + path = [ config.services.mysql.package ]; + serviceConfig = { + Type = "oneshot"; + RemainAfterExit = true; + User = config.services.mysql.user; + Group = config.services.mysql.group; + LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; }; + script = '' + set -o errexit -o pipefail -o nounset -o errtrace + shopt -s inherit_errexit - systemd.services.keycloak = - let - databaseServices = - if createLocalPostgreSQL then [ + # Read the password from the credentials directory and + # escape any single quotes by adding additional single + # quotes after them, following the rules laid out here: + # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html + db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" + db_password="''${db_password//\'/\'\'}" + + ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" + echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" + echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" + echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" + ) | mysql -N + ''; + }; + + systemd.services.keycloak = + let + databaseServices = + if createLocalPostgreSQL then + [ "keycloakPostgreSQLInit.service" "postgresql.service" ] - else if createLocalMySQL then [ + else if createLocalMySQL then + [ "keycloakMySQLInit.service" "mysql.service" ] - else [ ]; - secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); - mkSecretReplacement = file: '' - replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf - ''; - secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; - extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; - in - { - after = databaseServices; - bindsTo = databaseServices; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ - keycloakBuild - openssl - replace-secret - ]; - environment = { - KC_HOME_DIR = "/run/keycloak"; - KC_CONF_DIR = "/run/keycloak/conf"; - }; - serviceConfig = { - LoadCredential = - map (p: "${baseNameOf p}:${p}") secretPaths - ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ - "ssl_cert:${cfg.sslCertificate}" - "ssl_key:${cfg.sslCertificateKey}" - ]; - User = "keycloak"; - Group = "keycloak"; - DynamicUser = true; - RuntimeDirectory = "keycloak"; - RuntimeDirectoryMode = "0700"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; - Type = "notify"; # Requires quarkus-systemd-notify plugin - NotifyAccess = "all"; - }; - script = '' + else + [ ]; + secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); + mkSecretReplacement = file: '' + replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf + ''; + secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; + extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; + in + { + after = databaseServices; + bindsTo = databaseServices; + wantedBy = [ "multi-user.target" ]; + path = with pkgs; [ + keycloakBuild + openssl + replace-secret + ]; + environment = { + KC_HOME_DIR = "/run/keycloak"; + KC_CONF_DIR = "/run/keycloak/conf"; + }; + serviceConfig = { + LoadCredential = + map (p: "${baseNameOf p}:${p}") secretPaths + ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ + "ssl_cert:${cfg.sslCertificate}" + "ssl_key:${cfg.sslCertificateKey}" + ]; + User = "keycloak"; + Group = "keycloak"; + DynamicUser = true; + RuntimeDirectory = "keycloak"; + RuntimeDirectoryMode = "0700"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + Type = "notify"; # Requires quarkus-systemd-notify plugin + NotifyAccess = "all"; + }; + script = + '' set -o errexit -o pipefail -o nounset -o errtrace shopt -s inherit_errexit @@ -681,24 +781,26 @@ in # sequences. sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf - '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' + '' + + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' mkdir -p /run/keycloak/ssl cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ - '' + '' + '' + + '' export KEYCLOAK_ADMIN=admin export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} kc.sh --verbose start --optimized ${extraStartupFlags} ''; - }; + }; - services.postgresql.enable = mkDefault createLocalPostgreSQL; - services.mysql.enable = mkDefault createLocalMySQL; - services.mysql.package = - let - dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; - in - mkIf createLocalMySQL (mkDefault dbPkg); - }; + services.postgresql.enable = mkDefault createLocalPostgreSQL; + services.mysql.enable = mkDefault createLocalMySQL; + services.mysql.package = + let + dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; + in + mkIf createLocalMySQL (mkDefault dbPkg); + }; meta.doc = ./keycloak.md; meta.maintainers = [ maintainers.talyz ]; diff --git a/tests/default.nix b/tests/default.nix index 9de3431..08831f1 100644 --- a/tests/default.nix +++ b/tests/default.nix @@ -5,15 +5,22 @@ args@{ pkgs, inputs, ... -}: let +}: +let nixos-lib = import (inputs.nixpkgs + "/nixos/lib") { }; - loadTestFiles = with lib; dir: mapAttrs' (name: _: let - test = ((import (dir + "/${name}")) args); - in { - name = "test-" + (lib.strings.removeSuffix ".nix" name); - value = nixos-lib.runTest test; - }) - (filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") - (builtins.readDir dir)); -in loadTestFiles ./. + loadTestFiles = + with lib; + dir: + mapAttrs' ( + name: _: + let + test = ((import (dir + "/${name}")) args); + in + { + name = "test-" + (lib.strings.removeSuffix ".nix" name); + value = nixos-lib.runTest test; + } + ) (filterAttrs (name: _: (hasSuffix ".nix" name) && name != "default.nix") (builtins.readDir dir)); +in +loadTestFiles ./. diff --git a/tests/support/client.nix b/tests/support/client.nix index fab5631..43ad235 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -3,10 +3,12 @@ lib, config, ... -}: let - puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) {}); - puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) {}); -in { +}: +let + puppeteer-socket = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-socket.nix) { }); + puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) { }); +in +{ imports = [ ./global.nix ]; security.polkit.enable = true; @@ -18,9 +20,7 @@ in { services.getty.autologinUser = config.pub-solar-os.authentication.username; - virtualisation.qemu.options = [ - "-vga std" - ]; + virtualisation.qemu.options = [ "-vga std" ]; home-manager.users.${config.pub-solar-os.authentication.username} = { programs.bash.profileExtra = '' @@ -34,9 +34,9 @@ in { ''; config = { modifier = "Mod4"; - terminal = "${pkgs.alacritty}/bin/alacritty"; + terminal = "${pkgs.alacritty}/bin/alacritty"; startup = [ - {command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket";} + { command = "EXECUTABLE=${pkgs.firefox}/bin/firefox ${puppeteer-socket}/bin/puppeteer-socket"; } ]; }; }; diff --git a/tests/support/puppeteer-socket/puppeteer-run.nix b/tests/support/puppeteer-socket/puppeteer-run.nix index 296e6e8..f93d9e0 100644 --- a/tests/support/puppeteer-socket/puppeteer-run.nix +++ b/tests/support/puppeteer-socket/puppeteer-run.nix @@ -1,8 +1,6 @@ -{ - writeShellScriptBin, - curl -}: writeShellScriptBin "puppeteer-run" '' -set -e +{ writeShellScriptBin, curl }: +writeShellScriptBin "puppeteer-run" '' + set -e -exec ${curl}/bin/curl --fail-with-body -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket + exec ${curl}/bin/curl --fail-with-body -X POST -d "$@" --unix-socket "/tmp/puppeteer.sock" http://puppeteer-socket '' diff --git a/tests/support/puppeteer-socket/puppeteer-socket.nix b/tests/support/puppeteer-socket/puppeteer-socket.nix index 5193914..4e93776 100644 --- a/tests/support/puppeteer-socket/puppeteer-socket.nix +++ b/tests/support/puppeteer-socket/puppeteer-socket.nix @@ -1,7 +1,4 @@ -{ - buildNpmPackage, - nodejs, -}: +{ buildNpmPackage, nodejs }: buildNpmPackage rec { src = ./.; name = "puppeteer-socket"; -- 2.44.2 From 6efc88435337086443781e610855f7d7b6d42572 Mon Sep 17 00:00:00 2001 From: b12f Date: Tue, 27 Aug 2024 13:32:00 +0200 Subject: [PATCH 17/21] hosts: remove nachtigall-test --- hosts/default.nix | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/hosts/default.nix b/hosts/default.nix index f52214b..c8aaf1c 100644 --- a/hosts/default.nix +++ b/hosts/default.nix @@ -43,17 +43,6 @@ ]; }; - nachtigall-test = { - imports = [ - self.inputs.agenix.nixosModules.default - self.nixosModules.home-manager - ./nachtigall/test-vm.nix - self.nixosModules.overlays - self.nixosModules.core - self.nixosModules.docker - ]; - }; - flora-6 = self.nixos-flake.lib.mkLinuxSystem { imports = [ self.inputs.agenix.nixosModules.default -- 2.44.2 From dec2d76d2a7f52f72723a2cb1180aec927563379 Mon Sep 17 00:00:00 2001 From: b12f Date: Tue, 27 Aug 2024 13:37:28 +0200 Subject: [PATCH 18/21] tests: move back to old keycloak module --- modules/keycloak/default.nix | 3 - modules/keycloak/keycloak.nix | 807 ---------------------------------- tests/keycloak.nix | 21 +- 3 files changed, 1 insertion(+), 830 deletions(-) delete mode 100644 modules/keycloak/keycloak.nix diff --git a/modules/keycloak/default.nix b/modules/keycloak/default.nix index 5cd5f2d..b40234a 100644 --- a/modules/keycloak/default.nix +++ b/modules/keycloak/default.nix @@ -6,9 +6,6 @@ ... }: { - disabledModules = [ "services/web-apps/keycloak.nix" ]; - imports = [ ./keycloak.nix ]; - options.pub-solar-os.auth = with lib; { enable = mkEnableOption "Enable keycloak to run on the node"; diff --git a/modules/keycloak/keycloak.nix b/modules/keycloak/keycloak.nix deleted file mode 100644 index 283f98c..0000000 --- a/modules/keycloak/keycloak.nix +++ /dev/null @@ -1,807 +0,0 @@ -{ - config, - options, - pkgs, - lib, - ... -}: - -let - cfg = config.services.keycloak; - opt = options.services.keycloak; - - inherit (lib) - types - mkMerge - mkOption - mkChangedOptionModule - mkRenamedOptionModule - mkRemovedOptionModule - mkPackageOption - concatStringsSep - mapAttrsToList - escapeShellArg - mkIf - optionalString - optionals - mkDefault - literalExpression - isAttrs - literalMD - maintainers - catAttrs - collect - hasPrefix - ; - - inherit (builtins) - elem - typeOf - isInt - isString - hashString - isPath - ; - - prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}"; -in -{ - imports = [ - (mkRenamedOptionModule - [ - "services" - "keycloak" - "bindAddress" - ] - [ - "services" - "keycloak" - "settings" - "http-host" - ] - ) - (mkRenamedOptionModule - [ - "services" - "keycloak" - "forceBackendUrlToFrontendUrl" - ] - [ - "services" - "keycloak" - "settings" - "hostname-strict-backchannel" - ] - ) - (mkChangedOptionModule - [ - "services" - "keycloak" - "httpPort" - ] - [ - "services" - "keycloak" - "settings" - "http-port" - ] - (config: builtins.fromJSON config.services.keycloak.httpPort) - ) - (mkChangedOptionModule - [ - "services" - "keycloak" - "httpsPort" - ] - [ - "services" - "keycloak" - "settings" - "https-port" - ] - (config: builtins.fromJSON config.services.keycloak.httpsPort) - ) - (mkRemovedOptionModule - [ - "services" - "keycloak" - "frontendUrl" - ] - '' - Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead. - NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients. - See its description for more information. - '' - ) - (mkRemovedOptionModule [ - "services" - "keycloak" - "extraConfig" - ] "Use `services.keycloak.settings' instead.") - ]; - - options.services.keycloak = - let - inherit (types) - bool - str - int - nullOr - attrsOf - oneOf - path - enum - package - port - ; - - assertStringPath = - optionName: value: - if isPath value then - throw '' - services.keycloak.${optionName}: - ${toString value} - is a Nix path, but should be a string, since Nix - paths are copied into the world-readable Nix store. - '' - else - value; - in - { - enable = mkOption { - type = bool; - default = false; - example = true; - description = '' - Whether to enable the Keycloak identity and access management - server. - ''; - }; - - sslCertificate = mkOption { - type = nullOr path; - default = null; - example = "/run/keys/ssl_cert"; - apply = assertStringPath "sslCertificate"; - description = '' - The path to a PEM formatted certificate to use for TLS/SSL - connections. - ''; - }; - - sslCertificateKey = mkOption { - type = nullOr path; - default = null; - example = "/run/keys/ssl_key"; - apply = assertStringPath "sslCertificateKey"; - description = '' - The path to a PEM formatted private key to use for TLS/SSL - connections. - ''; - }; - - plugins = lib.mkOption { - type = lib.types.listOf lib.types.path; - default = [ ]; - description = '' - Keycloak plugin jar, ear files or derivations containing - them. Packaged plugins are available through - `pkgs.keycloak.plugins`. - ''; - }; - - database = { - type = mkOption { - type = enum [ - "mysql" - "mariadb" - "postgresql" - ]; - default = "postgresql"; - example = "mariadb"; - description = '' - The type of database Keycloak should connect to. - ''; - }; - - host = mkOption { - type = str; - default = "localhost"; - description = '' - Hostname of the database to connect to. - ''; - }; - - port = - let - dbPorts = { - postgresql = 5432; - mariadb = 3306; - mysql = 3306; - }; - in - mkOption { - type = port; - default = dbPorts.${cfg.database.type}; - defaultText = literalMD "default port of selected database"; - description = '' - Port of the database to connect to. - ''; - }; - - useSSL = mkOption { - type = bool; - default = cfg.database.host != "localhost"; - defaultText = literalExpression ''config.${opt.database.host} != "localhost"''; - description = '' - Whether the database connection should be secured by SSL / - TLS. - ''; - }; - - caCert = mkOption { - type = nullOr path; - default = null; - description = '' - The SSL / TLS CA certificate that verifies the identity of the - database server. - - Required when PostgreSQL is used and SSL is turned on. - - For MySQL, if left at `null`, the default - Java keystore is used, which should suffice if the server - certificate is issued by an official CA. - ''; - }; - - createLocally = mkOption { - type = bool; - default = true; - description = '' - Whether a database should be automatically created on the - local host. Set this to false if you plan on provisioning a - local database yourself. This has no effect if - services.keycloak.database.host is customized. - ''; - }; - - name = mkOption { - type = str; - default = "keycloak"; - description = '' - Database name to use when connecting to an external or - manually provisioned database; has no effect when a local - database is automatically provisioned. - - To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to - `false` and create the database and user - manually. - ''; - }; - - username = mkOption { - type = str; - default = "keycloak"; - description = '' - Username to use when connecting to an external or manually - provisioned database; has no effect when a local database is - automatically provisioned. - - To use this with a local database, set [](#opt-services.keycloak.database.createLocally) to - `false` and create the database and user - manually. - ''; - }; - - passwordFile = mkOption { - type = path; - example = "/run/keys/db_password"; - apply = assertStringPath "passwordFile"; - description = '' - The path to a file containing the database password. - ''; - }; - }; - - package = mkPackageOption pkgs "keycloak" { }; - - initialAdminPassword = mkOption { - type = str; - default = "changeme"; - description = '' - Initial password set for the `admin` - user. The password is not stored safely and should be changed - immediately in the admin panel. - ''; - }; - - themes = mkOption { - type = attrsOf package; - default = { }; - description = '' - Additional theme packages for Keycloak. Each theme is linked into - subdirectory with a corresponding attribute name. - - Theme packages consist of several subdirectories which provide - different theme types: for example, `account`, - `login` etc. After adding a theme to this option you - can select it by its name in Keycloak administration console. - ''; - }; - - extraStartupFlags = lib.mkOption { - type = lib.types.listOf str; - default = [ ]; - description = '' - Extra flags to be added to the startup command kc.sh. - This can be used to import a realm during startup or to - set configuration variables, see . - - --verbose and --optimized are always added. - ''; - }; - - settings = mkOption { - type = lib.types.submodule { - freeformType = attrsOf ( - nullOr (oneOf [ - str - int - bool - (attrsOf path) - ]) - ); - - options = { - http-host = mkOption { - type = str; - default = "0.0.0.0"; - example = "127.0.0.1"; - description = '' - On which address Keycloak should accept new connections. - ''; - }; - - http-port = mkOption { - type = port; - default = 80; - example = 8080; - description = '' - On which port Keycloak should listen for new HTTP connections. - ''; - }; - - https-port = mkOption { - type = port; - default = 443; - example = 8443; - description = '' - On which port Keycloak should listen for new HTTPS connections. - ''; - }; - - http-relative-path = mkOption { - type = str; - default = "/"; - example = "/auth"; - apply = x: if !(hasPrefix "/") x then "/" + x else x; - description = '' - The path relative to `/` for serving - resources. - - ::: {.note} - In versions of Keycloak using Wildfly (<17), - this defaulted to `/auth`. If - upgrading from the Wildfly version of Keycloak, - i.e. a NixOS version before 22.05, you'll likely - want to set this to `/auth` to - keep compatibility with your clients. - - See - for more information on migrating from Wildfly to Quarkus. - ::: - ''; - }; - - hostname = mkOption { - type = nullOr str; - example = "keycloak.example.com"; - description = '' - The hostname part of the public URL used as base for - all frontend requests. - - See - for more information about hostname configuration. - ''; - }; - - hostname-backchannel-dynamic = mkOption { - type = bool; - default = false; - example = true; - description = '' - Enables dynamic resolving of backchannel URLs, - including hostname, scheme, port and context path. - - See - for more information about hostname configuration. - ''; - }; - - proxy = mkOption { - type = enum [ - "edge" - "reencrypt" - "passthrough" - "none" - ]; - default = "none"; - example = "edge"; - description = '' - The proxy address forwarding mode if the server is - behind a reverse proxy. - - - `edge`: - Enables communication through HTTP between the - proxy and Keycloak. - - `reencrypt`: - Requires communication through HTTPS between the - proxy and Keycloak. - - `passthrough`: - Enables communication through HTTP or HTTPS between - the proxy and Keycloak. - - See for more information. - ''; - }; - }; - }; - - example = literalExpression '' - { - hostname = "keycloak.example.com"; - proxy = "reencrypt"; - https-key-store-file = "/path/to/file"; - https-key-store-password = { _secret = "/run/keys/store_password"; }; - } - ''; - - description = '' - Configuration options corresponding to parameters set in - {file}`conf/keycloak.conf`. - - Most available options are documented at . - - Options containing secret data should be set to an attribute - set containing the attribute `_secret` - a - string pointing to a file containing the value the option - should be set to. See the example to get a better picture of - this: in the resulting - {file}`conf/keycloak.conf` file, the - `https-key-store-password` key will be set - to the contents of the - {file}`/run/keys/store_password` file. - ''; - }; - }; - - config = - let - # We only want to create a database if we're actually going to - # connect to it. - databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost"; - createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql"; - createLocalMySQL = - databaseActuallyCreateLocally - && elem cfg.database.type [ - "mysql" - "mariadb" - ]; - - mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } '' - ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt - ''; - - # Both theme and theme type directories need to be actual - # directories in one hierarchy to pass Keycloak checks. - themesBundle = pkgs.runCommand "keycloak-themes" { } '' - linkTheme() { - theme="$1" - name="$2" - - mkdir "$out/$name" - for typeDir in "$theme"/*; do - if [ -d "$typeDir" ]; then - type="$(basename "$typeDir")" - mkdir "$out/$name/$type" - for file in "$typeDir"/*; do - ln -sn "$file" "$out/$name/$type/$(basename "$file")" - done - fi - done - } - - mkdir -p "$out" - for theme in ${keycloakBuild}/themes/*; do - if [ -d "$theme" ]; then - linkTheme "$theme" "$(basename "$theme")" - fi - done - - ${concatStringsSep "\n" ( - mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes - )} - ''; - - keycloakConfig = lib.generators.toKeyValue { - mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { - mkValueString = - v: - if isInt v then - toString v - else if isString v then - v - else if true == v then - "true" - else if false == v then - "false" - else if isSecret v then - hashString "sha256" v._secret - else - throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty { }) v}"; - }; - }; - - isSecret = v: isAttrs v && v ? _secret && isString v._secret; - filteredConfig = lib.converge (lib.filterAttrsRecursive ( - _: v: - !elem v [ - { } - null - ] - )) cfg.settings; - confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig); - keycloakBuild = cfg.package.override { - inherit confFile; - plugins = - cfg.package.enabledPlugins - ++ cfg.plugins - ++ (with cfg.package.plugins; [ - quarkus-systemd-notify - quarkus-systemd-notify-deployment - ]); - }; - in - mkIf cfg.enable { - assertions = [ - { - assertion = - (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); - message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL"; - } - { - assertion = - createLocalPostgreSQL -> config.services.postgresql.settings.standard_conforming_strings or true; - message = "Setting up a local PostgreSQL db for Keycloak requires `standard_conforming_strings` turned on to work reliably"; - } - { - assertion = cfg.settings.hostname != null || !cfg.settings.hostname-strict or true; - message = "Setting the Keycloak hostname is required, see `services.keycloak.settings.hostname`"; - } - { - assertion = cfg.settings.hostname-url or null == null; - message = '' - The option `services.keycloak.settings.hostname-url' has been removed. - Set `services.keycloak.settings.hostname' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - { - assertion = cfg.settings.hostname-strict-backchannel or null == null; - message = '' - The option `services.keycloak.settings.hostname-strict-backchannel' has been removed. - Set `services.keycloak.settings.hostname-backchannel-dynamic' instead. - See [New Hostname options](https://www.keycloak.org/docs/25.0.0/upgrading/#new-hostname-options) for details. - ''; - } - ]; - - environment.systemPackages = [ keycloakBuild ]; - - services.keycloak.settings = - let - postgresParams = concatStringsSep "&" ( - optionals cfg.database.useSSL [ "ssl=true" ] - ++ optionals (cfg.database.caCert != null) [ - "sslrootcert=${cfg.database.caCert}" - "sslmode=verify-ca" - ] - ); - mariadbParams = concatStringsSep "&" ( - [ "characterEncoding=UTF-8" ] - ++ optionals cfg.database.useSSL [ - "useSSL=true" - "requireSSL=true" - "verifyServerCertificate=true" - ] - ++ optionals (cfg.database.caCert != null) [ - "trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}" - "trustCertificateKeyStorePassword=notsosecretpassword" - ] - ); - dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams; - in - mkMerge [ - { - db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type; - db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; - db-password._secret = cfg.database.passwordFile; - db-url-host = cfg.database.host; - db-url-port = toString cfg.database.port; - db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name; - db-url-properties = prefixUnlessEmpty "?" dbProps; - db-url = null; - } - (mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { - https-certificate-file = "/run/keycloak/ssl/ssl_cert"; - https-certificate-key-file = "/run/keycloak/ssl/ssl_key"; - }) - ]; - - systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL { - after = [ "postgresql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "postgresql.service" ]; - path = [ config.services.postgresql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = "postgres"; - Group = "postgres"; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit - - create_role="$(mktemp)" - trap 'rm -f "$create_role"' EXIT - - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-CONSTANTS - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" - - echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB" > "$create_role" - psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" - psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' - ''; - }; - - systemd.services.keycloakMySQLInit = mkIf createLocalMySQL { - after = [ "mysql.service" ]; - before = [ "keycloak.service" ]; - bindsTo = [ "mysql.service" ]; - path = [ config.services.mysql.package ]; - serviceConfig = { - Type = "oneshot"; - RemainAfterExit = true; - User = config.services.mysql.user; - Group = config.services.mysql.group; - LoadCredential = [ "db_password:${cfg.database.passwordFile}" ]; - }; - script = '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit - - # Read the password from the credentials directory and - # escape any single quotes by adding additional single - # quotes after them, following the rules laid out here: - # https://dev.mysql.com/doc/refman/8.0/en/string-literals.html - db_password="$(<"$CREDENTIALS_DIRECTORY/db_password")" - db_password="''${db_password//\'/\'\'}" - - ( echo "SET sql_mode = 'NO_BACKSLASH_ESCAPES';" - echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" - echo "CREATE DATABASE IF NOT EXISTS keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" - echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" - ) | mysql -N - ''; - }; - - systemd.services.keycloak = - let - databaseServices = - if createLocalPostgreSQL then - [ - "keycloakPostgreSQLInit.service" - "postgresql.service" - ] - else if createLocalMySQL then - [ - "keycloakMySQLInit.service" - "mysql.service" - ] - else - [ ]; - secretPaths = catAttrs "_secret" (collect isSecret cfg.settings); - mkSecretReplacement = file: '' - replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf - ''; - secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths; - extraStartupFlags = lib.concatStringsSep " " cfg.extraStartupFlags; - in - { - after = databaseServices; - bindsTo = databaseServices; - wantedBy = [ "multi-user.target" ]; - path = with pkgs; [ - keycloakBuild - openssl - replace-secret - ]; - environment = { - KC_HOME_DIR = "/run/keycloak"; - KC_CONF_DIR = "/run/keycloak/conf"; - }; - serviceConfig = { - LoadCredential = - map (p: "${baseNameOf p}:${p}") secretPaths - ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ - "ssl_cert:${cfg.sslCertificate}" - "ssl_key:${cfg.sslCertificateKey}" - ]; - User = "keycloak"; - Group = "keycloak"; - DynamicUser = true; - RuntimeDirectory = "keycloak"; - RuntimeDirectoryMode = "0700"; - AmbientCapabilities = "CAP_NET_BIND_SERVICE"; - Type = "notify"; # Requires quarkus-systemd-notify plugin - NotifyAccess = "all"; - }; - script = - '' - set -o errexit -o pipefail -o nounset -o errtrace - shopt -s inherit_errexit - - umask u=rwx,g=,o= - - ln -s ${themesBundle} /run/keycloak/themes - ln -s ${keycloakBuild}/providers /run/keycloak/ - - install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf - - ${secretReplacements} - - # Escape any backslashes in the db parameters, since - # they're otherwise unexpectedly read as escape - # sequences. - sed -i '/db-/ s|\\|\\\\|g' /run/keycloak/conf/keycloak.conf - - '' - + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' - mkdir -p /run/keycloak/ssl - cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ - '' - + '' - export KEYCLOAK_ADMIN=admin - export KEYCLOAK_ADMIN_PASSWORD=${escapeShellArg cfg.initialAdminPassword} - kc.sh --verbose start --optimized ${extraStartupFlags} - ''; - }; - - services.postgresql.enable = mkDefault createLocalPostgreSQL; - services.mysql.enable = mkDefault createLocalMySQL; - services.mysql.package = - let - dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; - in - mkIf createLocalMySQL (mkDefault dbPkg); - }; - - meta.doc = ./keycloak.md; - meta.maintainers = [ maintainers.talyz ]; -} diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 251707a..eaa7676 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -72,23 +72,10 @@ in }; testScript = - { nodes, ... }: - let - user = nodes.client.users.users.${nodes.client.pub-solar-os.authentication.username}; - #uid = toString user.uid; - bus = "DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/$(id -u ${user.name})/bus"; - gdbus = "${bus} gdbus"; - su = command: "su - ${user.name} -c '${command}'"; - gseval = "call --session -d org.gnome.Shell -o /org/gnome/Shell -m org.gnome.Shell.Eval"; - wmClass = su "${gdbus} ${gseval} global.display.focus_window.wm_class"; - in - '' -<<<<<<< HEAD + { ... }: '' def puppeteer_run(cmd): client.succeed(f'puppeteer-run \'{cmd}\' ') -======= ->>>>>>> main start_all() nachtigall.wait_for_unit("system.slice") @@ -99,7 +86,6 @@ in nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") client.wait_for_unit("system.slice") -<<<<<<< HEAD client.wait_for_file("/tmp/puppeteer.sock") puppeteer_run('page.goto("https://auth.test.pub.solar")') puppeteer_run('page.waitForNetworkIdle()') @@ -118,10 +104,5 @@ in puppeteer_run('page.locator("button::-p-text(Register)").click()') puppeteer_run('page.waitForNetworkIdle()') client.screenshot("after-register") -======= - client.sleep(30) - # client.wait_until_succeeds("${wmClass} | grep -q 'firefox'") - client.screenshot("screen") ->>>>>>> main ''; } -- 2.44.2 From 9bba502b4633c31b7d8febbe88874efd1875e686 Mon Sep 17 00:00:00 2001 From: b12f Date: Tue, 27 Aug 2024 15:16:57 +0200 Subject: [PATCH 19/21] test: fix keycloak realm import --- tests/keycloak.nix | 37 +- .../keycloak-realm-export/realm-export.json | 543 ++++++++---------- .../keycloak-realm-export/src/index.mjs | 7 +- 3 files changed, 286 insertions(+), 301 deletions(-) diff --git a/tests/keycloak.nix b/tests/keycloak.nix index eaa7676..815d975 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -57,10 +57,6 @@ in database-password-file = "/tmp/dbf"; }; services.keycloak.database.createLocally = true; - services.keycloak.extraStartupFlags = [ - "--import-realm" - "--file=${realm-export}" - ]; networking.interfaces.eth0.ipv4.addresses = [ { @@ -81,12 +77,43 @@ in nachtigall.wait_for_unit("system.slice") nachtigall.succeed("ping 127.0.0.1 -c 2") nachtigall.wait_for_unit("nginx.service") - nachtigall.wait_for_unit("keycloak.service") + + nachtigall.systemctl("stop keycloak.service") + nachtigall.wait_until_succeeds("if (($(ps aux | grep 'Dkc.home.dir=/run/keycloak' | grep -v grep | wc -l) == 0)); then true; else false; fi") + nachtigall.succeed("${pkgs.keycloak}/bin/kc.sh --verbose import --optimized --file=${realm-export}") + nachtigall.systemctl("start keycloak.service") + nachtigall.sleep(30) nachtigall.wait_until_succeeds("curl http://127.0.0.1:8080/") nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") client.wait_for_unit("system.slice") client.wait_for_file("/tmp/puppeteer.sock") + + puppeteer_run('page.goto("https://auth.test.pub.solar/admin/master/console")') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("admin-initial") + puppeteer_run('page.locator("[name=username]").fill("admin")') + puppeteer_run('page.locator("::-p-text(Sign In)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("admin-password") + puppeteer_run('page.locator("[name=password]").fill("password")') + puppeteer_run('page.locator("::-p-text(Sign In)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("admin-login") + puppeteer_run('page.locator("::-p-text(Realm settings)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("admin-theme") + puppeteer_run('page.locator("::-p-text(Themes)").click()') + puppeteer_run('page.waitForNetworkIdle()') + puppeteer_run('page.locator("#kc-login-theme").click()') + client.screenshot("admin-theme-changed") + puppeteer_run('page.locator("li button::-p-text(pub.solar)").click()') + puppeteer_run('page.locator("::-p-text(Save)").click()') + puppeteer_run('page.waitForNetworkIdle()') + client.screenshot("admin-theme-saved") + + + puppeteer_run('page.goto("https://auth.test.pub.solar")') puppeteer_run('page.waitForNetworkIdle()') client.screenshot("initial") diff --git a/tests/support/keycloak-realm-export/realm-export.json b/tests/support/keycloak-realm-export/realm-export.json index 381456a..aa76b76 100644 --- a/tests/support/keycloak-realm-export/realm-export.json +++ b/tests/support/keycloak-realm-export/realm-export.json @@ -1,6 +1,6 @@ { - "id": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", - "realm": "test.pub.solar", + "id": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", + "realm": "test.test.pub.solar", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, @@ -47,17 +47,17 @@ "roles": { "realm": [ { - "id": "5e30b340-292f-4c23-982f-936b052634c1", + "id": "c3ebc28c-5ce2-4c53-a679-a5247c7a2c43", "name": "offline_access", "description": "${role_offline-access}", "composite": false, "clientRole": false, - "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "containerId": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", "attributes": {} }, { - "id": "49dd91a4-2176-4a84-aab0-37eb7f41fc1f", - "name": "default-roles-test.pub.solar", + "id": "2e271b49-ed2b-4dc0-a578-47e7571a2934", + "name": "default-roles-test.test.pub.solar", "description": "${role_default-roles}", "composite": true, "composites": { @@ -73,25 +73,25 @@ } }, "clientRole": false, - "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "containerId": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", "attributes": {} }, { - "id": "541db75b-d73a-478c-bfbc-942b64d6286d", + "id": "7b997bf1-6618-4ed3-b7cd-dcc69307589a", "name": "admin", "description": "Grafana admin role", "composite": false, "clientRole": false, - "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "containerId": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", "attributes": {} }, { - "id": "ca6ef8b3-aeca-420a-86d5-edb6698d83ef", + "id": "bb1739c7-a5d4-4f2c-9748-a365a0e39e82", "name": "uma_authorization", "description": "${role_uma_authorization}", "composite": false, "clientRole": false, - "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686", + "containerId": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", "attributes": {} } ], @@ -99,34 +99,34 @@ "nextcloud": [], "realm-management": [ { - "id": "ae0cb0ed-998f-476d-b688-ac087a6ddc5a", + "id": "1c0ff539-0604-4075-a6f1-2451be210107", "name": "manage-users", "description": "${role_manage-users}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "53b294e4-ab83-4c7f-ae21-e5df0d47d76d", + "id": "a7903fb7-2524-45d8-8e4e-d61ffca72c7c", "name": "query-realms", "description": "${role_query-realms}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "fce40cde-1df9-48b7-b18b-f61a95569f03", + "id": "b064020f-5a1d-4564-8da6-534c75837d3c", "name": "view-events", "description": "${role_view-events}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "471acf51-59c9-4e74-a470-8b9d650d7043", + "id": "fd1a6612-7b14-4b50-90e9-7938e21150da", "name": "view-users", "description": "${role_view-users}", "composite": true, @@ -139,29 +139,29 @@ } }, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "e2217f23-e8bf-44ab-ab43-6f3c6951b1ca", + "id": "cc7a0c1f-464f-4af3-88b9-43b458bfa5e4", "name": "manage-events", "description": "${role_manage-events}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "07648931-6258-4276-ab5c-4b7f1aa66e44", + "id": "c94a7871-a851-4787-b934-1cc2427c5559", "name": "manage-realm", "description": "${role_manage-realm}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "a3b51cd8-9a25-4361-9251-52dabdbf3af0", + "id": "ce75c268-9c52-44a2-969e-dd79edfec1d9", "name": "view-clients", "description": "${role_view-clients}", "composite": true, @@ -173,65 +173,65 @@ } }, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "e5db750b-6f51-41ac-885d-054300c072b2", + "id": "d6b1a5c5-b3a5-4893-a7e8-770b7b17c48b", "name": "view-realm", "description": "${role_view-realm}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "cfd61589-7ed6-4fc2-83d0-27f3ca1e6bbd", + "id": "7d5ef0c4-3196-4f6b-8835-36f7438f2358", "name": "impersonation", "description": "${role_impersonation}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "434e0ec3-9e6e-4358-8814-dc5b783ae2b3", + "id": "9c8737b4-11f4-4dde-ac47-dbf806916fc2", "name": "view-authorization", "description": "${role_view-authorization}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "32988bf3-3f8d-4150-b3a2-e342ec9a0587", + "id": "ba22c5f7-1a97-4b4d-b8a5-159602d4556a", "name": "query-groups", "description": "${role_query-groups}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "fa821c09-19a3-48da-9980-c093ba931902", + "id": "949a5129-a6d2-499d-b8db-d7b10173b185", "name": "manage-authorization", "description": "${role_manage-authorization}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "317528d1-b1f5-43f9-b88b-6afdc53fd975", + "id": "ae00b0bb-e7e9-4ae8-a56e-48af50eed9f1", "name": "create-client", "description": "${role_create-client}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "c446519c-24d0-4d60-b4c0-401bf6dd80d6", + "id": "782deeb3-0531-4c5f-9dd1-ff696bdac9d8", "name": "realm-admin", "description": "${role_realm-admin}", "composite": true, @@ -260,52 +260,52 @@ } }, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "c197af85-bdb6-4caf-9e77-1631479e51db", + "id": "8165e64e-3f18-482a-9afa-70aadce19b41", "name": "query-clients", "description": "${role_query-clients}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "c5865ad3-936b-4506-b4eb-33b154b4837c", + "id": "f5a6fe09-9b98-4cbd-ac7c-d26989c81821", "name": "query-users", "description": "${role_query-users}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "90a4b005-4ecd-479d-9a8e-824a15735045", + "id": "80bc8d9a-25fa-4912-9134-c488230814cc", "name": "view-identity-providers", "description": "${role_view-identity-providers}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "56875e67-b1f4-49e2-b120-8ce33b5f4460", + "id": "378685b7-4ecf-4f90-b2d6-1e2f6182e6db", "name": "manage-clients", "description": "${role_manage-clients}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} }, { - "id": "4d7dc40e-66b8-4712-8bde-8d8c504c39b7", + "id": "33cff12a-001c-406a-968f-1719fdab1203", "name": "manage-identity-providers", "description": "${role_manage-identity-providers}", "composite": false, "clientRole": true, - "containerId": "9c267669-4de5-4203-a1c2-5b2de0003635", + "containerId": "92d8b143-681e-4d16-9963-9a04930fed7b", "attributes": {} } ], @@ -315,22 +315,22 @@ "tailscale": [], "broker": [ { - "id": "100f0a26-618b-4de8-a4f5-4dabbb6c034c", + "id": "14e1379d-e139-46a4-ad6d-edcff96fe25b", "name": "read-token", "description": "${role_read-token}", "composite": false, "clientRole": true, - "containerId": "2321d398-262d-4fd7-aef8-e6cc0ee017d7", + "containerId": "e5a0be05-fb1e-459b-8aeb-0fe78c7aa96b", "attributes": {} } ], "matrix": [ { - "id": "8730c207-c839-4766-86f6-2e7006867ac9", + "id": "f183bda9-c257-486b-bf4c-7915b3c13db2", "name": "uma_protection", "composite": false, "clientRole": true, - "containerId": "cb5a2e5c-2c4a-4acd-9389-3d63c77e1011", + "containerId": "0dfacc26-e7b8-42a5-9b58-c8c806a178d2", "attributes": {} } ], @@ -343,7 +343,7 @@ "openbikesensor-portal": [], "account": [ { - "id": "53cb4bb7-ad4f-4cb6-b19b-60c367a9fca0", + "id": "1e82f944-0882-458d-bd79-dfa1c13e50f2", "name": "manage-account", "description": "${role_manage-account}", "composite": true, @@ -355,47 +355,47 @@ } }, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "22e2c8e7-3a1e-4681-9584-77f375255072", + "id": "c4aa2553-73cd-464c-99a6-289827946182", "name": "view-profile", "description": "${role_view-profile}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "c2da86e7-0c40-4202-b01f-711f115444ac", + "id": "b408737a-f98e-4e8e-b63b-917e4c61d5dc", "name": "delete-account", "description": "${role_delete-account}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "4a8aa5fd-e4e5-4533-8886-6b0d54b10516", + "id": "05abafba-6269-4cd9-aa95-31cc83d2a6a1", "name": "manage-account-links", "description": "${role_manage-account-links}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "518f2427-8d18-4960-b958-2477fdfdae90", + "id": "1db5d1fc-b617-4cc7-872f-ec06fbeadcc9", "name": "view-applications", "description": "${role_view-applications}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "e29e2d62-1992-4437-ae33-b47346fcd59a", + "id": "d6255e12-935c-4ce7-8fb3-d00f61f9d5bd", "name": "manage-consent", "description": "${role_manage-consent}", "composite": true, @@ -407,25 +407,25 @@ } }, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "96e61a70-2586-4c90-b2ea-52987b3894e1", + "id": "572493be-3830-41ad-a9d2-7cb8f3fb9bbc", "name": "view-groups", "description": "${role_view-groups}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} }, { - "id": "f7531a5f-0b66-481e-8b6a-546ca6dff284", + "id": "4e4bc555-17e6-4654-8580-f7f9f83d59c6", "name": "view-consent", "description": "${role_view-consent}", "composite": false, "clientRole": true, - "containerId": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "containerId": "92e4c955-ce51-4467-91ae-8a6821685f8c", "attributes": {} } ] @@ -433,12 +433,12 @@ }, "groups": [], "defaultRole": { - "id": "49dd91a4-2176-4a84-aab0-37eb7f41fc1f", - "name": "default-roles-test.pub.solar", + "id": "2e271b49-ed2b-4dc0-a578-47e7571a2934", + "name": "default-roles-test.test.pub.solar", "description": "${role_default-roles}", "composite": true, "clientRole": false, - "containerId": "b5b70f0e-7a0f-4adb-b87b-3311d40e9686" + "containerId": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e" }, "requiredCredentials": [ "password" @@ -484,7 +484,7 @@ "webAuthnPolicyPasswordlessExtraOrigins": [], "users": [ { - "id": "eeecbf5f-4671-4f1b-9fa1-1cba5c7f5f7a", + "id": "a0a10fbb-2d1d-4bf1-918d-86659f7dcef1", "username": "service-account-admin-cli", "emailVerified": true, "createdTimestamp": 1714175492873, @@ -494,7 +494,7 @@ "disableableCredentialTypes": [], "requiredActions": [], "realmRoles": [ - "default-roles-test.pub.solar" + "default-roles-test.test.pub.solar" ], "clientRoles": { "realm-management": [ @@ -523,7 +523,7 @@ "groups": [] }, { - "id": "1237f773-ea8a-4db1-8fe5-5ec7924e6a10", + "id": "abf1af26-788c-40d8-91d3-a61e4b8c9a82", "username": "service-account-matrix", "emailVerified": true, "createdTimestamp": 1669426534368, @@ -533,7 +533,7 @@ "disableableCredentialTypes": [], "requiredActions": [], "realmRoles": [ - "default-roles-test.pub.solar" + "default-roles-test.test.pub.solar" ], "clientRoles": { "matrix": [ @@ -565,19 +565,19 @@ }, "clients": [ { - "id": "ffda02c2-3535-4b98-ab04-fe7dcb7b80a4", + "id": "92e4c955-ce51-4467-91ae-8a6821685f8c", "clientId": "account", "name": "${client_account}", "description": "", "rootUrl": "${authBaseUrl}", "adminUrl": "", - "baseUrl": "/realms/test.pub.solar/account/", + "baseUrl": "/realms/test.test.pub.solar/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/test.pub.solar/account/*" + "/realms/test.test.pub.solar/account/*" ], "webOrigins": [], "notBefore": 0, @@ -622,19 +622,19 @@ ] }, { - "id": "16e24154-8351-4862-866e-ccb326d3143a", + "id": "b4f2c47e-8b3c-4471-aa88-f000b5e819a2", "clientId": "account-console", "name": "${client_account-console}", "description": "", "rootUrl": "${authBaseUrl}", "adminUrl": "", - "baseUrl": "/realms/test.pub.solar/account/", + "baseUrl": "/realms/test.test.pub.solar/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/test.pub.solar/account/*" + "/realms/test.test.pub.solar/account/*" ], "webOrigins": [], "notBefore": 0, @@ -667,7 +667,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "a076f7e4-08b2-4804-8784-526bcbcbf293", + "id": "edf96156-695e-4912-924f-d7e40287ad1e", "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-resolve-mapper", @@ -690,7 +690,7 @@ ] }, { - "id": "43795547-9881-429e-86f3-94cbb2961f4e", + "id": "d1e8e47c-4384-4077-ac5e-83834342350d", "clientId": "admin-cli", "name": "${client_admin-cli}", "description": "", @@ -718,7 +718,7 @@ "oidc.ciba.grant.enabled": "false", "display.on.consent.screen": "false", "oauth2.device.authorization.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "backchannel.logout.revoke.offline.tokens": "false" }, @@ -727,7 +727,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "ba37bbed-bf37-433e-a87c-17be807bebef", + "id": "552baff5-ae55-4bd7-8c64-0bc5003a1552", "name": "Client ID", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -742,7 +742,7 @@ } }, { - "id": "223f12dc-ea4e-415f-b219-579af08f077e", + "id": "33fcc9d5-4c96-474c-87e2-add1e27deac3", "name": "Client IP Address", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -757,7 +757,7 @@ } }, { - "id": "197639ae-6f64-41fb-88db-30e02507ee2a", + "id": "dc809826-4eb0-4ff2-a73b-8d5da2d6488f", "name": "Client Host", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -787,7 +787,7 @@ ] }, { - "id": "2321d398-262d-4fd7-aef8-e6cc0ee017d7", + "id": "e5a0be05-fb1e-459b-8aeb-0fe78c7aa96b", "clientId": "broker", "name": "${client_broker}", "surrogateAuthRequired": false, @@ -825,23 +825,23 @@ ] }, { - "id": "eb879c6d-d130-4eac-82c2-abb0c3b90eb1", + "id": "b926deaa-81bc-46d0-9254-f38a2a3e839b", "clientId": "gitea", "name": "", "description": "", - "rootUrl": "https://git.test.pub.solar", - "adminUrl": "https://git.test.pub.solar", - "baseUrl": "https://git.test.pub.solar", + "rootUrl": "https://git.test.test.pub.solar", + "adminUrl": "https://git.test.test.pub.solar", + "baseUrl": "https://git.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://git.test.pub.solar/*" + "https://git.test.test.pub.solar/*" ], "webOrigins": [ - "https://git.test.pub.solar" + "https://git.test.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -854,7 +854,7 @@ "frontchannelLogout": true, "protocol": "openid-connect", "attributes": { - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "post.logout.redirect.uris": "+", "oauth2.device.authorization.grant.enabled": "false", "backchannel.logout.revoke.offline.tokens": "false", @@ -886,12 +886,12 @@ ] }, { - "id": "8f4a114b-d41c-4942-b6a8-0d306ed84edf", + "id": "b016fab5-bced-404a-93ba-c084d360701f", "clientId": "grafana", "name": "", - "description": "https://grafana.test.pub.solar", - "rootUrl": "https://grafana.test.pub.solar", - "adminUrl": "https://grafana.test.pub.solar", + "description": "https://grafana.test.test.pub.solar", + "rootUrl": "https://grafana.test.test.pub.solar", + "adminUrl": "https://grafana.test.test.pub.solar", "baseUrl": "/login/generic_oauth", "surrogateAuthRequired": false, "enabled": true, @@ -899,10 +899,10 @@ "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://grafana.test.pub.solar/login/generic_oauth" + "https://grafana.test.test.pub.solar/login/generic_oauth" ], "webOrigins": [ - "https://grafana.test.pub.solar" + "https://grafana.test.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -916,7 +916,7 @@ "protocol": "openid-connect", "attributes": { "oidc.ciba.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "post.logout.redirect.uris": "+", "display.on.consent.screen": "false", @@ -941,13 +941,13 @@ ] }, { - "id": "212cab9b-cf2c-4bfd-8a1a-1e0533c430f6", + "id": "28345b4b-d793-4cfa-b38b-18414aba4a19", "clientId": "mastodon", "name": "mastodon", "description": "", - "rootUrl": "https://mastodon.test.pub.solar", + "rootUrl": "https://mastodon.test.test.pub.solar", "adminUrl": "", - "baseUrl": "https://mastodon.test.pub.solar", + "baseUrl": "https://mastodon.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -955,10 +955,10 @@ "secret": "secret", "redirectUris": [ "", - "https://mastodon.test.pub.solar/auth/auth/openid_connect/callback" + "https://mastodon.test.test.pub.solar/auth/auth/openid_connect/callback" ], "webOrigins": [ - "https://mastodon.test.pub.solar/auth/openid_connect/callback" + "https://mastodon.test.test.pub.solar/auth/openid_connect/callback" ], "notBefore": 0, "bearerOnly": false, @@ -973,7 +973,7 @@ "attributes": { "tls-client-certificate-bound-access-tokens": "false", "oidc.ciba.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "client_credentials.use_refresh_token": "false", "acr.loa.map": "{}", @@ -1002,21 +1002,21 @@ ] }, { - "id": "cb5a2e5c-2c4a-4acd-9389-3d63c77e1011", + "id": "0dfacc26-e7b8-42a5-9b58-c8c806a178d2", "clientId": "matrix", "name": "", "description": "", - "rootUrl": "https://chat.test.pub.solar", + "rootUrl": "https://chat.test.test.pub.solar", "adminUrl": "", - "baseUrl": "https://chat.test.pub.solar", + "baseUrl": "https://chat.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://matrix.test.pub.solar/_synapse/client/oidc/callback", - "https://matrix.test.test.pub.solar/_synapse/client/oidc/callback" + "https://matrix.test.test.pub.solar/_synapse/client/oidc/callback", + "https://matrix.test.test.test.pub.solar/_synapse/client/oidc/callback" ], "webOrigins": [], "notBefore": 0, @@ -1031,14 +1031,14 @@ "frontchannelLogout": true, "protocol": "openid-connect", "attributes": { - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "oauth2.device.authorization.grant.enabled": "false", "backchannel.logout.revoke.offline.tokens": "false", "use.refresh.tokens": "true", "tls-client-certificate-bound-access-tokens": "false", "oidc.ciba.grant.enabled": "false", "backchannel.logout.session.required": "true", - "backchannel.logout.url": "https://chat.test.pub.solar/_synapse/client/oidc/backchannel_logout", + "backchannel.logout.url": "https://chat.test.test.pub.solar/_synapse/client/oidc/backchannel_logout", "client_credentials.use_refresh_token": "false", "acr.loa.map": "{}", "require.pushed.authorization.requests": "false", @@ -1050,7 +1050,7 @@ "nodeReRegistrationTimeout": -1, "protocolMappers": [ { - "id": "895d5d35-d9c9-489d-bddc-37c40a337188", + "id": "1db6ced8-2b55-4c7b-bb02-4a42f98e647f", "name": "Client Host", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -1064,7 +1064,7 @@ } }, { - "id": "969c7760-7d2a-4117-8505-53bd4d0c10b1", + "id": "51ef7c46-947f-4c31-b986-797840199b3c", "name": "Client IP Address", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -1078,7 +1078,7 @@ } }, { - "id": "63d3be07-5ef2-4b84-92ec-1a739b2f58e4", + "id": "61781e18-8473-42b4-a028-5df2dfc3e587", "name": "Client ID", "protocol": "openid-connect", "protocolMapper": "oidc-usersessionmodel-note-mapper", @@ -1104,59 +1104,16 @@ "phone", "offline_access", "microprofile-jwt" - ], - "authorizationSettings": { - "allowRemoteResourceManagement": true, - "policyEnforcementMode": "ENFORCING", - "resources": [ - { - "name": "Default Resource", - "type": "urn:matrix:resources:default", - "ownerManagedAccess": false, - "attributes": {}, - "_id": "559732a1-23b5-4af2-b14f-32b0ae2afa6e", - "uris": [ - "/*" - ] - } - ], - "policies": [ - { - "id": "95abcad9-b9ff-416e-8ab1-706bf6a7f406", - "name": "Default Policy", - "description": "A policy that grants access only for users within this realm", - "type": "js", - "logic": "POSITIVE", - "decisionStrategy": "AFFIRMATIVE", - "config": { - "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n" - } - }, - { - "id": "26997def-9683-47e4-a6c3-c7d5b69e4a38", - "name": "Default Permission", - "description": "A permission that applies to the default resource type", - "type": "resource", - "logic": "POSITIVE", - "decisionStrategy": "UNANIMOUS", - "config": { - "defaultResourceType": "urn:matrix:resources:default", - "applyPolicies": "[\"Default Policy\"]" - } - } - ], - "scopes": [], - "decisionStrategy": "UNANIMOUS" - } + ] }, { - "id": "0bc9fc84-2636-4bc3-9394-61ec4b804939", + "id": "18c20089-95f7-4037-980e-d9127e33354a", "clientId": "matrix-authentication-service", "name": "", "description": "Used for our hosted https://github.com/matrix-org/matrix-authentication-service", - "rootUrl": "https://matrix.test.pub.solar/", - "adminUrl": "https://matrix.test.pub.solar/", - "baseUrl": "https://matrix.test.pub.solar/", + "rootUrl": "https://matrix.test.test.pub.solar/", + "adminUrl": "https://matrix.test.test.pub.solar/", + "baseUrl": "https://matrix.test.test.pub.solar/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -1180,7 +1137,7 @@ "protocol": "openid-connect", "attributes": { "oidc.ciba.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "post.logout.redirect.uris": "+", "display.on.consent.screen": "false", @@ -1205,23 +1162,23 @@ ] }, { - "id": "f4fb631d-de88-48b2-be28-8ee74190c743", + "id": "d0209580-812b-4125-b75b-37790bc40394", "clientId": "mediawiki", "name": "", "description": "", - "rootUrl": "https://wiki.test.pub.solar", - "adminUrl": "https://wiki.test.pub.solar", - "baseUrl": "https://wiki.test.pub.solar", + "rootUrl": "https://wiki.test.test.pub.solar", + "adminUrl": "https://wiki.test.test.pub.solar", + "baseUrl": "https://wiki.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://wiki.test.pub.solar/*" + "https://wiki.test.test.pub.solar/*" ], "webOrigins": [ - "https://wiki.test.pub.solar" + "https://wiki.test.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -1235,7 +1192,7 @@ "protocol": "openid-connect", "attributes": { "oidc.ciba.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "post.logout.redirect.uris": "+", "display.on.consent.screen": "false", @@ -1260,23 +1217,23 @@ ] }, { - "id": "d830160a-1c09-4dfd-b984-cd9e69e72649", + "id": "ccb1e6ee-8ae6-4f7e-8034-f1c7df07778d", "clientId": "nextcloud", "name": "", "description": "", - "rootUrl": "https://cloud.test.pub.solar", - "adminUrl": "https://cloud.test.pub.solar", - "baseUrl": "https://cloud.test.pub.solar", + "rootUrl": "https://cloud.test.test.pub.solar", + "adminUrl": "https://cloud.test.test.pub.solar", + "baseUrl": "https://cloud.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://cloud.test.pub.solar/apps/user_oidc/code" + "https://cloud.test.test.pub.solar/apps/user_oidc/code" ], "webOrigins": [ - "https://cloud.test.pub.solar" + "https://cloud.test.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -1289,15 +1246,15 @@ "frontchannelLogout": true, "protocol": "openid-connect", "attributes": { - "client.secret.creation.time": 1724701666039, - "post.logout.redirect.uris": "https://cloud.test.pub.solar##https://cloud.test.pub.solar/##https://cloud.test.pub.solar/*", + "client.secret.creation.time": 1724762383467, + "post.logout.redirect.uris": "https://cloud.test.test.pub.solar##https://cloud.test.test.pub.solar/##https://cloud.test.test.pub.solar/*", "oauth2.device.authorization.grant.enabled": "false", "backchannel.logout.revoke.offline.tokens": "false", "use.refresh.tokens": "true", "tls-client-certificate-bound-access-tokens": "false", "oidc.ciba.grant.enabled": "false", "backchannel.logout.session.required": "true", - "backchannel.logout.url": "https://cloud.test.pub.solar/apps/user_oidc/backchannel-logout/test.pub.solar%20ID", + "backchannel.logout.url": "https://cloud.test.test.pub.solar/apps/user_oidc/backchannel-logout/test.test.pub.solar%20ID", "client_credentials.use_refresh_token": "false", "require.pushed.authorization.requests": "false", "acr.loa.map": "{}", @@ -1322,20 +1279,20 @@ ] }, { - "id": "49bc30c2-6e4c-4c57-a1ea-91073ee099e3", + "id": "773f562f-2057-4adf-a628-3bd1d4a938fa", "clientId": "openbikesensor-portal", "name": "", "description": "", - "rootUrl": "https://obs-portal.test.pub.solar", + "rootUrl": "https://obs-portal.test.test.pub.solar", "adminUrl": "", - "baseUrl": "https://obs-portal.test.pub.solar", + "baseUrl": "https://obs-portal.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://obs-portal.test.pub.solar/*" + "https://obs-portal.test.test.pub.solar/*" ], "webOrigins": [ "+" @@ -1351,7 +1308,7 @@ "frontchannelLogout": true, "protocol": "openid-connect", "attributes": { - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "post.logout.redirect.uris": "+", "oauth2.device.authorization.grant.enabled": "false", "backchannel.logout.revoke.offline.tokens": "false", @@ -1383,7 +1340,7 @@ ] }, { - "id": "9c267669-4de5-4203-a1c2-5b2de0003635", + "id": "92d8b143-681e-4d16-9963-9a04930fed7b", "clientId": "realm-management", "name": "${client_realm-management}", "surrogateAuthRequired": false, @@ -1421,17 +1378,17 @@ ] }, { - "id": "50e53a35-6c81-4c2d-8207-54f4a3ac4c78", + "id": "4bd3427e-5f23-4405-b007-8ef9b37992a6", "clientId": "security-admin-console", "name": "${client_security-admin-console}", "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/test.pub.solar/console/", + "baseUrl": "/admin/test.test.pub.solar/console/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/admin/test.pub.solar/console/*" + "/admin/test.test.pub.solar/console/*" ], "webOrigins": [ "+" @@ -1455,7 +1412,7 @@ "nodeReRegistrationTimeout": 0, "protocolMappers": [ { - "id": "9bdb45b8-f97c-442d-8ee3-769229817926", + "id": "56bcdfec-4727-4197-8729-1d962d54462e", "name": "locale", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1485,7 +1442,7 @@ ] }, { - "id": "92afe526-965a-45f3-9222-e410ec4b8be4", + "id": "cb163619-5b9e-4792-b3b4-6dc732e9e54b", "clientId": "tailscale", "name": "", "description": "", @@ -1515,7 +1472,7 @@ "oidc.ciba.grant.enabled": "false", "display.on.consent.screen": "false", "oauth2.device.authorization.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "backchannel.logout.revoke.offline.tokens": "false" }, @@ -1537,23 +1494,23 @@ ] }, { - "id": "2d56c796-877e-46d8-8b3a-c3040cdbe615", + "id": "6983c280-863c-4c3c-afe4-3a3b25a3fe8d", "clientId": "tt-rss", "name": "tt-rss", "description": "", - "rootUrl": "https://rss.test.pub.solar", - "adminUrl": "https://rss.test.pub.solar", - "baseUrl": "https://rss.test.pub.solar", + "rootUrl": "https://rss.test.test.pub.solar", + "adminUrl": "https://rss.test.test.pub.solar", + "baseUrl": "https://rss.test.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://rss.test.pub.solar" + "https://rss.test.test.pub.solar" ], "webOrigins": [ - "https://rss.test.pub.solar" + "https://rss.test.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -1569,7 +1526,7 @@ "oidc.ciba.grant.enabled": "false", "display.on.consent.screen": "false", "oauth2.device.authorization.grant.enabled": "false", - "client.secret.creation.time": 1724701666039, + "client.secret.creation.time": 1724762383467, "backchannel.logout.session.required": "true", "backchannel.logout.revoke.offline.tokens": "false" }, @@ -1593,7 +1550,7 @@ ], "clientScopes": [ { - "id": "7a97955f-1df4-4521-a57d-b19a038b5008", + "id": "4acd8fec-e8f8-4177-9e0c-02b0cae24675", "name": "microprofile-jwt", "description": "Microprofile - JWT built-in scope", "protocol": "openid-connect", @@ -1603,7 +1560,7 @@ }, "protocolMappers": [ { - "id": "b222f3ee-2b6e-4bd4-8250-c1690b457262", + "id": "6251a5f3-9848-4e44-ba1c-107a1e152bcb", "name": "groups", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", @@ -1618,7 +1575,7 @@ } }, { - "id": "931ce4b0-3f94-409d-b28d-ce75a1d46676", + "id": "2c5b4760-3b7b-492e-a439-b269f0c1ddd1", "name": "upn", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -1635,7 +1592,7 @@ ] }, { - "id": "6d0fe6eb-b776-4c3e-9468-763abec48df2", + "id": "ce352b30-2bf2-4c5e-9a03-5a87c68d84c5", "name": "acr", "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", "protocol": "openid-connect", @@ -1645,7 +1602,7 @@ }, "protocolMappers": [ { - "id": "b7d3f70f-b57f-44fe-9454-8f02aa7f8fe5", + "id": "6e80f14d-dcb3-4fe8-b3a7-aa1ba5589bf2", "name": "acr loa level", "protocol": "openid-connect", "protocolMapper": "oidc-acr-mapper", @@ -1658,7 +1615,7 @@ ] }, { - "id": "57645a5b-ce73-4e39-9c0b-76b92dca0ced", + "id": "1f2d40e8-1e00-4258-ad91-e82a6d395c98", "name": "roles", "description": "OpenID Connect scope for add user roles to the access token", "protocol": "openid-connect", @@ -1669,7 +1626,7 @@ }, "protocolMappers": [ { - "id": "92a37264-4062-4cae-a935-d8dc2bef141d", + "id": "edfb0bf7-df12-418a-bcad-4a3d3113d1c7", "name": "roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", @@ -1684,7 +1641,7 @@ } }, { - "id": "2bf1a28e-db9f-4aac-b9aa-3fe13bb135fb", + "id": "2da52394-cd26-4856-b472-8e7856261e50", "name": "client roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-client-role-mapper", @@ -1698,7 +1655,7 @@ } }, { - "id": "d390481c-37a5-492f-bb9e-670fdc9b2a09", + "id": "da7baf6a-4a5a-405e-9365-ed579a69d897", "name": "audience resolve", "protocol": "openid-connect", "protocolMapper": "oidc-audience-resolve-mapper", @@ -1706,7 +1663,7 @@ "config": {} }, { - "id": "71823193-58b0-474c-bdca-c369035fa572", + "id": "5c38e0e2-233e-4ae7-84ab-a5169dacb15c", "name": "realm roles", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-realm-role-mapper", @@ -1722,7 +1679,7 @@ ] }, { - "id": "1768debd-6e76-488a-a46d-4f5eda32a10e", + "id": "5800979c-8d2e-4793-94f3-17eeb146a3f6", "name": "web-origins", "description": "OpenID Connect scope for add allowed web origins to the access token", "protocol": "openid-connect", @@ -1733,7 +1690,7 @@ }, "protocolMappers": [ { - "id": "91eaf891-9a35-4e8f-a17a-8827498729d8", + "id": "a0cad486-4e3f-4dae-97e7-294392146c64", "name": "allowed web origins", "protocol": "openid-connect", "protocolMapper": "oidc-allowed-origins-mapper", @@ -1743,7 +1700,7 @@ ] }, { - "id": "9ad3b314-4926-4fb9-9dad-bc2912739ece", + "id": "3e25bced-55ed-4c90-9159-29ea8ab7c1ee", "name": "profile", "description": "OpenID Connect built-in scope: profile", "protocol": "openid-connect", @@ -1754,7 +1711,7 @@ }, "protocolMappers": [ { - "id": "9b4a04cc-34e3-4f6c-89c2-eb0c46a84c53", + "id": "207ec015-577b-44f2-8561-e4ba182a9a4d", "name": "given name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -1769,7 +1726,7 @@ } }, { - "id": "327f25d5-98d6-4355-b1bf-6d51f0add59e", + "id": "9c6e4c28-ba3c-4047-b621-cfda66c066da", "name": "username", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -1784,7 +1741,7 @@ } }, { - "id": "a0d8ba01-3158-4200-a0ed-b472971e1e10", + "id": "553e3c64-4548-497a-96ed-24556e9bc831", "name": "website", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1799,7 +1756,7 @@ } }, { - "id": "f2257f8c-700d-425f-8cf2-e1d6795f2b01", + "id": "b989905d-33f2-41d7-8767-248d13204621", "name": "nickname", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1814,7 +1771,7 @@ } }, { - "id": "0143f9a9-384c-4124-9e64-4cafb53eaf4f", + "id": "e63ecfe0-e5c8-4d7b-8647-e23819bddb55", "name": "gender", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1829,7 +1786,7 @@ } }, { - "id": "fc84b9a0-2505-4295-829b-5c0fd70378b2", + "id": "3423c51e-392b-47f9-88a0-16405bf96457", "name": "middle name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1844,7 +1801,7 @@ } }, { - "id": "3a1a616f-9388-42b3-b8a1-ee08f158ec99", + "id": "b835e5f5-b034-497e-904b-8d67f6118a68", "name": "full name", "protocol": "openid-connect", "protocolMapper": "oidc-full-name-mapper", @@ -1856,7 +1813,7 @@ } }, { - "id": "927ff720-aa71-4c04-9d28-e32cd2937fd3", + "id": "16d37225-5456-4f61-b146-25b871cf4b63", "name": "profile", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1871,7 +1828,7 @@ } }, { - "id": "01d095b6-e644-4c2f-9fcd-2b18c67a46c5", + "id": "582534c3-34bf-405a-bd30-8b63ab5386a9", "name": "picture", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1886,7 +1843,7 @@ } }, { - "id": "230373d9-d8bb-4f5c-b6a9-aaedcc2a5618", + "id": "ae41252f-1bd3-491d-b7e1-41c04d106c15", "name": "zoneinfo", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1901,7 +1858,7 @@ } }, { - "id": "6db5cf0c-ecc8-45c7-bc40-425a0ef3a5f6", + "id": "e3701a68-bd52-4f03-8359-5490db33fe11", "name": "locale", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1916,7 +1873,7 @@ } }, { - "id": "c7cc861c-9dd8-496f-802f-bd6017e7bcbf", + "id": "fcce03d4-061d-4bca-b1c6-919e2d325713", "name": "birthdate", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1931,7 +1888,7 @@ } }, { - "id": "a64dbb41-3312-4426-b60c-31707a4f7811", + "id": "3cd9ae0b-2720-4800-be3b-fd327795257f", "name": "family name", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -1946,7 +1903,7 @@ } }, { - "id": "3636403b-8b38-451d-8400-70d2d75ea2a7", + "id": "b9c561f8-d748-4c2b-914c-bfcfee8b0610", "name": "updated at", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -1963,7 +1920,7 @@ ] }, { - "id": "8f7ce907-4a00-475f-8d4f-5d83448256d6", + "id": "d0159ab1-245d-4a9a-89bb-792cbcc83a6f", "name": "offline_access", "description": "OpenID Connect built-in scope: offline_access", "protocol": "openid-connect", @@ -1973,7 +1930,7 @@ } }, { - "id": "fe3ed7de-cf40-4c3c-921f-c0af091d8a3c", + "id": "a89345ba-94a0-402c-93f9-c397b08757d9", "name": "role_list", "description": "SAML role list", "protocol": "saml", @@ -1983,7 +1940,7 @@ }, "protocolMappers": [ { - "id": "f5741693-65be-49bc-bf4f-c717ad1c159d", + "id": "8d6d19af-8ee0-44e0-b6d1-2bed27085d52", "name": "role list", "protocol": "saml", "protocolMapper": "saml-role-list-mapper", @@ -1997,7 +1954,7 @@ ] }, { - "id": "3dacdfcf-e86d-44fb-be12-e9d05c858121", + "id": "aceacddb-fe27-42d8-bfe5-492e40e99de9", "name": "email", "description": "OpenID Connect built-in scope: email", "protocol": "openid-connect", @@ -2008,7 +1965,7 @@ }, "protocolMappers": [ { - "id": "3ba989a9-9659-4e1e-ab3e-2cd6357abca5", + "id": "794bea3b-3dbe-438a-aadc-c8cf8d6deb0f", "name": "email verified", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -2023,7 +1980,7 @@ } }, { - "id": "9c727f43-b33d-413a-830f-3640a58e3af7", + "id": "0945ab02-fd67-4314-8453-714e98d88bf9", "name": "email", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-property-mapper", @@ -2040,7 +1997,7 @@ ] }, { - "id": "e1a49b03-0235-47bf-8c6d-6f4134f2a627", + "id": "a83b5895-d90b-4926-bbb5-6f87f38d2373", "name": "phone", "description": "OpenID Connect built-in scope: phone", "protocol": "openid-connect", @@ -2051,7 +2008,7 @@ }, "protocolMappers": [ { - "id": "c2efaab6-8177-4f16-a27a-3ab93229b60a", + "id": "525746a8-a3c9-4107-8872-9a6ef3de88f4", "name": "phone number verified", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -2066,7 +2023,7 @@ } }, { - "id": "92179260-b057-4bcc-a903-05f937a3254d", + "id": "5fe1435b-7747-4bf3-9800-cde99e30e965", "name": "phone number", "protocol": "openid-connect", "protocolMapper": "oidc-usermodel-attribute-mapper", @@ -2083,7 +2040,7 @@ ] }, { - "id": "6721b07c-704b-4ccc-a6b2-995df73c568f", + "id": "985ee9e5-2937-4c79-93e7-517833db98af", "name": "address", "description": "OpenID Connect built-in scope: address", "protocol": "openid-connect", @@ -2094,7 +2051,7 @@ }, "protocolMappers": [ { - "id": "1b28c15b-e6de-4a1d-83a0-58a519033338", + "id": "0c60790e-062a-434b-932d-ec379b7cfb70", "name": "address", "protocol": "openid-connect", "protocolMapper": "oidc-address-mapper", @@ -2139,22 +2096,22 @@ }, "smtpServer": { "password": "**********", - "replyToDisplayName": "test.pub.solar Support", + "replyToDisplayName": "test.test.pub.solar Support", "starttls": "false", "auth": "true", "port": "465", - "replyTo": "admins@test.pub.solar", - "host": "mail.test.pub.solar", - "from": "keycloak@test.pub.solar", - "fromDisplayName": "test.pub.solar ID", + "replyTo": "admins@test.test.pub.solar", + "host": "mail.test.test.pub.solar", + "from": "keycloak@test.test.pub.solar", + "fromDisplayName": "test.test.pub.solar ID", "envelopeFrom": "", "ssl": "true", - "user": "admins@test.pub.solar" + "user": "admins@test.test.pub.solar" }, - "loginTheme": "test.pub.solar", - "accountTheme": "test.pub.solar", - "adminTheme": "test.pub.solar", - "emailTheme": "test.pub.solar", + "loginTheme": "test.test.pub.solar", + "accountTheme": "test.test.pub.solar", + "adminTheme": "test.test.pub.solar", + "emailTheme": "test.test.pub.solar", "eventsEnabled": false, "eventsListeners": [ "jboss-logging" @@ -2167,7 +2124,7 @@ "components": { "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ { - "id": "89713f44-8fd5-473f-abe9-f4d27fcbbb11", + "id": "9eaa73ef-8a69-4a03-aac2-da7e434cba29", "name": "Trusted Hosts", "providerId": "trusted-hosts", "subType": "anonymous", @@ -2182,7 +2139,7 @@ } }, { - "id": "109840f6-fe6d-413f-a92f-984ec519bace", + "id": "c41957a0-bb80-4f1b-b969-f5dd7a5746d8", "name": "Max Clients Limit", "providerId": "max-clients", "subType": "anonymous", @@ -2194,7 +2151,7 @@ } }, { - "id": "12cd90ef-89e3-411e-8dc9-30b4b360526c", + "id": "56d675b3-a577-4422-a319-a7a129647de2", "name": "Allowed Client Scopes", "providerId": "allowed-client-templates", "subType": "anonymous", @@ -2206,7 +2163,7 @@ } }, { - "id": "93f5007f-4271-4ab5-b055-61bd70789eea", + "id": "2ddaf1dd-5c3c-4c9e-9bd3-6c182b65a355", "name": "Allowed Protocol Mapper Types", "providerId": "allowed-protocol-mappers", "subType": "authenticated", @@ -2225,7 +2182,7 @@ } }, { - "id": "551237c4-bd4a-4e65-ad2b-67adab62f368", + "id": "1e2ffe7b-7787-4ad4-8a62-1b8501dd294f", "name": "Full Scope Disabled", "providerId": "scope", "subType": "anonymous", @@ -2233,7 +2190,7 @@ "config": {} }, { - "id": "330eb614-8b38-4414-ad7a-0ae51083044d", + "id": "ffe5a467-1b6f-4adb-bfc5-b22b8b138051", "name": "Allowed Client Scopes", "providerId": "allowed-client-templates", "subType": "authenticated", @@ -2245,7 +2202,7 @@ } }, { - "id": "ca9bd5bb-21b2-401a-b5d0-0d5764f1b73a", + "id": "6a9c932a-bd54-4c51-afd8-8f153075dee9", "name": "Allowed Protocol Mapper Types", "providerId": "allowed-protocol-mappers", "subType": "anonymous", @@ -2264,7 +2221,7 @@ } }, { - "id": "49561521-b026-4fca-954b-49b7c527dc3a", + "id": "44970dd4-43d3-47c9-9cf3-54cac0f911b6", "name": "Consent Required", "providerId": "consent-required", "subType": "anonymous", @@ -2274,7 +2231,7 @@ ], "org.keycloak.userprofile.UserProfileProvider": [ { - "id": "48ba8848-a3a6-4444-918f-9663abe09391", + "id": "587e5c82-ece9-4f93-a270-d90def08a5a1", "providerId": "declarative-user-profile", "subComponents": {}, "config": { @@ -2286,7 +2243,7 @@ ], "org.keycloak.keys.KeyProvider": [ { - "id": "27867206-2a90-4889-90eb-2a289a17bba9", + "id": "fd419e6b-7a3d-4c3b-af9f-79b715072a09", "name": "aes-generated", "providerId": "aes-generated", "subComponents": {}, @@ -2297,7 +2254,7 @@ } }, { - "id": "37c64054-1aa5-4ade-a132-084dfdbbf290", + "id": "286304c9-b77c-4e94-bb1a-9c806b79026b", "name": "hmac-generated", "providerId": "hmac-generated", "subComponents": {}, @@ -2311,7 +2268,7 @@ } }, { - "id": "e7e81798-74aa-4232-bced-f8d94af77186", + "id": "0152050d-004e-4917-aed1-5a86c2f8e899", "name": "rsa-generated", "providerId": "rsa-generated", "subComponents": {}, @@ -2322,7 +2279,7 @@ } }, { - "id": "1e1ffc41-1c09-4953-bcd7-ac4b0381328a", + "id": "57652a70-c79e-49f1-b76d-402c8f9dae59", "name": "rsa-enc-generated", "providerId": "rsa-enc-generated", "subComponents": {}, @@ -2336,7 +2293,7 @@ } }, { - "id": "28bc97a0-1328-4f6a-a98b-64d7fd0de8c3", + "id": "a5bbf9eb-76da-4905-94ca-d6afc761fd44", "name": "fallback-HS512", "providerId": "hmac-generated", "subComponents": {}, @@ -2359,7 +2316,7 @@ "defaultLocale": "en", "authenticationFlows": [ { - "id": "ce72bdaa-3251-44c7-809f-5e246f29fad3", + "id": "fd65a300-6e34-461d-8c03-b1289aad4e74", "alias": "2FA_new", "description": "", "providerId": "basic-flow", @@ -2393,7 +2350,7 @@ ] }, { - "id": "3db2c722-66fd-4069-882b-5a9d78688760", + "id": "46261bd7-aede-4afd-ad55-1baaf7078784", "alias": "Account verification options", "description": "Method with which to verity the existing account", "providerId": "basic-flow", @@ -2419,7 +2376,7 @@ ] }, { - "id": "271b2e17-075d-4aad-9bab-c08e40b7d465", + "id": "6e090847-d703-49d5-a81a-876a1725187d", "alias": "Authentication forms", "description": "", "providerId": "basic-flow", @@ -2445,7 +2402,7 @@ ] }, { - "id": "ad1c9730-eaf3-4e13-9127-02f501b35255", + "id": "d7af4463-13de-4b7c-88fb-bf2d9642a902", "alias": "Browser - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -2471,7 +2428,7 @@ ] }, { - "id": "f4b016fc-6074-485e-a4a8-ad139d08de18", + "id": "a537deed-9a0e-4651-beee-a7df6f6ea9d9", "alias": "Direct Grant - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -2497,7 +2454,7 @@ ] }, { - "id": "222bbd1e-409d-451c-93d1-c0725ff1f6b3", + "id": "bbfb37e2-79bc-40f9-a51c-990cac4eac5a", "alias": "First broker login - Conditional OTP", "description": "Flow to determine if the OTP is required for the authentication", "providerId": "basic-flow", @@ -2523,7 +2480,7 @@ ] }, { - "id": "4a5cf709-4c21-451c-a891-86605e7f3ead", + "id": "77752d54-ba65-4a4b-bab9-b856d3b4ca7f", "alias": "Handle Existing Account", "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", "providerId": "basic-flow", @@ -2549,7 +2506,7 @@ ] }, { - "id": "004c7828-a040-4bc3-b941-de7a284c94b0", + "id": "ff93ce89-6a44-4a7b-b6a5-b546352b48ed", "alias": "Password_and_2FA_new", "description": "", "providerId": "basic-flow", @@ -2575,7 +2532,7 @@ ] }, { - "id": "dff9260d-f49e-423d-b821-a5200232e8d0", + "id": "064e7dd4-11ab-4802-aeea-dbf14b12b812", "alias": "Passwordless_or_2FA_new", "description": "", "providerId": "basic-flow", @@ -2601,7 +2558,7 @@ ] }, { - "id": "1722cdb4-38c3-417a-9380-2eda6a33f785", + "id": "d5eb3d13-ca58-437b-8054-16628fe66a2b", "alias": "Reset - Conditional OTP", "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", "providerId": "basic-flow", @@ -2627,7 +2584,7 @@ ] }, { - "id": "aa454877-1434-4c2e-8545-066b4f3b4054", + "id": "7c8f3f54-8f75-43d7-a4a9-b24e0f331303", "alias": "User creation or linking", "description": "Flow for the existing/non-existing user alternatives", "providerId": "basic-flow", @@ -2654,7 +2611,7 @@ ] }, { - "id": "42835c0a-1717-43b8-82bf-5170b67da30f", + "id": "5d00aa4b-30f6-43af-8da7-34e5d0b0c5c2", "alias": "Verify Existing Account by Re-authentication", "description": "Reauthentication of existing account", "providerId": "basic-flow", @@ -2680,7 +2637,7 @@ ] }, { - "id": "f36074df-ca57-4156-a946-665b77ef9a98", + "id": "bcac3181-3669-4b4b-b82f-11c973af8a82", "alias": "Webauthn Browser", "description": "browser based authentication with Webauthn enabled", "providerId": "basic-flow", @@ -2723,7 +2680,7 @@ ] }, { - "id": "84aeccff-bd3f-4432-9c41-6cdfd68ec8e5", + "id": "5a4f1055-7b4f-453c-b684-52a3703d19bd", "alias": "Webauthn Browser no required username 2FA", "description": "", "providerId": "basic-flow", @@ -2757,7 +2714,7 @@ ] }, { - "id": "9c5ad713-27b7-4dc1-a721-3460fc7ddfe0", + "id": "0a711676-70f4-49d2-9321-f9f2d02e9ae9", "alias": "Webauthn Browser no required username Password_and_2FA", "description": "Flow to determine if password + 2FA is required for the authentication", "providerId": "basic-flow", @@ -2775,7 +2732,7 @@ ] }, { - "id": "ce06e5fa-237a-46d4-89da-94401f4b42e0", + "id": "c4234d9e-f69c-4527-88d6-495111395dcd", "alias": "browser", "description": "browser based authentication", "providerId": "basic-flow", @@ -2817,7 +2774,7 @@ ] }, { - "id": "f922a19b-a3ae-4e31-981c-e5e05c48063d", + "id": "c45565f7-b435-4f5e-9ed8-916aa8c7083f", "alias": "clients", "description": "Base authentication for clients", "providerId": "client-flow", @@ -2859,7 +2816,7 @@ ] }, { - "id": "4d29a72e-cfc1-4a39-be48-5fe985b46244", + "id": "747807e9-a09d-4023-b2ad-828d3565e227", "alias": "direct grant", "description": "OpenID Connect Resource Owner Grant", "providerId": "basic-flow", @@ -2893,7 +2850,7 @@ ] }, { - "id": "2829ac62-1d83-4912-b63b-e8710ae0b4c2", + "id": "850def83-0df5-40fd-9612-4712c17026d4", "alias": "docker auth", "description": "Used by Docker clients to authenticate against the IDP", "providerId": "basic-flow", @@ -2911,7 +2868,7 @@ ] }, { - "id": "401235ad-1f4d-4764-afb6-5a8adf244604", + "id": "a9b06c96-ab41-4575-aa15-ca84d2145336", "alias": "first broker login", "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", "providerId": "basic-flow", @@ -2938,7 +2895,7 @@ ] }, { - "id": "d833da39-216f-4400-8e84-db5446a0e651", + "id": "68a7097b-5ceb-45a4-b83a-a3bbb7dc2c3e", "alias": "forms", "description": "Username, password, otp and other auth forms.", "providerId": "basic-flow", @@ -2964,7 +2921,7 @@ ] }, { - "id": "b3edb2a4-48fa-40b6-bcf3-5f178fc1e45e", + "id": "8c055d82-0b98-40e5-b29e-bcb6fae19667", "alias": "registration", "description": "registration flow", "providerId": "basic-flow", @@ -2983,7 +2940,7 @@ ] }, { - "id": "568f69e7-a69c-4299-ab41-c66473e98d01", + "id": "83559b54-9261-463d-80ee-421b4514bb48", "alias": "registration form", "description": "registration form", "providerId": "form-flow", @@ -3017,7 +2974,7 @@ ] }, { - "id": "4ae2919a-2033-4201-b9fc-b9f3320e939f", + "id": "17406fae-467f-40e1-9e0f-3a57081ff708", "alias": "reset credentials", "description": "Reset credentials for a user if they forgot their password or something", "providerId": "basic-flow", @@ -3059,7 +3016,7 @@ ] }, { - "id": "ff50f985-4ab1-428b-b0c8-2fd99f109198", + "id": "fe82c3e4-f483-4256-8559-c6ecc4e1c992", "alias": "saml ecp", "description": "SAML ECP Profile Authentication Flow", "providerId": "basic-flow", @@ -3079,21 +3036,21 @@ ], "authenticatorConfig": [ { - "id": "9794787b-bc86-4440-b6ae-eed8705e32ae", + "id": "3754fdcf-9c2e-46b3-adee-c2cc640cdd86", "alias": "Identity Provider Redirector", "config": { "defaultProvider": "oidc" } }, { - "id": "01d47dfc-83a7-49c6-89a1-ac543fe92f58", + "id": "c789eae7-96c1-4cc1-859f-845841c4e265", "alias": "create unique user config", "config": { "require.password.update.after.registration": "false" } }, { - "id": "7dce77a9-dba9-4fca-9aa4-8b78ed48ca4f", + "id": "4693e2ae-1386-4c25-9e13-8d9eb9a9c7a6", "alias": "review profile config", "config": { "update.profile.on.first.login": "missing" diff --git a/tests/support/keycloak-realm-export/src/index.mjs b/tests/support/keycloak-realm-export/src/index.mjs index 92e8c8e..fdccbff 100644 --- a/tests/support/keycloak-realm-export/src/index.mjs +++ b/tests/support/keycloak-realm-export/src/index.mjs @@ -14,10 +14,11 @@ const ID_KEYS = [ const renameDomain = (s) => s.replace(/pub.solar/g, 'test.pub.solar'); -const changeClientSecrets = (data) => ({ +const cleanClients = (data) => ({ ...data, clients: data.clients.map(c => ({ ...c, + authorizationSettings: undefined, ...(c.secret ? { secret: 'secret', attributes: { @@ -44,7 +45,7 @@ const changeIds = (node) => { ...acc, [key]: shouldChangeId(node, key) ? (() => { - const oldId = node[key]; + const oldId = node[key]; if (newIds[oldId]) { return newIds[oldId]; } @@ -63,7 +64,7 @@ const changeIds = (node) => { const fileContents = await readFile(filePath, { encoding: 'utf8' }); const data = JSON.parse(renameDomain(fileContents)); - const newData = changeIds(changeClientSecrets(data)); + const newData = changeIds(cleanClients(data)); console.log(JSON.stringify(newData, null, 2)); })(); -- 2.44.2 From 760d6e345822a0d880391b0f792131a9bb117e68 Mon Sep 17 00:00:00 2001 From: b12f Date: Wed, 28 Aug 2024 23:54:59 +0200 Subject: [PATCH 20/21] tests/keycloak: email sending works --- hosts/metronom/default.nix | 1 + hosts/metronom/email.nix | 45 ++++++ hosts/nachtigall/test-vm.nix | 53 -------- modules/mail/default.nix | 66 +++------ tests/keycloak.nix | 65 ++------- tests/support/{ca.nix => acme-server.nix} | 14 +- tests/support/client.nix | 14 +- tests/support/dns-server.nix | 70 ++++++++++ tests/support/global.nix | 31 ++--- tests/support/keycloak-realm-export/README.md | 2 +- .../keycloak-realm-export/realm-export.json | 128 +++++++++--------- .../keycloak-realm-export/src/index.mjs | 14 +- tests/support/mail-server.nix | 27 ++++ tests/website.nix | 8 +- 14 files changed, 269 insertions(+), 269 deletions(-) create mode 100644 hosts/metronom/email.nix delete mode 100644 hosts/nachtigall/test-vm.nix rename tests/support/{ca.nix => acme-server.nix} (86%) create mode 100644 tests/support/dns-server.nix create mode 100644 tests/support/mail-server.nix diff --git a/hosts/metronom/default.nix b/hosts/metronom/default.nix index a1699f1..a6b523c 100644 --- a/hosts/metronom/default.nix +++ b/hosts/metronom/default.nix @@ -7,6 +7,7 @@ ./networking.nix ./wireguard.nix + ./email.nix #./backups.nix ]; } diff --git a/hosts/metronom/email.nix b/hosts/metronom/email.nix new file mode 100644 index 0000000..71f3798 --- /dev/null +++ b/hosts/metronom/email.nix @@ -0,0 +1,45 @@ +{ config, flake, ... }: { + age.secrets.mail-hensoko.file = "${flake.self}/secrets/mail/hensoko.age"; + age.secrets.mail-teutat3s.file = "${flake.self}/secrets/mail/teutat3s.age"; + age.secrets.mail-admins.file = "${flake.self}/secrets/mail/admins.age"; + age.secrets.mail-bot.file = "${flake.self}/secrets/mail/bot.age"; + age.secrets.mail-crew.file = "${flake.self}/secrets/mail/crew.age"; + age.secrets.mail-erpnext.file = "${flake.self}/secrets/mail/erpnext.age"; + age.secrets.mail-hakkonaut.file = "${flake.self}/secrets/mail/hakkonaut.age"; + + mailserver = { + # A list of all login accounts. To create the password hashes, use + # nix-shell -p mkpasswd --run 'mkpasswd -R11 -m bcrypt' + loginAccounts = { + "admins@${config.pub-solar-os.networking.domain}" = { + hashedPasswordFile = config.age.secrets.mail-admins.path; + }; + "hakkonaut@${config.pub-solar-os.networking.domain}" = { + hashedPasswordFile = config.age.secrets.mail-hakkonaut.path; + }; + + "hensoko@pub.solar" = { + hashedPasswordFile = config.age.secrets.mail-hensoko.path; + quota = "2G"; + }; + "teutat3s@pub.solar" = { + hashedPasswordFile = config.age.secrets.mail-teutat3s.path; + quota = "2G"; + }; + "bot@pub.solar" = { + hashedPasswordFile = config.age.secrets.mail-bot.path; + quota = "2G"; + aliases = [ "hackernews-bot@pub.solar" ]; + }; + "crew@pub.solar" = { + hashedPasswordFile = config.age.secrets.mail-crew.path; + quota = "2G"; + aliases = [ "moderation@pub.solar" ]; + }; + "erpnext@pub.solar" = { + hashedPasswordFile = config.age.secrets.mail-erpnext.path; + quota = "2G"; + }; + }; + }; +} diff --git a/hosts/nachtigall/test-vm.nix b/hosts/nachtigall/test-vm.nix deleted file mode 100644 index ff0e4dc..0000000 --- a/hosts/nachtigall/test-vm.nix +++ /dev/null @@ -1,53 +0,0 @@ -{ flake, lib, ... }: - -{ - imports = [ - ./backups.nix - ./apps/nginx.nix - - ./apps/collabora.nix - ./apps/coturn.nix - ./apps/forgejo.nix - ./apps/keycloak.nix - ./apps/mailman.nix - ./apps/mastodon.nix - ./apps/mediawiki.nix - ./apps/nextcloud.nix - ./apps/nginx-mastodon.nix - ./apps/nginx-mastodon-files.nix - ./apps/nginx-prometheus-exporters.nix - ./apps/nginx-website.nix - ./apps/nginx-website-miom.nix - ./apps/opensearch.nix - ./apps/owncast.nix - ./apps/postgresql.nix - ./apps/prometheus-exporters.nix - ./apps/promtail.nix - ./apps/searx.nix - ./apps/tmate.nix - - ./apps/matrix/irc.nix - ./apps/matrix/mautrix-telegram.nix - ./apps/matrix/synapse.nix - ./apps/nginx-matrix.nix - ]; - - nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux"; - - security.acme.defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; - security.acme.preliminarySelfsigned = true; - - networking.useDHCP = true; - networking.interfaces."enp35s0".ipv4.addresses = [ - { - address = "10.0.0.1"; - prefixLength = 26; - } - ]; - networking.interfaces."enp35s0".ipv6.addresses = [ - { - address = "2a01:4f8:172:1c25::1"; - prefixLength = 64; - } - ]; -} diff --git a/modules/mail/default.nix b/modules/mail/default.nix index 9b28816..cfbfe87 100644 --- a/modules/mail/default.nix +++ b/modules/mail/default.nix @@ -1,62 +1,28 @@ -{ config, flake, ... }: - -{ - age.secrets.mail-hensoko.file = "${flake.self}/secrets/mail/hensoko.age"; - age.secrets.mail-teutat3s.file = "${flake.self}/secrets/mail/teutat3s.age"; - age.secrets.mail-admins.file = "${flake.self}/secrets/mail/admins.age"; - age.secrets.mail-bot.file = "${flake.self}/secrets/mail/bot.age"; - age.secrets.mail-crew.file = "${flake.self}/secrets/mail/crew.age"; - age.secrets.mail-erpnext.file = "${flake.self}/secrets/mail/erpnext.age"; - age.secrets.mail-hakkonaut.file = "${flake.self}/secrets/mail/hakkonaut.age"; - +{ config, ... }: { mailserver = { enable = true; - fqdn = "mail.pub.solar"; - domains = [ "pub.solar" ]; + fqdn = "mail.${config.pub-solar-os.networking.domain}"; + domains = [ config.pub-solar-os.networking.domain ]; # A list of all login accounts. To create the password hashes, use # nix-shell -p mkpasswd --run 'mkpasswd -R11 -m bcrypt' loginAccounts = { - "hensoko@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-hensoko.path; - quota = "2G"; - }; - "teutat3s@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-teutat3s.path; - quota = "2G"; - }; - "admins@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-admins.path; + "admins@${config.pub-solar-os.networking.domain}" = { quota = "2G"; aliases = [ - "abuse@pub.solar" - "alerts@pub.solar" - "forgejo@pub.solar" - "keycloak@pub.solar" - "mastodon-notifications@pub.solar" - "matrix@pub.solar" - "postmaster@pub.solar" - "nextcloud@pub.solar" - "no-reply@pub.solar" - "security@pub.solar" + "abuse@${config.pub-solar-os.networking.domain}" + "alerts@${config.pub-solar-os.networking.domain}" + "forgejo@${config.pub-solar-os.networking.domain}" + "keycloak@${config.pub-solar-os.networking.domain}" + "mastodon-notifications@${config.pub-solar-os.networking.domain}" + "matrix@${config.pub-solar-os.networking.domain}" + "postmaster@${config.pub-solar-os.networking.domain}" + "nextcloud@${config.pub-solar-os.networking.domain}" + "no-reply@${config.pub-solar-os.networking.domain}" + "security@${config.pub-solar-os.networking.domain}" ]; }; - "bot@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-bot.path; - quota = "2G"; - aliases = [ "hackernews-bot@pub.solar" ]; - }; - "crew@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-crew.path; - quota = "2G"; - aliases = [ "moderation@pub.solar" ]; - }; - "erpnext@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-erpnext.path; - quota = "2G"; - }; - "hakkonaut@pub.solar" = { - hashedPasswordFile = config.age.secrets.mail-hakkonaut.path; + "hakkonaut@${config.pub-solar-os.networking.domain}" = { quota = "2G"; }; }; @@ -66,5 +32,5 @@ certificateScheme = "acme-nginx"; }; security.acme.acceptTerms = true; - security.acme.defaults.email = "security@pub.solar"; + security.acme.defaults.email = "security@${config.pub-solar-os.networking.domain}"; } diff --git a/tests/keycloak.nix b/tests/keycloak.nix index 815d975..b189391 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -1,5 +1,6 @@ { self, + system, pkgs, lib, config, @@ -20,21 +21,10 @@ in node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; nodes = { - acme-server = { - imports = [ - self.nixosModules.home-manager - self.nixosModules.core - ./support/ca.nix - ]; - }; - - client = { - imports = [ - self.nixosModules.home-manager - self.nixosModules.core - ./support/client.nix - ]; - }; + dns-server.imports = [ ./support/dns-server.nix ]; + acme-server.imports = [ ./support/acme-server.nix ]; + mail-server.imports = [ ./support/mail-server.nix ]; + client.imports = [ ./support/client.nix ]; nachtigall = { imports = [ @@ -57,63 +47,30 @@ in database-password-file = "/tmp/dbf"; }; services.keycloak.database.createLocally = true; - - networking.interfaces.eth0.ipv4.addresses = [ - { - address = "192.168.1.3"; - prefixLength = 32; - } - ]; + services.keycloak.initialAdminPassword = "password"; }; }; - testScript = - { ... }: '' + testScript = { ... }: '' def puppeteer_run(cmd): client.succeed(f'puppeteer-run \'{cmd}\' ') start_all() + acme_server.wait_for_unit("system.slice") + mail_server.wait_for_unit("dovecot2.service") + mail_server.wait_for_unit("postfix.service") nachtigall.wait_for_unit("system.slice") nachtigall.succeed("ping 127.0.0.1 -c 2") nachtigall.wait_for_unit("nginx.service") - nachtigall.systemctl("stop keycloak.service") - nachtigall.wait_until_succeeds("if (($(ps aux | grep 'Dkc.home.dir=/run/keycloak' | grep -v grep | wc -l) == 0)); then true; else false; fi") - nachtigall.succeed("${pkgs.keycloak}/bin/kc.sh --verbose import --optimized --file=${realm-export}") - nachtigall.systemctl("start keycloak.service") - nachtigall.sleep(30) nachtigall.wait_until_succeeds("curl http://127.0.0.1:8080/") nachtigall.wait_until_succeeds("curl https://auth.test.pub.solar/") + nachtigall.succeed("${pkgs.keycloak}/bin/kcadm.sh create realms -f ${realm-export} --server http://localhost:8080 --realm master --user admin --password password --no-config") client.wait_for_unit("system.slice") client.wait_for_file("/tmp/puppeteer.sock") - puppeteer_run('page.goto("https://auth.test.pub.solar/admin/master/console")') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("admin-initial") - puppeteer_run('page.locator("[name=username]").fill("admin")') - puppeteer_run('page.locator("::-p-text(Sign In)").click()') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("admin-password") - puppeteer_run('page.locator("[name=password]").fill("password")') - puppeteer_run('page.locator("::-p-text(Sign In)").click()') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("admin-login") - puppeteer_run('page.locator("::-p-text(Realm settings)").click()') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("admin-theme") - puppeteer_run('page.locator("::-p-text(Themes)").click()') - puppeteer_run('page.waitForNetworkIdle()') - puppeteer_run('page.locator("#kc-login-theme").click()') - client.screenshot("admin-theme-changed") - puppeteer_run('page.locator("li button::-p-text(pub.solar)").click()') - puppeteer_run('page.locator("::-p-text(Save)").click()') - puppeteer_run('page.waitForNetworkIdle()') - client.screenshot("admin-theme-saved") - - - puppeteer_run('page.goto("https://auth.test.pub.solar")') puppeteer_run('page.waitForNetworkIdle()') client.screenshot("initial") diff --git a/tests/support/ca.nix b/tests/support/acme-server.nix similarity index 86% rename from tests/support/ca.nix rename to tests/support/acme-server.nix index 632c610..2ff408a 100644 --- a/tests/support/ca.nix +++ b/tests/support/acme-server.nix @@ -1,21 +1,19 @@ { + flake, pkgs, lib, config, ... }: { - imports = [ ./global.nix ]; + imports = [ + flake.self.nixosModules.home-manager + flake.self.nixosModules.core + ./global.nix + ]; systemd.tmpfiles.rules = [ "f /tmp/step-ca-intermediate-pw 1777 root root 10d password" ]; - networking.interfaces.eth0.ipv4.addresses = [ - { - address = "192.168.1.1"; - prefixLength = 32; - } - ]; - services.step-ca = let certificates = pkgs.stdenv.mkDerivation { diff --git a/tests/support/client.nix b/tests/support/client.nix index 43ad235..ddc8457 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -1,4 +1,5 @@ { + flake, pkgs, lib, config, @@ -9,7 +10,11 @@ let puppeteer-run = (pkgs.callPackage (import ./puppeteer-socket/puppeteer-run.nix) { }); in { - imports = [ ./global.nix ]; + imports = [ + flake.self.nixosModules.home-manager + flake.self.nixosModules.core + ./global.nix + ]; security.polkit.enable = true; @@ -41,11 +46,4 @@ in }; }; }; - - networking.interfaces.eth0.ipv4.addresses = [ - { - address = "192.168.1.2"; - prefixLength = 32; - } - ]; } diff --git a/tests/support/dns-server.nix b/tests/support/dns-server.nix new file mode 100644 index 0000000..c4a4abc --- /dev/null +++ b/tests/support/dns-server.nix @@ -0,0 +1,70 @@ +{ + config, + flake, + lib, + ... +}: { + imports = [ + flake.self.nixosModules.home-manager + flake.self.nixosModules.core + ./global.nix + ]; + + networking.nameservers = lib.mkForce [ + "193.110.81.0" #dns0.eu + "2a0f:fc80::" #dns0.eu + "185.253.5.0" #dns0.eu + "2a0f:fc81::" #dns0.eu + ]; + + services.resolved.enable = lib.mkForce false; + + networking.firewall.allowedUDPPorts = [53]; + networking.firewall.allowedTCPPorts = [53]; + + networking.interfaces.eth1.ipv4.addresses = [ + { + address = "192.168.1.254"; + prefixLength = 32; + } + ]; + + services.unbound = { + enable = true; + settings = { + server = { + interface = [ + "192.168.1.254" + ]; + access-control = [ + "0.0.0.0/0 allow" + ]; + local-zone = [ + "\"pub.solar\" transparent" + ]; + local-data = [ + "\"mail.${config.pub-solar-os.networking.domain}. 10800 IN CNAME mail-server\"" + "\"ca.${config.pub-solar-os.networking.domain}. 10800 IN CNAME acme-server\"" + "\"${config.pub-solar-os.networking.domain}. 10800 IN CNAME nachtigall\"" + "\"www.${config.pub-solar-os.networking.domain}. 10800 IN CNAME nachtigall\"" + "\"auth.${config.pub-solar-os.networking.domain}. 10800 IN CNAME nachtigall\"" + ]; + + tls-cert-bundle = "/etc/ssl/certs/ca-certificates.crt"; + }; + + forward-zone = [ + { + name = "."; + forward-addr = [ + "193.110.81.0#dns0.eu" + "2a0f:fc80::#dns0.eu" + "185.253.5.0#dns0.eu" + "2a0f:fc81::#dns0.eu" + ]; + forward-tls-upstream = "yes"; + } + ]; + }; + }; +} diff --git a/tests/support/global.nix b/tests/support/global.nix index f5e68c9..8736b4e 100644 --- a/tests/support/global.nix +++ b/tests/support/global.nix @@ -23,28 +23,13 @@ security.pam.services.sshd.allowNullPassword = true; - virtualisation.forwardPorts = - let - address = (builtins.elemAt config.networking.interfaces.eth0.ipv4.addresses 0).address; - lastAddressPart = builtins.elemAt (lib.strings.splitString "." address) 3; - in - [ - { - from = "host"; - host.port = 2000 + (lib.strings.toInt lastAddressPart); - guest.port = 22; - } - ]; + services.resolved.extraConfig = lib.mkForce '' + DNS=192.168.1.254 + Domains=~. + ''; - networking.interfaces.eth0.useDHCP = false; - - networking.hosts = { - "192.168.1.1" = [ "ca.${config.pub-solar-os.networking.domain}" ]; - "192.168.1.2" = [ "client.${config.pub-solar-os.networking.domain}" ]; - "192.168.1.3" = [ - "${config.pub-solar-os.networking.domain}" - "www.${config.pub-solar-os.networking.domain}" - "auth.${config.pub-solar-os.networking.domain}" - ]; - }; + environment.systemPackages = [ + pkgs.dig + ]; } + diff --git a/tests/support/keycloak-realm-export/README.md b/tests/support/keycloak-realm-export/README.md index 3edceaa..c3f71f7 100644 --- a/tests/support/keycloak-realm-export/README.md +++ b/tests/support/keycloak-realm-export/README.md @@ -2,4 +2,4 @@ 1. Export realm settings from keycloak, you'll get a file called `realm-export.json`. 2. Install dependencies for this package: `npm ci` -3. Clean the exported file: `npm start $downloadedExportJSON > realm-export.json +3. Clean the exported file: `node src/index.mjs $downloadedExportJSON > realm-export.json diff --git a/tests/support/keycloak-realm-export/realm-export.json b/tests/support/keycloak-realm-export/realm-export.json index aa76b76..63fd58c 100644 --- a/tests/support/keycloak-realm-export/realm-export.json +++ b/tests/support/keycloak-realm-export/realm-export.json @@ -1,6 +1,6 @@ { "id": "8cd6ddbb-d0d3-40ff-9f1e-efdfce05fa6e", - "realm": "test.test.pub.solar", + "realm": "test.pub.solar", "notBefore": 0, "defaultSignatureAlgorithm": "RS256", "revokeRefreshToken": false, @@ -57,7 +57,7 @@ }, { "id": "2e271b49-ed2b-4dc0-a578-47e7571a2934", - "name": "default-roles-test.test.pub.solar", + "name": "default-roles-test.pub.solar", "description": "${role_default-roles}", "composite": true, "composites": { @@ -434,7 +434,7 @@ "groups": [], "defaultRole": { "id": "2e271b49-ed2b-4dc0-a578-47e7571a2934", - "name": "default-roles-test.test.pub.solar", + "name": "default-roles-test.pub.solar", "description": "${role_default-roles}", "composite": true, "clientRole": false, @@ -494,7 +494,7 @@ "disableableCredentialTypes": [], "requiredActions": [], "realmRoles": [ - "default-roles-test.test.pub.solar" + "default-roles-test.pub.solar" ], "clientRoles": { "realm-management": [ @@ -533,7 +533,7 @@ "disableableCredentialTypes": [], "requiredActions": [], "realmRoles": [ - "default-roles-test.test.pub.solar" + "default-roles-test.pub.solar" ], "clientRoles": { "matrix": [ @@ -571,13 +571,13 @@ "description": "", "rootUrl": "${authBaseUrl}", "adminUrl": "", - "baseUrl": "/realms/test.test.pub.solar/account/", + "baseUrl": "/realms/test.pub.solar/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/test.test.pub.solar/account/*" + "/realms/test.pub.solar/account/*" ], "webOrigins": [], "notBefore": 0, @@ -628,13 +628,13 @@ "description": "", "rootUrl": "${authBaseUrl}", "adminUrl": "", - "baseUrl": "/realms/test.test.pub.solar/account/", + "baseUrl": "/realms/test.pub.solar/account/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/realms/test.test.pub.solar/account/*" + "/realms/test.pub.solar/account/*" ], "webOrigins": [], "notBefore": 0, @@ -829,19 +829,19 @@ "clientId": "gitea", "name": "", "description": "", - "rootUrl": "https://git.test.test.pub.solar", - "adminUrl": "https://git.test.test.pub.solar", - "baseUrl": "https://git.test.test.pub.solar", + "rootUrl": "https://git.test.pub.solar", + "adminUrl": "https://git.test.pub.solar", + "baseUrl": "https://git.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://git.test.test.pub.solar/*" + "https://git.test.pub.solar/*" ], "webOrigins": [ - "https://git.test.test.pub.solar" + "https://git.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -889,9 +889,9 @@ "id": "b016fab5-bced-404a-93ba-c084d360701f", "clientId": "grafana", "name": "", - "description": "https://grafana.test.test.pub.solar", - "rootUrl": "https://grafana.test.test.pub.solar", - "adminUrl": "https://grafana.test.test.pub.solar", + "description": "https://grafana.test.pub.solar", + "rootUrl": "https://grafana.test.pub.solar", + "adminUrl": "https://grafana.test.pub.solar", "baseUrl": "/login/generic_oauth", "surrogateAuthRequired": false, "enabled": true, @@ -899,10 +899,10 @@ "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://grafana.test.test.pub.solar/login/generic_oauth" + "https://grafana.test.pub.solar/login/generic_oauth" ], "webOrigins": [ - "https://grafana.test.test.pub.solar" + "https://grafana.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -945,9 +945,9 @@ "clientId": "mastodon", "name": "mastodon", "description": "", - "rootUrl": "https://mastodon.test.test.pub.solar", + "rootUrl": "https://mastodon.test.pub.solar", "adminUrl": "", - "baseUrl": "https://mastodon.test.test.pub.solar", + "baseUrl": "https://mastodon.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -955,10 +955,10 @@ "secret": "secret", "redirectUris": [ "", - "https://mastodon.test.test.pub.solar/auth/auth/openid_connect/callback" + "https://mastodon.test.pub.solar/auth/auth/openid_connect/callback" ], "webOrigins": [ - "https://mastodon.test.test.pub.solar/auth/openid_connect/callback" + "https://mastodon.test.pub.solar/auth/openid_connect/callback" ], "notBefore": 0, "bearerOnly": false, @@ -1006,17 +1006,17 @@ "clientId": "matrix", "name": "", "description": "", - "rootUrl": "https://chat.test.test.pub.solar", + "rootUrl": "https://chat.test.pub.solar", "adminUrl": "", - "baseUrl": "https://chat.test.test.pub.solar", + "baseUrl": "https://chat.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://matrix.test.test.pub.solar/_synapse/client/oidc/callback", - "https://matrix.test.test.test.pub.solar/_synapse/client/oidc/callback" + "https://matrix.test.pub.solar/_synapse/client/oidc/callback", + "https://matrix.test.test.pub.solar/_synapse/client/oidc/callback" ], "webOrigins": [], "notBefore": 0, @@ -1038,7 +1038,7 @@ "tls-client-certificate-bound-access-tokens": "false", "oidc.ciba.grant.enabled": "false", "backchannel.logout.session.required": "true", - "backchannel.logout.url": "https://chat.test.test.pub.solar/_synapse/client/oidc/backchannel_logout", + "backchannel.logout.url": "https://chat.test.pub.solar/_synapse/client/oidc/backchannel_logout", "client_credentials.use_refresh_token": "false", "acr.loa.map": "{}", "require.pushed.authorization.requests": "false", @@ -1111,9 +1111,9 @@ "clientId": "matrix-authentication-service", "name": "", "description": "Used for our hosted https://github.com/matrix-org/matrix-authentication-service", - "rootUrl": "https://matrix.test.test.pub.solar/", - "adminUrl": "https://matrix.test.test.pub.solar/", - "baseUrl": "https://matrix.test.test.pub.solar/", + "rootUrl": "https://matrix.test.pub.solar/", + "adminUrl": "https://matrix.test.pub.solar/", + "baseUrl": "https://matrix.test.pub.solar/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, @@ -1166,19 +1166,19 @@ "clientId": "mediawiki", "name": "", "description": "", - "rootUrl": "https://wiki.test.test.pub.solar", - "adminUrl": "https://wiki.test.test.pub.solar", - "baseUrl": "https://wiki.test.test.pub.solar", + "rootUrl": "https://wiki.test.pub.solar", + "adminUrl": "https://wiki.test.pub.solar", + "baseUrl": "https://wiki.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://wiki.test.test.pub.solar/*" + "https://wiki.test.pub.solar/*" ], "webOrigins": [ - "https://wiki.test.test.pub.solar" + "https://wiki.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -1221,19 +1221,19 @@ "clientId": "nextcloud", "name": "", "description": "", - "rootUrl": "https://cloud.test.test.pub.solar", - "adminUrl": "https://cloud.test.test.pub.solar", - "baseUrl": "https://cloud.test.test.pub.solar", + "rootUrl": "https://cloud.test.pub.solar", + "adminUrl": "https://cloud.test.pub.solar", + "baseUrl": "https://cloud.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://cloud.test.test.pub.solar/apps/user_oidc/code" + "https://cloud.test.pub.solar/apps/user_oidc/code" ], "webOrigins": [ - "https://cloud.test.test.pub.solar" + "https://cloud.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -1247,14 +1247,14 @@ "protocol": "openid-connect", "attributes": { "client.secret.creation.time": 1724762383467, - "post.logout.redirect.uris": "https://cloud.test.test.pub.solar##https://cloud.test.test.pub.solar/##https://cloud.test.test.pub.solar/*", + "post.logout.redirect.uris": "https://cloud.test.pub.solar##https://cloud.test.pub.solar/##https://cloud.test.pub.solar/*", "oauth2.device.authorization.grant.enabled": "false", "backchannel.logout.revoke.offline.tokens": "false", "use.refresh.tokens": "true", "tls-client-certificate-bound-access-tokens": "false", "oidc.ciba.grant.enabled": "false", "backchannel.logout.session.required": "true", - "backchannel.logout.url": "https://cloud.test.test.pub.solar/apps/user_oidc/backchannel-logout/test.test.pub.solar%20ID", + "backchannel.logout.url": "https://cloud.test.pub.solar/apps/user_oidc/backchannel-logout/test.pub.solar%20ID", "client_credentials.use_refresh_token": "false", "require.pushed.authorization.requests": "false", "acr.loa.map": "{}", @@ -1283,16 +1283,16 @@ "clientId": "openbikesensor-portal", "name": "", "description": "", - "rootUrl": "https://obs-portal.test.test.pub.solar", + "rootUrl": "https://obs-portal.test.pub.solar", "adminUrl": "", - "baseUrl": "https://obs-portal.test.test.pub.solar", + "baseUrl": "https://obs-portal.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://obs-portal.test.test.pub.solar/*" + "https://obs-portal.test.pub.solar/*" ], "webOrigins": [ "+" @@ -1382,13 +1382,13 @@ "clientId": "security-admin-console", "name": "${client_security-admin-console}", "rootUrl": "${authAdminUrl}", - "baseUrl": "/admin/test.test.pub.solar/console/", + "baseUrl": "/admin/test.pub.solar/console/", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "redirectUris": [ - "/admin/test.test.pub.solar/console/*" + "/admin/test.pub.solar/console/*" ], "webOrigins": [ "+" @@ -1498,19 +1498,19 @@ "clientId": "tt-rss", "name": "tt-rss", "description": "", - "rootUrl": "https://rss.test.test.pub.solar", - "adminUrl": "https://rss.test.test.pub.solar", - "baseUrl": "https://rss.test.test.pub.solar", + "rootUrl": "https://rss.test.pub.solar", + "adminUrl": "https://rss.test.pub.solar", + "baseUrl": "https://rss.test.pub.solar", "surrogateAuthRequired": false, "enabled": true, "alwaysDisplayInConsole": false, "clientAuthenticatorType": "client-secret", "secret": "secret", "redirectUris": [ - "https://rss.test.test.pub.solar" + "https://rss.test.pub.solar" ], "webOrigins": [ - "https://rss.test.test.pub.solar" + "https://rss.test.pub.solar" ], "notBefore": 0, "bearerOnly": false, @@ -2095,23 +2095,23 @@ "strictTransportSecurity": "max-age=31536000; includeSubDomains" }, "smtpServer": { - "password": "**********", - "replyToDisplayName": "test.test.pub.solar Support", + "password": "password", + "replyToDisplayName": "test.pub.solar Support", "starttls": "false", "auth": "true", "port": "465", - "replyTo": "admins@test.test.pub.solar", - "host": "mail.test.test.pub.solar", - "from": "keycloak@test.test.pub.solar", - "fromDisplayName": "test.test.pub.solar ID", + "replyTo": "admins@test.pub.solar", + "host": "mail.test.pub.solar", + "from": "keycloak@test.pub.solar", + "fromDisplayName": "test.pub.solar ID", "envelopeFrom": "", "ssl": "true", - "user": "admins@test.test.pub.solar" + "user": "admins@test.pub.solar" }, - "loginTheme": "test.test.pub.solar", - "accountTheme": "test.test.pub.solar", - "adminTheme": "test.test.pub.solar", - "emailTheme": "test.test.pub.solar", + "loginTheme": "pub.solar", + "accountTheme": "pub.solar", + "adminTheme": "pub.solar", + "emailTheme": "pub.solar", "eventsEnabled": false, "eventsListeners": [ "jboss-logging" diff --git a/tests/support/keycloak-realm-export/src/index.mjs b/tests/support/keycloak-realm-export/src/index.mjs index fdccbff..8aef73f 100644 --- a/tests/support/keycloak-realm-export/src/index.mjs +++ b/tests/support/keycloak-realm-export/src/index.mjs @@ -60,11 +60,23 @@ const changeIds = (node) => { return node; }; +const setExtra = (data) => ({ + ...data, + loginTheme: "pub.solar", + accountTheme: "pub.solar", + adminTheme: "pub.solar", + emailTheme: "pub.solar", + smtpServer: { + ...data.smtpServer, + password: "password", + }, +}); + (async () => { const fileContents = await readFile(filePath, { encoding: 'utf8' }); const data = JSON.parse(renameDomain(fileContents)); - const newData = changeIds(cleanClients(data)); + const newData = setExtra(changeIds(cleanClients(data))); console.log(JSON.stringify(newData, null, 2)); })(); diff --git a/tests/support/mail-server.nix b/tests/support/mail-server.nix new file mode 100644 index 0000000..b0a1ada --- /dev/null +++ b/tests/support/mail-server.nix @@ -0,0 +1,27 @@ +{ + config, + flake, + ... +}: { + imports = [ + flake.self.nixosModules.home-manager + flake.self.nixosModules.core + flake.self.nixosModules.mail + flake.inputs.simple-nixos-mailserver.nixosModule + ./global.nix + ]; + + # password is password + systemd.tmpfiles.rules = [ "f /tmp/emailpw 1777 root root 10d $2b$11$NV75HGZzMcIwrnVUZKXtxexX9DN52HayDW4eKrD1A8O3uIPnCquQ2" ]; + + mailserver = { + loginAccounts = { + "admins@${config.pub-solar-os.networking.domain}" = { + hashedPasswordFile = "/tmp/emailpw"; + }; + "hakkonaut@${config.pub-solar-os.networking.domain}" = { + hashedPasswordFile = "/tmp/emailpw"; + }; + }; + }; +} diff --git a/tests/website.nix b/tests/website.nix index 452262b..3a2b91e 100644 --- a/tests/website.nix +++ b/tests/website.nix @@ -14,13 +14,7 @@ node.specialArgs = self.outputs.nixosConfigurations.nachtigall._module.specialArgs; nodes = { - acme-server = { - imports = [ - self.nixosModules.home-manager - self.nixosModules.core - ./support/ca.nix - ]; - }; + acme-server.imports = [ ./support/acme-server.nix ]; nachtigall = { imports = [ -- 2.44.2 From 86c239d44cb031f87679fa95a1bdcaafb36c6e24 Mon Sep 17 00:00:00 2001 From: b12f Date: Tue, 3 Sep 2024 10:18:11 +0200 Subject: [PATCH 21/21] tests/keycloak: local imap syncing for client --- tests/keycloak.nix | 2 ++ tests/support/client.nix | 20 ++++++++++++++++++++ tests/support/mail-server.nix | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/tests/keycloak.nix b/tests/keycloak.nix index b189391..10df7c1 100644 --- a/tests/keycloak.nix +++ b/tests/keycloak.nix @@ -88,5 +88,7 @@ in puppeteer_run('page.locator("button::-p-text(Register)").click()') puppeteer_run('page.waitForNetworkIdle()') client.screenshot("after-register") + + client.succeed("offlineimap") ''; } diff --git a/tests/support/client.nix b/tests/support/client.nix index ddc8457..335dadb 100644 --- a/tests/support/client.nix +++ b/tests/support/client.nix @@ -45,5 +45,25 @@ in ]; }; }; + + accounts.email.accounts."test-user@${config.pub-solar-os.networking.domain}" = { + primary = true; + address = "test-user@${config.pub-solar-os.networking.domain}"; + userName = "test-user@${config.pub-solar-os.networking.domain}"; + passwordCommand = "echo password"; + realName = "Test User"; + imap = { + host = "mail.${config.pub-solar-os.networking.domain}"; + port = 993; + }; + smtp = { + host = "mail.${config.pub-solar-os.networking.domain}"; + port = 587; + tls.useStartTls = true; + }; + getmail.enable = true; + getmail.mailboxes = [ "ALL" ]; + msmtp.enable = true; + }; }; } diff --git a/tests/support/mail-server.nix b/tests/support/mail-server.nix index b0a1ada..27e6d7b 100644 --- a/tests/support/mail-server.nix +++ b/tests/support/mail-server.nix @@ -22,6 +22,10 @@ "hakkonaut@${config.pub-solar-os.networking.domain}" = { hashedPasswordFile = "/tmp/emailpw"; }; + "test-user@${config.pub-solar-os.networking.domain}" = { + quota = "1G"; + hashedPasswordFile = "/tmp/emailpw"; + }; }; }; } -- 2.44.2