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 96dbfd59c83b..33b0b3faea6f 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 @@ -723,6 +723,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 a1b0212fafc3..a671f08acfd9 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -288,6 +288,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 ]; };