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.
This commit is contained in:
talyz 2021-05-14 12:15:44 +02:00
parent 83e406e97a
commit dbf91bc2f1
No known key found for this signature in database
GPG Key ID: 2DED2151F4671A2B
3 changed files with 121 additions and 116 deletions

View File

@ -98,7 +98,8 @@ in
''; '';
}; };
databaseType = lib.mkOption { database = {
type = lib.mkOption {
type = lib.types.enum [ "mysql" "postgresql" ]; type = lib.types.enum [ "mysql" "postgresql" ];
default = "postgresql"; default = "postgresql";
example = "mysql"; example = "mysql";
@ -107,7 +108,7 @@ in
''; '';
}; };
databaseHost = lib.mkOption { host = lib.mkOption {
type = lib.types.str; type = lib.types.str;
default = "localhost"; default = "localhost";
description = '' description = ''
@ -115,7 +116,7 @@ in
''; '';
}; };
databasePort = port =
let let
dbPorts = { dbPorts = {
postgresql = 5432; postgresql = 5432;
@ -124,22 +125,22 @@ in
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 = ''
@ -154,18 +155,18 @@ in
''; '';
}; };
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 = ''
@ -174,14 +175,14 @@ in
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 = ''
@ -191,6 +192,7 @@ in
copied into the world-readable Nix store. copied into the world-readable Nix store.
''; '';
}; };
};
package = lib.mkOption { package = lib.mkOption {
type = lib.types.package; type = lib.types.package;
@ -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
''; '';

View File

@ -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>

View File

@ -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