nixos/keycloak: keycloak.database* -> keycloak.database.*

Move all database options to their own group / attribute. This makes
the configuration clearer and brings it in line with most other modern
modules.
wip/little-gl
talyz 3 years ago
parent 83e406e97a
commit dbf91bc2f1
No known key found for this signature in database
GPG Key ID: 2DED2151F4671A2B
  1. 208
      nixos/modules/services/web-apps/keycloak.nix
  2. 22
      nixos/modules/services/web-apps/keycloak.xml
  3. 9
      nixos/tests/keycloak.nix

@ -98,98 +98,100 @@ in
''; '';
}; };
databaseType = lib.mkOption { database = {
type = lib.types.enum [ "mysql" "postgresql" ]; type = lib.mkOption {
default = "postgresql"; type = lib.types.enum [ "mysql" "postgresql" ];
example = "mysql"; default = "postgresql";
description = '' example = "mysql";
The type of database Keycloak should connect to. description = ''
''; The type of database Keycloak should connect to.
}; '';
};
databaseHost = lib.mkOption { host = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "localhost"; default = "localhost";
description = '' description = ''
Hostname of the database to connect to. Hostname of the database to connect to.
''; '';
}; };
databasePort = port =
let let
dbPorts = { dbPorts = {
postgresql = 5432; postgresql = 5432;
mysql = 3306; mysql = 3306;
}; };
in in
lib.mkOption { lib.mkOption {
type = lib.types.port; type = lib.types.port;
default = dbPorts.${cfg.databaseType}; default = dbPorts.${cfg.database.type};
description = '' description = ''
Port of the database to connect to. Port of the database to connect to.
''; '';
}; };
databaseUseSSL = lib.mkOption { useSSL = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = cfg.databaseHost != "localhost"; default = cfg.database.host != "localhost";
description = '' description = ''
Whether the database connection should be secured by SSL / Whether the database connection should be secured by SSL /
TLS. TLS.
''; '';
}; };
databaseCaCert = lib.mkOption { caCert = lib.mkOption {
type = lib.types.nullOr lib.types.path; type = lib.types.nullOr lib.types.path;
default = null; default = null;
description = '' description = ''
The SSL / TLS CA certificate that verifies the identity of the The SSL / TLS CA certificate that verifies the identity of the
database server. database server.
Required when PostgreSQL is used and SSL is turned on. Required when PostgreSQL is used and SSL is turned on.
For MySQL, if left at <literal>null</literal>, the default For MySQL, if left at <literal>null</literal>, the default
Java keystore is used, which should suffice if the server Java keystore is used, which should suffice if the server
certificate is issued by an official CA. certificate is issued by an official CA.
''; '';
}; };
databaseCreateLocally = lib.mkOption { createLocally = lib.mkOption {
type = lib.types.bool; type = lib.types.bool;
default = true; default = true;
description = '' description = ''
Whether a database should be automatically created on the Whether a database should be automatically created on the
local host. Set this to false if you plan on provisioning a local host. Set this to false if you plan on provisioning a
local database yourself. This has no effect if local database yourself. This has no effect if
services.keycloak.databaseHost is customized. services.keycloak.database.host is customized.
''; '';
}; };
databaseUsername = lib.mkOption { username = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "keycloak"; default = "keycloak";
description = '' description = ''
Username to use when connecting to an external or manually Username to use when connecting to an external or manually
provisioned database; has no effect when a local database is provisioned database; has no effect when a local database is
automatically provisioned. automatically provisioned.
To use this with a local database, set <xref To use this with a local database, set <xref
linkend="opt-services.keycloak.databaseCreateLocally" /> to linkend="opt-services.keycloak.database.createLocally" /> to
<literal>false</literal> and create the database and user <literal>false</literal> and create the database and user
manually. The database should be called manually. The database should be called
<literal>keycloak</literal>. <literal>keycloak</literal>.
''; '';
}; };
databasePasswordFile = lib.mkOption { passwordFile = lib.mkOption {
type = lib.types.path; type = lib.types.path;
example = "/run/keys/db_password"; example = "/run/keys/db_password";
description = '' description = ''
File containing the database password. File containing the database password.
This should be a string, not a Nix path, since Nix paths are This should be a string, not a Nix path, since Nix paths are
copied into the world-readable Nix store. copied into the world-readable Nix store.
''; '';
};
}; };
package = lib.mkOption { package = lib.mkOption {
@ -262,12 +264,12 @@ in
config = config =
let 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.databaseCreateLocally && cfg.databaseHost == "localhost"; databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost";
createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.databaseType == "postgresql"; createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql";
createLocalMySQL = databaseActuallyCreateLocally && cfg.databaseType == "mysql"; createLocalMySQL = databaseActuallyCreateLocally && cfg.database.type == "mysql";
mySqlCaKeystore = pkgs.runCommandNoCC "mysql-ca-keystore" {} '' mySqlCaKeystore = pkgs.runCommandNoCC "mysql-ca-keystore" {} ''
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.databaseCaCert} -keystore $out -storepass notsosecretpassword -noprompt ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
''; '';
keycloakConfig' = builtins.foldl' lib.recursiveUpdate { keycloakConfig' = builtins.foldl' lib.recursiveUpdate {
@ -283,11 +285,11 @@ in
}; };
"subsystem=datasources"."data-source=KeycloakDS" = { "subsystem=datasources"."data-source=KeycloakDS" = {
max-pool-size = "20"; max-pool-size = "20";
user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.databaseUsername; user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
password = "@db-password@"; password = "@db-password@";
}; };
} [ } [
(lib.optionalAttrs (cfg.databaseType == "postgresql") { (lib.optionalAttrs (cfg.database.type == "postgresql") {
"subsystem=datasources" = { "subsystem=datasources" = {
"jdbc-driver=postgresql" = { "jdbc-driver=postgresql" = {
driver-module-name = "org.postgresql"; driver-module-name = "org.postgresql";
@ -295,16 +297,16 @@ in
driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource"; driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
}; };
"data-source=KeycloakDS" = { "data-source=KeycloakDS" = {
connection-url = "jdbc:postgresql://${cfg.databaseHost}:${builtins.toString cfg.databasePort}/keycloak"; connection-url = "jdbc:postgresql://${cfg.database.host}:${builtins.toString cfg.database.port}/keycloak";
driver-name = "postgresql"; driver-name = "postgresql";
"connection-properties=ssl".value = lib.boolToString cfg.databaseUseSSL; "connection-properties=ssl".value = lib.boolToString cfg.database.useSSL;
} // (lib.optionalAttrs (cfg.databaseCaCert != null) { } // (lib.optionalAttrs (cfg.database.caCert != null) {
"connection-properties=sslrootcert".value = cfg.databaseCaCert; "connection-properties=sslrootcert".value = cfg.database.caCert;
"connection-properties=sslmode".value = "verify-ca"; "connection-properties=sslmode".value = "verify-ca";
}); });
}; };
}) })
(lib.optionalAttrs (cfg.databaseType == "mysql") { (lib.optionalAttrs (cfg.database.type == "mysql") {
"subsystem=datasources" = { "subsystem=datasources" = {
"jdbc-driver=mysql" = { "jdbc-driver=mysql" = {
driver-module-name = "com.mysql"; driver-module-name = "com.mysql";
@ -312,16 +314,16 @@ in
driver-class-name = "com.mysql.jdbc.Driver"; driver-class-name = "com.mysql.jdbc.Driver";
}; };
"data-source=KeycloakDS" = { "data-source=KeycloakDS" = {
connection-url = "jdbc:mysql://${cfg.databaseHost}:${builtins.toString cfg.databasePort}/keycloak"; connection-url = "jdbc:mysql://${cfg.database.host}:${builtins.toString cfg.database.port}/keycloak";
driver-name = "mysql"; driver-name = "mysql";
"connection-properties=useSSL".value = lib.boolToString cfg.databaseUseSSL; "connection-properties=useSSL".value = lib.boolToString cfg.database.useSSL;
"connection-properties=requireSSL".value = lib.boolToString cfg.databaseUseSSL; "connection-properties=requireSSL".value = lib.boolToString cfg.database.useSSL;
"connection-properties=verifyServerCertificate".value = lib.boolToString cfg.databaseUseSSL; "connection-properties=verifyServerCertificate".value = lib.boolToString cfg.database.useSSL;
"connection-properties=characterEncoding".value = "UTF-8"; "connection-properties=characterEncoding".value = "UTF-8";
valid-connection-checker-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"; valid-connection-checker-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker";
validate-on-match = true; validate-on-match = true;
exception-sorter-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"; exception-sorter-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter";
} // (lib.optionalAttrs (cfg.databaseCaCert != null) { } // (lib.optionalAttrs (cfg.database.caCert != null) {
"connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}"; "connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
"connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword"; "connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
}); });
@ -573,8 +575,8 @@ in
assertions = [ assertions = [
{ {
assertion = (cfg.databaseUseSSL && cfg.databaseType == "postgresql") -> (cfg.databaseCaCert != null); assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
message = "A CA certificate must be specified (in 'services.keycloak.databaseCaCert') when PostgreSQL is used with SSL"; message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL";
} }
]; ];
@ -598,7 +600,7 @@ in
create_role="$(mktemp)" create_role="$(mktemp)"
trap 'rm -f "$create_role"' ERR EXIT trap 'rm -f "$create_role"' ERR EXIT
echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$(<'${cfg.databasePasswordFile}')' CREATEDB" > "$create_role" echo "CREATE ROLE keycloak WITH LOGIN PASSWORD '$(<'${cfg.database.passwordFile}')' CREATEDB" > "$create_role"
psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role" psql -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || psql -tA --file="$create_role"
psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"' psql -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
''; '';
@ -619,7 +621,7 @@ in
set -o errexit -o pipefail -o nounset -o errtrace set -o errexit -o pipefail -o nounset -o errtrace
shopt -s inherit_errexit shopt -s inherit_errexit
db_password="$(<'${cfg.databasePasswordFile}')" db_password="$(<'${cfg.database.passwordFile}')"
( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';" ( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
echo "CREATE DATABASE keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;" echo "CREATE DATABASE keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';" echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
@ -659,7 +661,7 @@ in
umask u=rwx,g=,o= umask u=rwx,g=,o=
install -T -m 0400 -o keycloak -g keycloak '${cfg.databasePasswordFile}' /run/keycloak/secrets/db_password install -T -m 0400 -o keycloak -g keycloak '${cfg.database.passwordFile}' /run/keycloak/secrets/db_password
'' + lib.optionalString (cfg.certificatePrivateKeyBundle != null) '' '' + lib.optionalString (cfg.certificatePrivateKeyBundle != null) ''
install -T -m 0400 -o keycloak -g keycloak '${cfg.certificatePrivateKeyBundle}' /run/keycloak/secrets/ssl_cert_pk_bundle install -T -m 0400 -o keycloak -g keycloak '${cfg.certificatePrivateKeyBundle}' /run/keycloak/secrets/ssl_cert_pk_bundle
''; '';

@ -41,31 +41,31 @@
<productname>PostgreSQL</productname> or <productname>PostgreSQL</productname> or
<productname>MySQL</productname>. Which one is used can be <productname>MySQL</productname>. Which one is used can be
configured in <xref configured in <xref
linkend="opt-services.keycloak.databaseType" />. The selected linkend="opt-services.keycloak.database.type" />. The selected
database will automatically be enabled and a database and role database will automatically be enabled and a database and role
created unless <xref created unless <xref
linkend="opt-services.keycloak.databaseHost" /> is changed from linkend="opt-services.keycloak.database.host" /> is changed from
its default of <literal>localhost</literal> or <xref its default of <literal>localhost</literal> or <xref
linkend="opt-services.keycloak.databaseCreateLocally" /> is set linkend="opt-services.keycloak.database.createLocally" /> is set
to <literal>false</literal>. to <literal>false</literal>.
</para> </para>
<para> <para>
External database access can also be configured by setting External database access can also be configured by setting
<xref linkend="opt-services.keycloak.databaseHost" />, <xref <xref linkend="opt-services.keycloak.database.host" />, <xref
linkend="opt-services.keycloak.databaseUsername" />, <xref linkend="opt-services.keycloak.database.username" />, <xref
linkend="opt-services.keycloak.databaseUseSSL" /> and <xref linkend="opt-services.keycloak.database.useSSL" /> and <xref
linkend="opt-services.keycloak.databaseCaCert" /> as linkend="opt-services.keycloak.database.caCert" /> as
appropriate. Note that you need to manually create a database appropriate. Note that you need to manually create a database
called <literal>keycloak</literal> and allow the configured called <literal>keycloak</literal> and allow the configured
database user full access to it. database user full access to it.
</para> </para>
<para> <para>
<xref linkend="opt-services.keycloak.databasePasswordFile" /> <xref linkend="opt-services.keycloak.database.passwordFile" />
must be set to the path to a file containing the password used must be set to the path to a file containing the password used
to log in to the database. If <xref linkend="opt-services.keycloak.databaseHost" /> to log in to the database. If <xref linkend="opt-services.keycloak.database.host" />
and <xref linkend="opt-services.keycloak.databaseCreateLocally" /> and <xref linkend="opt-services.keycloak.database.createLocally" />
are kept at their defaults, the database role are kept at their defaults, the database role
<literal>keycloak</literal> with that password is provisioned <literal>keycloak</literal> with that password is provisioned
on the local database instance. on the local database instance.
@ -196,7 +196,7 @@ services.keycloak = {
<link linkend="opt-services.keycloak.frontendUrl">frontendUrl</link> = "https://keycloak.example.com/auth"; <link linkend="opt-services.keycloak.frontendUrl">frontendUrl</link> = "https://keycloak.example.com/auth";
<link linkend="opt-services.keycloak.forceBackendUrlToFrontendUrl">forceBackendUrlToFrontendUrl</link> = true; <link linkend="opt-services.keycloak.forceBackendUrlToFrontendUrl">forceBackendUrlToFrontendUrl</link> = true;
<link linkend="opt-services.keycloak.certificatePrivateKeyBundle">certificatePrivateKeyBundle</link> = "/run/keys/ssl_cert"; <link linkend="opt-services.keycloak.certificatePrivateKeyBundle">certificatePrivateKeyBundle</link> = "/run/keys/ssl_cert";
<link linkend="opt-services.keycloak.databasePasswordFile">databasePasswordFile</link> = "/run/keys/db_password"; <link linkend="opt-services.keycloak.database.passwordFile">database.passwordFile</link> = "/run/keys/db_password";
}; };
</programlisting> </programlisting>
</para> </para>

@ -19,9 +19,12 @@ let
virtualisation.memorySize = 1024; virtualisation.memorySize = 1024;
services.keycloak = { services.keycloak = {
enable = true; enable = true;
inherit frontendUrl databaseType initialAdminPassword; inherit frontendUrl initialAdminPassword;
databaseUsername = "bogus"; database = {
databasePasswordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH"; type = databaseType;
username = "bogus";
passwordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH";
};
}; };
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
xmlstarlet xmlstarlet

Loading…
Cancel
Save