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 accessKeycloak 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 ];
};