From ed30d3b02f56c1da19fb35459c3aa120b75eaf7b Mon Sep 17 00:00:00 2001 From: talyz Date: Tue, 5 Apr 2022 18:59:05 +0200 Subject: [PATCH] keycloak: Switch to the new Quarkus version of Keycloak With version 17 of Keycloak, the Wildfly based distribution was deprecated in favor of the one based on Quarkus. The difference in configuration is massive and to accommodate it, both the package and module had to be rewritten. --- .../from_md/release-notes/rl-2205.section.xml | 125 +++ .../manual/release-notes/rl-2205.section.md | 75 ++ nixos/modules/services/web-apps/keycloak.nix | 861 +++++++----------- nixos/modules/services/web-apps/keycloak.xml | 142 ++- nixos/tests/keycloak.nix | 22 +- pkgs/servers/keycloak/default.nix | 96 +- 6 files changed, 679 insertions(+), 642 deletions(-) diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 830730caef83..c815d50089aa 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -643,6 +643,131 @@ updated. + + + The Keycloak package (pkgs.keycloak) has + been switched from the Wildfly version, which will soon be + deprecated, to the Quarkus based version. The Keycloak service + (services.keycloak) has been updated to + accommodate the change and now differs from the previous + version in a few ways: + + + + + services.keycloak.extraConfig has been + removed in favor of the new + settings-style + services.keycloak.settings + option. The available options correspond directly to + parameters in conf/keycloak.conf. Some + of the most important parameters are documented as + suboptions, the rest can be found in the + All + configuration section of the Keycloak Server Installation + and Configuration Guide. While the new + configuration is much simpler and cleaner than the old + JBoss CLI one, this unfortunately mean that there’s no + straightforward way to convert an old configuration to the + new format and some settings may not even be available + anymore. + + + + + services.keycloak.frontendUrl was + removed and the frontend URL is now configured through the + hostname family of settings in + services.keycloak.settings + instead. See the + Hostname + section of the Keycloak Server Installation and + Configuration Guide for more details. Additionally, + /auth was removed from the default + context path and needs to be added back in + services.keycloak.settings.http-relative-path + if you want to keep compatibility with your current + clients. + + + + + services.keycloak.bindAddress, + services.keycloak.forceBackendUrlToFrontendUrl, + services.keycloak.httpPort and + services.keycloak.httpsPort have been + removed in favor of their equivalent options in + services.keycloak.settings. + httpPort and + httpsPort have additionally had their + types changed from str to + port. + + + The new names are as follows: + + + + + bindAddress: + services.keycloak.settings.http-host + + + + + forceBackendUrlToFrontendUrl: + services.keycloak.settings.hostname-strict-backchannel + + + + + httpPort: + services.keycloak.settings.http-port + + + + + httpsPort: + services.keycloak.settings.https-port + + + + + + + For example, when using a reverse proxy the migration could + look like this: + + + Before: + + + services.keycloak = { + enable = true; + httpPort = "8080"; + frontendUrl = "https://keycloak.example.com/auth"; + database.passwordFile = "/run/keys/db_password"; + extraConfig = { + "subsystem=undertow"."server=default-server"."http-listener=default".proxy-address-forwarding = true; + }; + }; + + + After: + + + services.keycloak = { + enable = true; + settings = { + http-port = 8080; + hostname = "keycloak.example.com"; + http-relative-path = "/auth"; + proxy = "edge"; + }; + database.passwordFile = "/run/keys/db_password"; + }; + + The MoinMoin wiki engine diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 333c2355232d..2e083d000d32 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -267,6 +267,81 @@ In addition to numerous new and upgraded packages, this release has the followin `media_store_path` was changed from `${dataDir}/media` to `${dataDir}/media_store` if `system.stateVersion` is at least `22.05`. Files will need to be manually moved to the new location if the `stateVersion` is updated. +- The Keycloak package (`pkgs.keycloak`) has been switched from the + Wildfly version, which will soon be deprecated, to the Quarkus based + version. The Keycloak service (`services.keycloak`) has been updated + to accommodate the change and now differs from the previous version + in a few ways: + + - `services.keycloak.extraConfig` has been removed in favor of the + new [settings-style](https://github.com/NixOS/rfcs/blob/master/rfcs/0042-config-option.md) + [`services.keycloak.settings`](#opt-services.keycloak.settings) + option. The available options correspond directly to parameters in + `conf/keycloak.conf`. Some of the most important parameters are + documented as suboptions, the rest can be found in the [All + configuration section of the Keycloak Server Installation and + Configuration + Guide](https://www.keycloak.org/server/all-config). While the new + configuration is much simpler and cleaner than the old JBoss CLI + one, this unfortunately mean that there's no straightforward way + to convert an old configuration to the new format and some + settings may not even be available anymore. + + - `services.keycloak.frontendUrl` was removed and the frontend URL + is now configured through the `hostname` family of settings in + [`services.keycloak.settings`](#opt-services.keycloak.settings) + instead. See the [Hostname section of the Keycloak Server + Installation and Configuration + Guide](https://www.keycloak.org/server/hostname) for more + details. Additionally, `/auth` was removed from the default + context path and needs to be added back in + [`services.keycloak.settings.http-relative-path`](#opt-services.keycloak.settings.http-relative-path) + if you want to keep compatibility with your current clients. + + - `services.keycloak.bindAddress`, + `services.keycloak.forceBackendUrlToFrontendUrl`, + `services.keycloak.httpPort` and `services.keycloak.httpsPort` + have been removed in favor of their equivalent options in + [`services.keycloak.settings`](#opt-services.keycloak.settings). `httpPort` + and `httpsPort` have additionally had their types changed from + `str` to `port`. + + The new names are as follows: + - `bindAddress`: [`services.keycloak.settings.http-host`](#opt-services.keycloak.settings.http-host) + - `forceBackendUrlToFrontendUrl`: [`services.keycloak.settings.hostname-strict-backchannel`](#opt-services.keycloak.settings.hostname-strict-backchannel) + - `httpPort`: [`services.keycloak.settings.http-port`](#opt-services.keycloak.settings.http-port) + - `httpsPort`: [`services.keycloak.settings.https-port`](#opt-services.keycloak.settings.https-port) + + For example, when using a reverse proxy the migration could look + like this: + + Before: + ```nix + services.keycloak = { + enable = true; + httpPort = "8080"; + frontendUrl = "https://keycloak.example.com/auth"; + database.passwordFile = "/run/keys/db_password"; + extraConfig = { + "subsystem=undertow"."server=default-server"."http-listener=default".proxy-address-forwarding = true; + }; + }; + ``` + + After: + ```nix + services.keycloak = { + enable = true; + settings = { + http-port = 8080; + hostname = "keycloak.example.com"; + http-relative-path = "/auth"; + proxy = "edge"; + }; + database.passwordFile = "/run/keys/db_password"; + }; + ``` + - The MoinMoin wiki engine (`services.moinmoin`) has been removed, because Python 2 is being retired from nixpkgs. - Services in the `hadoop` module previously set `openFirewall` to true by default. diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix index c4a2127663a9..2d817ca19234 100644 --- a/nixos/modules/services/web-apps/keycloak.nix +++ b/nixos/modules/services/web-apps/keycloak.nix @@ -4,20 +4,94 @@ let cfg = config.services.keycloak; opt = options.services.keycloak; - inherit (lib) types mkOption concatStringsSep mapAttrsToList - escapeShellArg recursiveUpdate optionalAttrs boolToString mkOrder - sort filterAttrs concatMapStringsSep concatStrings mkIf - optionalString optionals mkDefault literalExpression hasSuffix - foldl' isAttrs filter attrNames elem literalDocBook - maintainers; + inherit (lib) + types + mkMerge + mkOption + mkChangedOptionModule + mkRenamedOptionModule + mkRemovedOptionModule + concatStringsSep + mapAttrsToList + escapeShellArg + mkIf + optionalString + optionals + mkDefault + literalExpression + isAttrs + literalDocBook + maintainers + catAttrs + collect + splitString + ; - inherit (builtins) match typeOf; + 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 nullOr attrsOf path enum anything - package port; + 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 { @@ -30,89 +104,14 @@ in ''; }; - bindAddress = mkOption { - type = str; - default = "\${jboss.bind.address:0.0.0.0}"; - example = "127.0.0.1"; - description = '' - On which address Keycloak should accept new connections. - - A special syntax can be used to allow command line Java system - properties to override the value: ''${property.name:value} - ''; - }; - - httpPort = mkOption { - type = str; - default = "\${jboss.http.port:80}"; - example = "8080"; - description = '' - On which port Keycloak should listen for new HTTP connections. - - A special syntax can be used to allow command line Java system - properties to override the value: ''${property.name:value} - ''; - }; - - httpsPort = mkOption { - type = str; - default = "\${jboss.https.port:443}"; - example = "8443"; - description = '' - On which port Keycloak should listen for new HTTPS connections. - - A special syntax can be used to allow command line Java system - properties to override the value: ''${property.name:value} - ''; - }; - - frontendUrl = mkOption { - type = str; - apply = x: - if x == "" || hasSuffix "/" x then - x - else - x + "/"; - example = "keycloak.example.com/auth"; - description = '' - The public URL used as base for all frontend requests. Should - normally include a trailing /auth. - - See the - Hostname section of the Keycloak server installation - manual for more information. - ''; - }; - - forceBackendUrlToFrontendUrl = mkOption { - type = bool; - default = false; - example = true; - description = '' - Whether Keycloak should force all requests to go through the - frontend URL configured in . By default, - Keycloak allows backend requests to instead use its local - hostname or IP address and may also advertise it to clients - through its OpenID Connect Discovery endpoint. - - See the - Hostname section of the Keycloak server installation - manual for more information. - ''; - }; - 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. - - This should be a string, not a Nix path, since Nix paths are - copied into the world-readable Nix store. ''; }; @@ -120,28 +119,28 @@ in 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. - - This should be a string, not a Nix path, since Nix paths are - copied into the world-readable Nix store. ''; }; plugins = lib.mkOption { type = lib.types.listOf lib.types.path; - default = []; + default = [ ]; description = '' - Keycloak plugin jar, ear files or derivations with them + Keycloak plugin jar, ear files or derivations containing + them. Packaged plugins are available through + pkgs.keycloak.plugins. ''; }; database = { type = mkOption { - type = enum [ "mysql" "postgresql" ]; + type = enum [ "mysql" "mariadb" "postgresql" ]; default = "postgresql"; - example = "mysql"; + example = "mariadb"; description = '' The type of database Keycloak should connect to. ''; @@ -159,6 +158,7 @@ in let dbPorts = { postgresql = 5432; + mariadb = 3306; mysql = 3306; }; in @@ -207,6 +207,21 @@ in ''; }; + 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 to + false and create the database and user + manually. + ''; + }; + username = mkOption { type = str; default = "keycloak"; @@ -218,19 +233,16 @@ in To use this with a local database, set to false and create the database and user - manually. The database should be called - keycloak. + manually. ''; }; passwordFile = mkOption { type = path; example = "/run/keys/db_password"; + apply = assertStringPath "passwordFile"; description = '' - File containing the database password. - - This should be a string, not a Nix path, since Nix paths are - copied into the world-readable Nix store. + The path to a file containing the database password. ''; }; }; @@ -268,67 +280,181 @@ in ''; }; - extraConfig = mkOption { - type = attrsOf anything; - default = { }; + 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"; + description = '' + The path relative to / for serving + resources. + + + + 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 = 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-strict-backchannel = mkOption { + type = bool; + default = false; + example = true; + description = '' + Whether Keycloak should force all requests to go + through the frontend URL. By default, Keycloak allows + backend requests to instead use its local hostname or + IP address and may also advertise it to clients + through its OpenID Connect Discovery endpoint. + + 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 '' { - "subsystem=keycloak-server" = { - "spi=hostname" = { - "provider=default" = null; - "provider=fixed" = { - enabled = true; - properties.hostname = "keycloak.example.com"; - }; - default-provider = "fixed"; - }; - }; + hostname = "keycloak.example.com"; + proxy = "reencrypt"; + https-key-store-file = "/path/to/file"; + https-key-store-password = { _secret = "/run/keys/store_password"; }; } ''; + description = '' - Additional Keycloak configuration options to set in - standalone.xml. + Configuration options corresponding to parameters set in + conf/keycloak.conf. - Options are expressed as a Nix attribute set which matches the - structure of the jboss-cli configuration. The configuration is - effectively overlayed on top of the default configuration - shipped with Keycloak. To remove existing nodes and undefine - attributes from the default configuration, set them to - null. + Most available options are documented at . - The example configuration does the equivalent of the following - script, which removes the hostname provider - default, adds the deprecated hostname - provider fixed and defines it the default: - - - /subsystem=keycloak-server/spi=hostname/provider=default:remove() - /subsystem=keycloak-server/spi=hostname/provider=fixed:add(enabled = true, properties = { hostname = "keycloak.example.com" }) - /subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="fixed") - - - You can discover available options by using the jboss-cli.sh - program and by referring to the Keycloak - Server Installation and Configuration Guide. + 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 + conf/keycloak.conf file, the + https-key-store-password key will be set + to the contents of the + /run/keys/store_password file. ''; }; - }; config = let - # We only want to create a database if we're actually going to connect to it. + # 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 && cfg.database.type == "mysql"; + 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. + # 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" @@ -347,7 +473,7 @@ in } mkdir -p "$out" - for theme in ${cfg.package}/themes/*; do + for theme in ${keycloakBuild}/themes/*; do if [ -d "$theme" ]; then linkTheme "$theme" "$(basename "$theme")" fi @@ -356,329 +482,25 @@ in ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)} ''; - keycloakConfig' = foldl' recursiveUpdate - { - "interface=public".inet-address = cfg.bindAddress; - "socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort; - "subsystem=keycloak-server" = { - "spi=hostname"."provider=default" = { - enabled = true; - properties = { - inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl; - }; - }; - "theme=defaults".dir = toString themesBundle; - }; - "subsystem=datasources"."data-source=KeycloakDS" = { - max-pool-size = "20"; - user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username; - password = "@db-password@"; - }; - } [ - (optionalAttrs (cfg.database.type == "postgresql") { - "subsystem=datasources" = { - "jdbc-driver=postgresql" = { - driver-module-name = "org.postgresql"; - driver-name = "postgresql"; - driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource"; - }; - "data-source=KeycloakDS" = { - connection-url = "jdbc:postgresql://${cfg.database.host}:${toString cfg.database.port}/keycloak"; - driver-name = "postgresql"; - "connection-properties=ssl".value = boolToString cfg.database.useSSL; - } // (optionalAttrs (cfg.database.caCert != null) { - "connection-properties=sslrootcert".value = cfg.database.caCert; - "connection-properties=sslmode".value = "verify-ca"; - }); - }; - }) - (optionalAttrs (cfg.database.type == "mysql") { - "subsystem=datasources" = { - "jdbc-driver=mysql" = { - driver-module-name = "com.mysql"; - driver-name = "mysql"; - driver-class-name = "com.mysql.jdbc.Driver"; - }; - "data-source=KeycloakDS" = { - connection-url = "jdbc:mysql://${cfg.database.host}:${toString cfg.database.port}/keycloak"; - driver-name = "mysql"; - "connection-properties=useSSL".value = boolToString cfg.database.useSSL; - "connection-properties=requireSSL".value = boolToString cfg.database.useSSL; - "connection-properties=verifyServerCertificate".value = boolToString cfg.database.useSSL; - "connection-properties=characterEncoding".value = "UTF-8"; - valid-connection-checker-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"; - validate-on-match = true; - exception-sorter-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"; - } // (optionalAttrs (cfg.database.caCert != null) { - "connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}"; - "connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword"; - }); - }; - }) - (optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { - "socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort; - "subsystem=elytron" = mkOrder 900 { - "key-store=httpsKS" = mkOrder 900 { - path = "/run/keycloak/ssl/certificate_private_key_bundle.p12"; - credential-reference.clear-text = "notsosecretpassword"; - type = "JKS"; - }; - "key-manager=httpsKM" = mkOrder 901 { - key-store = "httpsKS"; - credential-reference.clear-text = "notsosecretpassword"; - }; - "server-ssl-context=httpsSSC" = mkOrder 902 { - key-manager = "httpsKM"; - }; - }; - "subsystem=undertow" = mkOrder 901 { - "server=default-server"."https-listener=https".ssl-context = "httpsSSC"; - }; - }) - cfg.extraConfig - ]; + keycloakConfig = lib.generators.toKeyValue { + mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" { + mkValueString = v: with builtins; + 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}"; + }; + }; - - /* Produces a JBoss CLI script that creates paths and sets - attributes matching those described by `attrs`. When the - script is run, the existing settings are effectively overlayed - by those from `attrs`. Existing attributes can be unset by - defining them `null`. - - JBoss paths and attributes / maps are distinguished by their - name, where paths follow a `key=value` scheme. - - Example: - mkJbossScript { - "subsystem=keycloak-server"."spi=hostname" = { - "provider=fixed" = null; - "provider=default" = { - enabled = true; - properties = { - inherit frontendUrl; - forceBackendUrlToFrontendUrl = false; - }; - }; - }; - } - => '' - if (outcome != success) of /:read-resource() - /:add() - end-if - if (outcome != success) of /subsystem=keycloak-server:read-resource() - /subsystem=keycloak-server:add() - end-if - if (outcome != success) of /subsystem=keycloak-server/spi=hostname:read-resource() - /subsystem=keycloak-server/spi=hostname:add() - end-if - if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=default:read-resource() - /subsystem=keycloak-server/spi=hostname/provider=default:add(enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" }) - end-if - if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true) - end-if - if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false) - end-if - if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth") - end-if - if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=fixed:read-resource() - /subsystem=keycloak-server/spi=hostname/provider=fixed:remove() - end-if - '' - */ - mkJbossScript = attrs: - let - /* From a JBoss path and an attrset, produces a JBoss CLI - snippet that writes the corresponding attributes starting - at `path`. Recurses down into subattrsets as necessary, - producing the variable name from its full path in the - attrset. - - Example: - writeAttributes "/subsystem=keycloak-server/spi=hostname/provider=default" { - enabled = true; - properties = { - forceBackendUrlToFrontendUrl = false; - frontendUrl = "https://keycloak.example.com/auth"; - }; - } - => '' - if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true) - end-if - if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false) - end-if - if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl") - /subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth") - end-if - '' - */ - writeAttributes = path: set: - let - # JBoss expressions like `${var}` need to be prefixed - # with `expression` to evaluate. - prefixExpression = string: - let - matchResult = match ''"\$\{.*}"'' string; - in - if matchResult != null then - "expression " + string - else - string; - - writeAttribute = attribute: value: - let - type = typeOf value; - in - if type == "set" then - let - names = attrNames value; - in - foldl' (text: name: text + (writeAttribute "${attribute}.${name}" value.${name})) "" names - else if value == null then '' - if (outcome == success) of ${path}:read-attribute(name="${attribute}") - ${path}:undefine-attribute(name="${attribute}") - end-if - '' - else if elem type [ "string" "path" "bool" ] then - let - value' = if type == "bool" then boolToString value else ''"${value}"''; - in - '' - if (result != ${prefixExpression value'}) of ${path}:read-attribute(name="${attribute}") - ${path}:write-attribute(name=${attribute}, value=${value'}) - end-if - '' - else throw "Unsupported type '${type}' for path '${path}'!"; - in - concatStrings - (mapAttrsToList - (attribute: value: (writeAttribute attribute value)) - set); - - - /* Produces an argument list for the JBoss `add()` function, - which adds a JBoss path and takes as its arguments the - required subpaths and attributes. - - Example: - makeArgList { - enabled = true; - properties = { - forceBackendUrlToFrontendUrl = false; - frontendUrl = "https://keycloak.example.com/auth"; - }; - } - => '' - enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" } - '' - */ - makeArgList = set: - let - makeArg = attribute: value: - let - type = typeOf value; - in - if type == "set" then - "${attribute} = { " + (makeArgList value) + " }" - else if elem type [ "string" "path" "bool" ] then - "${attribute} = ${if type == "bool" then boolToString value else ''"${value}"''}" - else if value == null then - "" - else - throw "Unsupported type '${type}' for attribute '${attribute}'!"; - - in - concatStringsSep ", " (mapAttrsToList makeArg set); - - - /* Recurses into the `nodeValue` attrset. Only subattrsets that - are JBoss paths, i.e. follows the `key=value` format, are recursed - into - the rest are considered JBoss attributes / maps. - */ - recurse = nodePath: nodeValue: - let - nodeContent = - if isAttrs nodeValue && nodeValue._type or "" == "order" then - nodeValue.content - else - nodeValue; - isPath = name: - let - value = nodeContent.${name}; - in - if (match ".*([=]).*" name) == [ "=" ] then - if isAttrs value || value == null then - true - else - throw "Parsing path '${concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!" - else - false; - jbossPath = "/" + concatStringsSep "/" nodePath; - children = if !isAttrs nodeContent then { } else nodeContent; - subPaths = filter isPath (attrNames children); - getPriority = name: - let - value = children.${name}; - in - if value._type or "" == "order" then value.priority else 1000; - orderedSubPaths = sort (a: b: getPriority a < getPriority b) subPaths; - jbossAttrs = filterAttrs (name: _: !(isPath name)) children; - text = - if nodeContent != null then - '' - if (outcome != success) of ${jbossPath}:read-resource() - ${jbossPath}:add(${makeArgList jbossAttrs}) - end-if - '' + writeAttributes jbossPath jbossAttrs - else - '' - if (outcome == success) of ${jbossPath}:read-resource() - ${jbossPath}:remove() - end-if - ''; - in - text + concatMapStringsSep "\n" (name: recurse (nodePath ++ [ name ]) children.${name}) orderedSubPaths; - in - recurse [ ] attrs; - - jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig'); - - keycloakConfig = pkgs.runCommand "keycloak-config" - { - nativeBuildInputs = [ cfg.package ]; - } - '' - export JBOSS_BASE_DIR="$(pwd -P)"; - export JBOSS_MODULEPATH="${cfg.package}/modules"; - export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log"; - - cp -r ${cfg.package}/standalone/configuration . - chmod -R u+rwX ./configuration - - mkdir -p {deployments,ssl} - - standalone.sh& - - attempt=1 - max_attempts=30 - while ! jboss-cli.sh --connect ':read-attribute(name=server-state)'; do - if [[ "$attempt" == "$max_attempts" ]]; then - echo "ERROR: Could not connect to Keycloak after $attempt attempts! Failing.." >&2 - exit 1 - fi - echo "Keycloak not fully started yet, retrying.. ($attempt/$max_attempts)" - sleep 1 - (( attempt++ )) - done - - jboss-cli.sh --connect --file=${jbossCliScript} --echo-command - - cp configuration/standalone.xml $out - ''; + 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; + }; in mkIf cfg.enable { @@ -689,7 +511,45 @@ in } ]; - environment.systemPackages = [ cfg.package ]; + 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}:${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" ]; @@ -752,41 +612,37 @@ in "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; in { after = databaseServices; bindsTo = databaseServices; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ - cfg.package + keycloakBuild openssl replace-secret ]; environment = { - JBOSS_LOG_DIR = "/var/log/keycloak"; - JBOSS_BASE_DIR = "/run/keycloak"; - JBOSS_MODULEPATH = "${cfg.package}/modules"; + KC_HOME_DIR = "/run/keycloak"; + KC_CONF_DIR = "/run/keycloak/conf"; }; serviceConfig = { - LoadCredential = [ - "db_password:${cfg.database.passwordFile}" - ] ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [ - "ssl_cert:${cfg.sslCertificate}" - "ssl_key:${cfg.sslCertificateKey}" - ]; + 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 = map (p: "keycloak/" + p) [ - "configuration" - "deployments" - "data" - "ssl" - "log" - "tmp" - ]; + RuntimeDirectory = "keycloak"; RuntimeDirectoryMode = 0700; - LogsDirectory = "keycloak"; AmbientCapabilities = "CAP_NET_BIND_SERVICE"; }; script = '' @@ -795,41 +651,30 @@ in umask u=rwx,g=,o= - install_plugin() { - if [ -d "$1" ]; then - find "$1" -type f \( -iname \*.ear -o -iname \*.jar \) -exec install -m 0500 -o keycloak -g keycloak "{}" "/run/keycloak/deployments/" \; - else - install -m 0500 -o keycloak -g keycloak "$1" "/run/keycloak/deployments/" - fi - } + ln -s ${themesBundle} /run/keycloak/themes + ln -s ${keycloakBuild}/providers /run/keycloak/ - install -m 0600 ${cfg.package}/standalone/configuration/*.properties /run/keycloak/configuration - install -T -m 0600 ${keycloakConfig} /run/keycloak/configuration/standalone.xml + install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf - replace-secret '@db-password@' "$CREDENTIALS_DIRECTORY/db_password" /run/keycloak/configuration/standalone.xml + ${secretReplacements} - export JAVA_OPTS=-Djboss.server.config.user.dir=/run/keycloak/configuration - add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}' - '' - + lib.optionalString (cfg.plugins != []) (lib.concatStringsSep "\n" (map (pl: "install_plugin ${lib.escapeShellArg pl}") cfg.plugins)) + "\n" - + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' - pushd /run/keycloak/ssl/ - cat "$CREDENTIALS_DIRECTORY/ssl_cert" <(echo) \ - "$CREDENTIALS_DIRECTORY/ssl_key" <(echo) \ - /etc/ssl/certs/ca-certificates.crt \ - > allcerts.pem - openssl pkcs12 -export -in "$CREDENTIALS_DIRECTORY/ssl_cert" -inkey "$CREDENTIALS_DIRECTORY/ssl_key" -chain \ - -name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \ - -CAfile allcerts.pem -passout pass:notsosecretpassword - popd + '' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) '' + mkdir -p /run/keycloak/ssl + cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/ '' + '' - ${cfg.package}/bin/standalone.sh + export KEYCLOAK_ADMIN=admin + export KEYCLOAK_ADMIN_PASSWORD=${cfg.initialAdminPassword} + kc.sh start ''; }; services.postgresql.enable = mkDefault createLocalPostgreSQL; services.mysql.enable = mkDefault createLocalMySQL; - services.mysql.package = mkIf createLocalMySQL pkgs.mariadb; + services.mysql.package = + let + dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80; + in + mkIf createLocalMySQL (mkDefault dbPkg); }; meta.doc = ./keycloak.xml; diff --git a/nixos/modules/services/web-apps/keycloak.xml b/nixos/modules/services/web-apps/keycloak.xml index cb706932f48f..861756e33ac0 100644 --- a/nixos/modules/services/web-apps/keycloak.xml +++ b/nixos/modules/services/web-apps/keycloak.xml @@ -27,10 +27,10 @@ Refer to the Admin - Console section of the Keycloak Server Administration Guide for - information on how to administer your - Keycloak instance. + xlink:href="https://www.keycloak.org/docs/latest/server_admin/index.html"> + Keycloak Server Administration Guide for information on + how to administer your Keycloak + instance. @@ -38,27 +38,28 @@ Database access Keycloak can be used with either - PostgreSQL or + PostgreSQL, + MariaDB or MySQL. Which one is used can be configured in . The selected database will automatically be enabled and a database and role created unless is changed from - its default of localhost or is set - to false. + linkend="opt-services.keycloak.database.host" /> is changed + from its default of localhost or is + set to false. External database access can also be configured by setting , , , and as - appropriate. Note that you need to manually create a database - called keycloak and allow the configured - database user full access to it. + appropriate. Note that you need to manually create the database + and allow the configured database user full access to it. @@ -79,22 +80,27 @@ -
- Frontend URL +
+ Hostname - The frontend URL is used as base for all frontend requests and - must be configured through . - It should normally include a trailing /auth - (the default web context). If you use a reverse proxy, you need - to set this option to "", so that frontend URL - is derived from HTTP headers. X-Forwarded-* headers - support also should be enabled, using - respective guidelines. + The hostname is used to build the public URL used as base for + all frontend requests and must be configured through . + + + If you're migrating an old Wildfly based Keycloak instance + and want to keep compatibility with your current clients, + you'll likely want to set to /auth. See the option description + for more details. + + + - + determines whether Keycloak should force all requests to go through the frontend URL. By default, Keycloak allows backend requests to @@ -104,10 +110,10 @@ - See the Hostname - section of the Keycloak Server Installation and Configuration - Guide for more information. + For more information on hostname configuration, see the Hostname + section of the Keycloak Server Installation and Configuration + Guide.
@@ -139,68 +145,40 @@
Themes - You can package custom themes and make them visible to Keycloak via - - option. See the + You can package custom themes and make them visible to + Keycloak through . See the Themes section of the Keycloak Server Development Guide - and respective NixOS option description for more information. + and the description of the aforementioned NixOS option for + more information.
-
- Additional configuration +
+ Configuration file settings - Additional Keycloak configuration options, for which no - explicit NixOS options are provided, - can be set in . + Keycloak server configuration parameters can be set in . These correspond + directly to options in + conf/keycloak.conf. Some of the most + important parameters are documented as suboptions, the rest can + be found in the All + configuration section of the Keycloak Server Installation and + Configuration Guide. - Options are expressed as a Nix attribute set which matches the - structure of the jboss-cli configuration. The configuration is - effectively overlayed on top of the default configuration - shipped with Keycloak. To remove existing nodes and undefine - attributes from the default configuration, set them to - null. - - - For example, the following script, which removes the hostname - provider default, adds the deprecated - hostname provider fixed and defines it the - default: - - -/subsystem=keycloak-server/spi=hostname/provider=default:remove() -/subsystem=keycloak-server/spi=hostname/provider=fixed:add(enabled = true, properties = { hostname = "keycloak.example.com" }) -/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="fixed") - - - would be expressed as - - -services.keycloak.extraConfig = { - "subsystem=keycloak-server" = { - "spi=hostname" = { - "provider=default" = null; - "provider=fixed" = { - enabled = true; - properties.hostname = "keycloak.example.com"; - }; - default-provider = "fixed"; - }; - }; -}; - - - - You can discover available options by using the jboss-cli.sh - program and by referring to the Keycloak - Server Installation and Configuration Guide. + 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 description of for an example.
+
Example configuration @@ -208,9 +186,11 @@ services.keycloak.extraConfig = { services.keycloak = { enable = true; + settings = { + hostname = "keycloak.example.com"; + hostname-strict-backchannel = true; + }; initialAdminPassword = "e6Wcm0RrtegMEHl"; # change on first login - frontendUrl = "https://keycloak.example.com/auth"; - forceBackendUrlToFrontendUrl = true; sslCertificate = "/run/keys/ssl_cert"; sslCertificateKey = "/run/keys/ssl_key"; database.passwordFile = "/run/keys/db_password"; diff --git a/nixos/tests/keycloak.nix b/nixos/tests/keycloak.nix index 267216a5e5a6..6ce136330d43 100644 --- a/nixos/tests/keycloak.nix +++ b/nixos/tests/keycloak.nix @@ -4,7 +4,7 @@ let certs = import ./common/acme/server/snakeoil-certs.nix; - frontendUrl = "https://${certs.domain}/auth"; + frontendUrl = "https://${certs.domain}"; initialAdminPassword = "h4IhoJFnt2iQIR9"; keycloakTest = import ./make-test-python.nix ( @@ -27,20 +27,23 @@ let services.keycloak = { enable = true; - inherit frontendUrl initialAdminPassword; - sslCertificate = certs.${certs.domain}.cert; - sslCertificateKey = certs.${certs.domain}.key; + settings = { + hostname = certs.domain; + }; + inherit initialAdminPassword; + sslCertificate = "${certs.${certs.domain}.cert}"; + sslCertificateKey = "${certs.${certs.domain}.key}"; database = { type = databaseType; username = "bogus"; - passwordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH"; + name = "also bogus"; + passwordFile = "${pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH"}"; }; plugins = with config.services.keycloak.package.plugins; [ keycloak-discord keycloak-metrics-spi ]; }; - environment.systemPackages = with pkgs; [ xmlstarlet html-tidy @@ -99,9 +102,9 @@ let in '' keycloak.start() keycloak.wait_for_unit("keycloak.service") + keycloak.wait_for_open_port(443) keycloak.wait_until_succeeds("curl -sSf ${frontendUrl}") - ### Realm Setup ### # Get an admin interface access token @@ -117,8 +120,8 @@ let # Register the metrics SPI keycloak.succeed( "${pkgs.jre}/bin/keytool -import -alias snakeoil -file ${certs.ca.cert} -storepass aaaaaa -keystore cacert.jks -noprompt", - "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' ${pkgs.keycloak}/bin/kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password '${initialAdminPassword}'", - "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' ${pkgs.keycloak}/bin/kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'", + "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh config credentials --server '${frontendUrl}' --realm master --user admin --password '${initialAdminPassword}'", + "KC_OPTS='-Djavax.net.ssl.trustStore=cacert.jks -Djavax.net.ssl.trustStorePassword=aaaaaa' kcadm.sh update events/config -s 'eventsEnabled=true' -s 'adminEventsEnabled=true' -s 'eventsListeners+=metrics-listener'", "curl -sSf '${frontendUrl}/realms/master/metrics' | grep '^keycloak_admin_event_UPDATE'" ) @@ -172,5 +175,6 @@ let in { postgres = keycloakTest { databaseType = "postgresql"; }; + mariadb = keycloakTest { databaseType = "mariadb"; }; mysql = keycloakTest { databaseType = "mysql"; }; } diff --git a/pkgs/servers/keycloak/default.nix b/pkgs/servers/keycloak/default.nix index 6f7723eb3448..f28679f2cf5f 100644 --- a/pkgs/servers/keycloak/default.nix +++ b/pkgs/servers/keycloak/default.nix @@ -1,73 +1,81 @@ -{ stdenv, lib, fetchzip, makeWrapper, jre, writeText, nixosTests -, postgresql_jdbc ? null, mysql_jdbc ? null +{ stdenv +, lib +, fetchzip +, makeWrapper +, jre +, writeText +, nixosTests , callPackage + +, confFile ? null +, plugins ? [ ] }: -let - mkModuleXml = name: jarFile: writeText "module.xml" '' - - - - - - - - - - - ''; -in stdenv.mkDerivation rec { - pname = "keycloak"; + pname = "keycloak"; version = "17.0.1"; src = fetchzip { - url = "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-legacy-${version}.zip"; - sha256 = "sha256-oqANNk7T6+CAS818v3I1QNsuxetL/JFZMqxouRn+kdE="; + url = "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-${version}.zip"; + sha256 = "sha256-z1LfTUoK+v4oQxdyIQruFhl5O333zirSrkPoTFgVfmI="; }; - nativeBuildInputs = [ makeWrapper ]; + nativeBuildInputs = [ makeWrapper jre ]; + + buildPhase = '' + runHook preBuild + '' + lib.optionalString (confFile != null) '' + install -m 0600 ${confFile} conf/keycloak.conf + '' + '' + install_plugin() { + if [ -d "$1" ]; then + find "$1" -type f \( -iname \*.ear -o -iname \*.jar \) -exec install -m 0500 "{}" "providers/" \; + else + install -m 0500 "$1" "providers/" + fi + } + ${lib.concatMapStringsSep "\n" (pl: "install_plugin ${lib.escapeShellArg pl}") plugins} + '' + '' + export KC_HOME_DIR=$out + export KC_CONF_DIR=$out/conf + + patchShebangs bin/kc.sh + bin/kc.sh build + + runHook postBuild + ''; installPhase = '' + runHook preInstall + mkdir $out cp -r * $out - rm -rf $out/bin/*.{ps1,bat} + rm $out/bin/*.{ps1,bat} - module_path=$out/modules/system/layers/keycloak - if ! [[ -d $module_path ]]; then - echo "The module path $module_path not found!" - exit 1 - fi + runHook postInstall + ''; - ${lib.optionalString (postgresql_jdbc != null) '' - mkdir -p $module_path/org/postgresql/main - ln -s ${postgresql_jdbc}/share/java/postgresql-jdbc.jar $module_path/org/postgresql/main/ - ln -s ${mkModuleXml "org.postgresql" "postgresql-jdbc.jar"} $module_path/org/postgresql/main/module.xml - ''} - ${lib.optionalString (mysql_jdbc != null) '' - mkdir -p $module_path/com/mysql/main - ln -s ${mysql_jdbc}/share/java/mysql-connector-java.jar $module_path/com/mysql/main/ - ln -s ${mkModuleXml "com.mysql" "mysql-connector-java.jar"} $module_path/com/mysql/main/module.xml - ''} + postFixup = '' + substituteInPlace $out/bin/kc.sh --replace '-Dkc.home.dir=$DIRNAME/../' '-Dkc.home.dir=$KC_HOME_DIR' + substituteInPlace $out/bin/kc.sh --replace '-Djboss.server.config.dir=$DIRNAME/../conf' '-Djboss.server.config.dir=$KC_CONF_DIR' - for script in add-user-keycloak.sh add-user.sh domain.sh elytron-tool.sh jboss-cli.sh jconsole.sh jdr.sh standalone.sh wsconsume.sh wsprovide.sh; do - wrapProgram $out/bin/$script --set JAVA_HOME ${jre} + for script in $(find $out/bin -type f -executable); do + wrapProgram "$script" --set JAVA_HOME ${jre} --prefix PATH : ${jre}/bin done - wrapProgram $out/bin/kcadm.sh --prefix PATH : ${jre}/bin - wrapProgram $out/bin/kcreg.sh --prefix PATH : ${jre}/bin ''; passthru = { tests = nixosTests.keycloak; - plugins = callPackage ./all-plugins.nix {}; + plugins = callPackage ./all-plugins.nix { }; + enabledPlugins = plugins; }; meta = with lib; { - homepage = "https://www.keycloak.org/"; + homepage = "https://www.keycloak.org/"; description = "Identity and access management for modern applications and services"; - license = licenses.asl20; - platforms = jre.meta.platforms; + license = licenses.asl20; + platforms = jre.meta.platforms; maintainers = with maintainers; [ ngerstle talyz ]; };