diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix
index aff4ed8dd608..a01f0049b2c7 100644
--- a/nixos/modules/services/web-apps/keycloak.nix
+++ b/nixos/modules/services/web-apps/keycloak.nix
@@ -3,299 +3,312 @@
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 (builtins) match typeOf;
in
{
- options.services.keycloak = {
-
- enable = lib.mkOption {
- type = lib.types.bool;
- default = false;
- example = true;
- description = ''
- Whether to enable the Keycloak identity and access management
- server.
- '';
- };
-
- bindAddress = lib.mkOption {
- type = lib.types.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 = lib.mkOption {
- type = lib.types.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 = lib.mkOption {
- type = lib.types.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 = lib.mkOption {
- type = lib.types.str;
- apply = x:
- if x == "" || lib.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 = lib.mkOption {
- type = lib.types.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 = lib.mkOption {
- type = lib.types.nullOr lib.types.path;
- default = null;
- example = "/run/keys/ssl_cert";
- 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.
- '';
- };
-
- sslCertificateKey = lib.mkOption {
- type = lib.types.nullOr lib.types.path;
- default = null;
- example = "/run/keys/ssl_key";
- 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.
- '';
- };
-
- database = {
- type = lib.mkOption {
- type = lib.types.enum [ "mysql" "postgresql" ];
- default = "postgresql";
- example = "mysql";
+ options.services.keycloak =
+ let
+ inherit (types) bool str nullOr attrsOf path enum anything
+ package port;
+ in
+ {
+ enable = mkOption {
+ type = bool;
+ default = false;
+ example = true;
description = ''
- The type of database Keycloak should connect to.
+ Whether to enable the Keycloak identity and access management
+ server.
'';
};
- host = lib.mkOption {
- type = lib.types.str;
- default = "localhost";
+ bindAddress = mkOption {
+ type = str;
+ default = "\${jboss.bind.address:0.0.0.0}";
+ example = "127.0.0.1";
description = ''
- Hostname of the database to connect to.
+ 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}
'';
};
- port =
- let
- dbPorts = {
- postgresql = 5432;
- mysql = 3306;
- };
- in
- lib.mkOption {
- type = lib.types.port;
- default = dbPorts.${cfg.database.type};
- defaultText = lib.literalDocBook "default port of selected database";
- description = ''
- Port of the database to connect to.
- '';
- };
-
- useSSL = lib.mkOption {
- type = lib.types.bool;
- default = cfg.database.host != "localhost";
- defaultText = lib.literalExpression ''config.${opt.database.host} != "localhost"'';
+ httpPort = mkOption {
+ type = str;
+ default = "\${jboss.http.port:80}";
+ example = "8080";
description = ''
- Whether the database connection should be secured by SSL /
- TLS.
+ 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}
'';
};
- caCert = lib.mkOption {
- type = lib.types.nullOr lib.types.path;
+ 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";
description = ''
- The SSL / TLS CA certificate that verifies the identity of the
- database server.
-
- Required when PostgreSQL is used and SSL is turned on.
-
- For MySQL, if left at null, the default
- Java keystore is used, which should suffice if the server
- certificate is issued by an official CA.
- '';
- };
-
- createLocally = lib.mkOption {
- type = lib.types.bool;
- default = true;
- description = ''
- Whether a database should be automatically created on the
- local host. Set this to false if you plan on provisioning a
- local database yourself. This has no effect if
- services.keycloak.database.host is customized.
- '';
- };
-
- username = lib.mkOption {
- type = lib.types.str;
- default = "keycloak";
- description = ''
- Username to use when connecting to an external or manually
- provisioned database; has no effect when a local database is
- automatically provisioned.
-
- To use this with a local database, set to
- false and create the database and user
- manually. The database should be called
- keycloak.
- '';
- };
-
- passwordFile = lib.mkOption {
- type = lib.types.path;
- example = "/run/keys/db_password";
- description = ''
- File containing the database password.
+ 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.
'';
};
- };
- package = lib.mkOption {
- type = lib.types.package;
- default = pkgs.keycloak;
- defaultText = lib.literalExpression "pkgs.keycloak";
- description = ''
- Keycloak package to use.
- '';
- };
+ sslCertificateKey = mkOption {
+ type = nullOr path;
+ default = null;
+ example = "/run/keys/ssl_key";
+ description = ''
+ The path to a PEM formatted private key to use for TLS/SSL
+ connections.
- initialAdminPassword = lib.mkOption {
- type = lib.types.str;
- default = "changeme";
- description = ''
- Initial password set for the admin
- user. The password is not stored safely and should be changed
- immediately in the admin panel.
- '';
- };
+ This should be a string, not a Nix path, since Nix paths are
+ copied into the world-readable Nix store.
+ '';
+ };
- themes = lib.mkOption {
- type = lib.types.attrsOf lib.types.package;
- default = {};
- description = ''
- Additional theme packages for Keycloak. Each theme is linked into
- subdirectory with a corresponding attribute name.
+ database = {
+ type = mkOption {
+ type = enum [ "mysql" "postgresql" ];
+ default = "postgresql";
+ example = "mysql";
+ description = ''
+ The type of database Keycloak should connect to.
+ '';
+ };
- Theme packages consist of several subdirectories which provide
- different theme types: for example, account,
- login etc. After adding a theme to this option you
- can select it by its name in Keycloak administration console.
- '';
- };
+ host = mkOption {
+ type = str;
+ default = "localhost";
+ description = ''
+ Hostname of the database to connect to.
+ '';
+ };
- extraConfig = lib.mkOption {
- type = lib.types.attrsOf lib.types.anything;
- default = { };
- example = lib.literalExpression ''
- {
- "subsystem=keycloak-server" = {
- "spi=hostname" = {
- "provider=default" = null;
- "provider=fixed" = {
- enabled = true;
- properties.hostname = "keycloak.example.com";
- };
- default-provider = "fixed";
+ port =
+ let
+ dbPorts = {
+ postgresql = 5432;
+ mysql = 3306;
};
+ in
+ mkOption {
+ type = port;
+ default = dbPorts.${cfg.database.type};
+ defaultText = literalDocBook "default port of selected database";
+ description = ''
+ Port of the database to connect to.
+ '';
};
- }
- '';
- description = ''
- Additional Keycloak configuration options to set in
- standalone.xml.
- 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.
+ useSSL = mkOption {
+ type = bool;
+ default = cfg.database.host != "localhost";
+ defaultText = literalExpression ''config.${opt.database.host} != "localhost"'';
+ description = ''
+ Whether the database connection should be secured by SSL /
+ TLS.
+ '';
+ };
- 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:
+ caCert = mkOption {
+ type = nullOr path;
+ default = null;
+ description = ''
+ The SSL / TLS CA certificate that verifies the identity of the
+ database server.
-
- /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")
-
+ Required when PostgreSQL is used and SSL is turned on.
+
+ For MySQL, if left at null, the default
+ Java keystore is used, which should suffice if the server
+ certificate is issued by an official CA.
+ '';
+ };
+
+ createLocally = mkOption {
+ type = bool;
+ default = true;
+ description = ''
+ Whether a database should be automatically created on the
+ local host. Set this to false if you plan on provisioning a
+ local database yourself. This has no effect if
+ services.keycloak.database.host is customized.
+ '';
+ };
+
+ username = mkOption {
+ type = str;
+ default = "keycloak";
+ description = ''
+ Username to use when connecting to an external or manually
+ provisioned database; has no effect when a local database is
+ automatically provisioned.
+
+ To use this with a local database, set to
+ false and create the database and user
+ manually. The database should be called
+ keycloak.
+ '';
+ };
+
+ passwordFile = mkOption {
+ type = path;
+ example = "/run/keys/db_password";
+ 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.
+ '';
+ };
+ };
+
+ package = mkOption {
+ type = package;
+ default = pkgs.keycloak;
+ defaultText = literalExpression "pkgs.keycloak";
+ description = ''
+ Keycloak package to use.
+ '';
+ };
+
+ initialAdminPassword = mkOption {
+ type = str;
+ default = "changeme";
+ description = ''
+ Initial password set for the admin
+ user. The password is not stored safely and should be changed
+ immediately in the admin panel.
+ '';
+ };
+
+ themes = mkOption {
+ type = attrsOf package;
+ default = { };
+ description = ''
+ Additional theme packages for Keycloak. Each theme is linked into
+ subdirectory with a corresponding attribute name.
+
+ Theme packages consist of several subdirectories which provide
+ different theme types: for example, account,
+ login etc. After adding a theme to this option you
+ can select it by its name in Keycloak administration console.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = attrsOf anything;
+ default = { };
+ example = literalExpression ''
+ {
+ "subsystem=keycloak-server" = {
+ "spi=hostname" = {
+ "provider=default" = null;
+ "provider=fixed" = {
+ enabled = true;
+ properties.hostname = "keycloak.example.com";
+ };
+ default-provider = "fixed";
+ };
+ };
+ }
+ '';
+ description = ''
+ Additional Keycloak configuration options to set in
+ standalone.xml.
+
+ 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.
+
+ 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.
+ '';
+ };
- You can discover available options by using the jboss-cli.sh
- program and by referring to the Keycloak
- Server Installation and Configuration Guide.
- '';
};
- };
-
config =
let
# We only want to create a database if we're actually going to connect to it.
@@ -303,12 +316,12 @@ in
createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql";
createLocalMySQL = databaseActuallyCreateLocally && cfg.database.type == "mysql";
- mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" {} ''
+ mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } ''
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
'';
# Both theme and theme type directories need to be actual directories in one hierarchy to pass Keycloak checks.
- themesBundle = pkgs.runCommand "keycloak-themes" {} ''
+ themesBundle = pkgs.runCommand "keycloak-themes" { } ''
linkTheme() {
theme="$1"
name="$2"
@@ -332,28 +345,29 @@ in
fi
done
- ${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: theme: "linkTheme ${theme} ${lib.escapeShellArg name}") cfg.themes)}
+ ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)}
'';
- keycloakConfig' = builtins.foldl' lib.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;
+ 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;
};
- "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@";
- };
- } [
- (lib.optionalAttrs (cfg.database.type == "postgresql") {
+ "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";
@@ -361,16 +375,16 @@ in
driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
};
"data-source=KeycloakDS" = {
- connection-url = "jdbc:postgresql://${cfg.database.host}:${builtins.toString cfg.database.port}/keycloak";
+ connection-url = "jdbc:postgresql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
driver-name = "postgresql";
- "connection-properties=ssl".value = lib.boolToString cfg.database.useSSL;
- } // (lib.optionalAttrs (cfg.database.caCert != null) {
+ "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";
});
};
})
- (lib.optionalAttrs (cfg.database.type == "mysql") {
+ (optionalAttrs (cfg.database.type == "mysql") {
"subsystem=datasources" = {
"jdbc-driver=mysql" = {
driver-module-name = "com.mysql";
@@ -378,38 +392,38 @@ in
driver-class-name = "com.mysql.jdbc.Driver";
};
"data-source=KeycloakDS" = {
- connection-url = "jdbc:mysql://${cfg.database.host}:${builtins.toString cfg.database.port}/keycloak";
+ connection-url = "jdbc:mysql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
driver-name = "mysql";
- "connection-properties=useSSL".value = lib.boolToString cfg.database.useSSL;
- "connection-properties=requireSSL".value = lib.boolToString cfg.database.useSSL;
- "connection-properties=verifyServerCertificate".value = lib.boolToString cfg.database.useSSL;
+ "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";
- } // (lib.optionalAttrs (cfg.database.caCert != null) {
+ } // (optionalAttrs (cfg.database.caCert != null) {
"connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
"connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
});
};
})
- (lib.optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
+ (optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
- "subsystem=elytron" = lib.mkOrder 900 {
- "key-store=httpsKS" = lib.mkOrder 900 {
+ "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" = lib.mkOrder 901 {
+ "key-manager=httpsKM" = mkOrder 901 {
key-store = "httpsKS";
credential-reference.clear-text = "notsosecretpassword";
};
- "server-ssl-context=httpsSSC" = lib.mkOrder 902 {
+ "server-ssl-context=httpsSSC" = mkOrder 902 {
key-manager = "httpsKM";
};
};
- "subsystem=undertow" = lib.mkOrder 901 {
+ "subsystem=undertow" = mkOrder 901 {
"server=default-server"."https-listener=https".ssl-context = "httpsSSC";
};
})
@@ -500,41 +514,42 @@ in
# with `expression` to evaluate.
prefixExpression = string:
let
- matchResult = builtins.match ''"\$\{.*}"'' string;
+ matchResult = match ''"\$\{.*}"'' string;
in
- if matchResult != null then
- "expression " + string
- else
- string;
+ if matchResult != null then
+ "expression " + string
+ else
+ string;
writeAttribute = attribute: value:
let
- type = builtins.typeOf value;
+ type = typeOf value;
in
- if type == "set" then
- let
- names = builtins.attrNames value;
- in
- builtins.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}")
+ 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 if builtins.elem type [ "string" "path" "bool" ] then
- let
- value' = if type == "bool" then lib.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}'!";
+ else throw "Unsupported type '${type}' for path '${path}'!";
in
- lib.concatStrings
- (lib.mapAttrsToList
- (attribute: value: (writeAttribute attribute value))
- set);
+ concatStrings
+ (mapAttrsToList
+ (attribute: value: (writeAttribute attribute value))
+ set);
/* Produces an argument list for the JBoss `add()` function,
@@ -557,19 +572,19 @@ in
let
makeArg = attribute: value:
let
- type = builtins.typeOf value;
+ type = typeOf value;
in
- if type == "set" then
- "${attribute} = { " + (makeArgList value) + " }"
- else if builtins.elem type [ "string" "path" "bool" ] then
- "${attribute} = ${if type == "bool" then lib.boolToString value else ''"${value}"''}"
- else if value == null then
- ""
- else
- throw "Unsupported type '${type}' for attribute '${attribute}'!";
+ 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
- lib.concatStringsSep ", " (lib.mapAttrsToList makeArg set);
+ concatStringsSep ", " (mapAttrsToList makeArg set);
/* Recurses into the `nodeValue` attrset. Only subattrsets that
@@ -579,7 +594,7 @@ in
recurse = nodePath: nodeValue:
let
nodeContent =
- if builtins.isAttrs nodeValue && nodeValue._type or "" == "order" then
+ if isAttrs nodeValue && nodeValue._type or "" == "order" then
nodeValue.content
else
nodeValue;
@@ -587,21 +602,23 @@ in
let
value = nodeContent.${name};
in
- if (builtins.match ".*([=]).*" name) == [ "=" ] then
- if builtins.isAttrs value || value == null then
- true
- else
- throw "Parsing path '${lib.concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!"
+ if (match ".*([=]).*" name) == [ "=" ] then
+ if isAttrs value || value == null then
+ true
else
- false;
- jbossPath = "/" + lib.concatStringsSep "/" nodePath;
- children = if !builtins.isAttrs nodeContent then {} else nodeContent;
- subPaths = builtins.filter isPath (builtins.attrNames children);
+ 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 = lib.sort (a: b: getPriority a < getPriority b) subPaths;
- jbossAttrs = lib.filterAttrs (name: _: !(isPath name)) children;
+ 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
''
@@ -615,45 +632,48 @@ in
${jbossPath}:remove()
end-if
'';
- in text + lib.concatMapStringsSep "\n" (name: recurse (nodePath ++ [name]) children.${name}) orderedSubPaths;
+ in
+ text + concatMapStringsSep "\n" (name: recurse (nodePath ++ [ name ]) children.${name}) orderedSubPaths;
in
- recurse [] attrs;
+ 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";
+ 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
+ cp -r ${cfg.package}/standalone/configuration .
+ chmod -R u+rwX ./configuration
- mkdir -p {deployments,ssl}
+ mkdir -p {deployments,ssl}
- standalone.sh&
+ 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
+ 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
+ jboss-cli.sh --connect --file=${jbossCliScript} --echo-command
- cp configuration/standalone.xml $out
- '';
+ cp configuration/standalone.xml $out
+ '';
in
- lib.mkIf cfg.enable {
-
+ mkIf cfg.enable
+ {
assertions = [
{
assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
@@ -663,7 +683,7 @@ in
environment.systemPackages = [ cfg.package ];
- systemd.services.keycloakPostgreSQLInit = lib.mkIf createLocalPostgreSQL {
+ systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
after = [ "postgresql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "postgresql.service" ];
@@ -687,7 +707,7 @@ in
'';
};
- systemd.services.keycloakMySQLInit = lib.mkIf createLocalMySQL {
+ systemd.services.keycloakMySQLInit = mkIf createLocalMySQL {
after = [ "mysql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "mysql.service" ];
@@ -714,13 +734,16 @@ in
let
databaseServices =
if createLocalPostgreSQL then [
- "keycloakPostgreSQLInit.service" "postgresql.service"
+ "keycloakPostgreSQLInit.service"
+ "postgresql.service"
]
else if createLocalMySQL then [
- "keycloakMySQLInit.service" "mysql.service"
+ "keycloakMySQLInit.service"
+ "mysql.service"
]
else [ ];
- in {
+ in
+ {
after = databaseServices;
bindsTo = databaseServices;
wantedBy = [ "multi-user.target" ];
@@ -735,52 +758,16 @@ in
JBOSS_MODULEPATH = "${cfg.package}/modules";
};
serviceConfig = {
- ExecStartPre = let
- startPreFullPrivileges = ''
- set -o errexit -o pipefail -o nounset -o errtrace
- shopt -s inherit_errexit
-
- umask u=rwx,g=,o=
-
- install -T -m 0400 -o keycloak -g keycloak '${cfg.database.passwordFile}' /run/keycloak/secrets/db_password
- '' + lib.optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
- install -T -m 0400 -o keycloak -g keycloak '${cfg.sslCertificate}' /run/keycloak/secrets/ssl_cert
- install -T -m 0400 -o keycloak -g keycloak '${cfg.sslCertificateKey}' /run/keycloak/secrets/ssl_key
- '';
- startPre = ''
- set -o errexit -o pipefail -o nounset -o errtrace
- shopt -s inherit_errexit
-
- umask u=rwx,g=,o=
-
- install -m 0600 ${cfg.package}/standalone/configuration/*.properties /run/keycloak/configuration
- install -T -m 0600 ${keycloakConfig} /run/keycloak/configuration/standalone.xml
-
- replace-secret '@db-password@' '/run/keycloak/secrets/db_password' /run/keycloak/configuration/standalone.xml
-
- export JAVA_OPTS=-Djboss.server.config.user.dir=/run/keycloak/configuration
- add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}'
- '' + lib.optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
- pushd /run/keycloak/ssl/
- cat /run/keycloak/secrets/ssl_cert <(echo) \
- /run/keycloak/secrets/ssl_key <(echo) \
- /etc/ssl/certs/ca-certificates.crt \
- > allcerts.pem
- openssl pkcs12 -export -in /run/keycloak/secrets/ssl_cert -inkey /run/keycloak/secrets/ssl_key -chain \
- -name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \
- -CAfile allcerts.pem -passout pass:notsosecretpassword
- popd
- '';
- in [
- "+${pkgs.writeShellScript "keycloak-start-pre-full-privileges" startPreFullPrivileges}"
- "${pkgs.writeShellScript "keycloak-start-pre" startPre}"
+ LoadCredential = [
+ "db_password:${cfg.database.passwordFile}"
+ ] ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
+ "ssl_cert:${cfg.sslCertificate}"
+ "ssl_key:${cfg.sslCertificateKey}"
];
- ExecStart = "${cfg.package}/bin/standalone.sh";
User = "keycloak";
Group = "keycloak";
DynamicUser = true;
RuntimeDirectory = map (p: "keycloak/" + p) [
- "secrets"
"configuration"
"deployments"
"data"
@@ -792,13 +779,39 @@ in
LogsDirectory = "keycloak";
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
};
+ script = ''
+ set -o errexit -o pipefail -o nounset -o errtrace
+ shopt -s inherit_errexit
+
+ umask u=rwx,g=,o=
+
+ install -m 0600 ${cfg.package}/standalone/configuration/*.properties /run/keycloak/configuration
+ install -T -m 0600 ${keycloakConfig} /run/keycloak/configuration/standalone.xml
+
+ replace-secret '@db-password@' "$CREDENTIALS_DIRECTORY/db_password" /run/keycloak/configuration/standalone.xml
+
+ export JAVA_OPTS=-Djboss.server.config.user.dir=/run/keycloak/configuration
+ add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}'
+ '' + 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
+ '' + ''
+ ${cfg.package}/bin/standalone.sh
+ '';
};
- services.postgresql.enable = lib.mkDefault createLocalPostgreSQL;
- services.mysql.enable = lib.mkDefault createLocalMySQL;
- services.mysql.package = lib.mkIf createLocalMySQL pkgs.mariadb;
+ services.postgresql.enable = mkDefault createLocalPostgreSQL;
+ services.mysql.enable = mkDefault createLocalMySQL;
+ services.mysql.package = mkIf createLocalMySQL pkgs.mariadb;
};
meta.doc = ./keycloak.xml;
- meta.maintainers = [ lib.maintainers.talyz ];
+ meta.maintainers = [ maintainers.talyz ];
}