Merge pull request #121778 from talyz/keycloak-security

nixos/keycloak: Security fixes + misc

Changed files
+198 -145
nixos
modules
services
tests
+159 -123
nixos/modules/services/web-apps/keycloak.nix
···
frontendUrl = lib.mkOption {
type = lib.types.str;
example = "keycloak.example.com/auth";
description = ''
The public URL used as base for all frontend requests. Should
···
'';
};
-
certificatePrivateKeyBundle = lib.mkOption {
type = lib.types.nullOr lib.types.path;
default = null;
example = "/run/keys/ssl_cert";
description = ''
-
The path to a PEM formatted bundle of the private key and
-
certificate to use for TLS connections.
This should be a string, not a Nix path, since Nix paths are
copied into the world-readable Nix store.
'';
};
-
databaseType = lib.mkOption {
-
type = lib.types.enum [ "mysql" "postgresql" ];
-
default = "postgresql";
-
example = "mysql";
description = ''
-
The type of database Keycloak should connect to.
-
'';
-
};
-
databaseHost = lib.mkOption {
-
type = lib.types.str;
-
default = "localhost";
-
description = ''
-
Hostname of the database to connect to.
'';
};
-
databasePort =
-
let
-
dbPorts = {
-
postgresql = 5432;
-
mysql = 3306;
-
};
-
in
-
lib.mkOption {
-
type = lib.types.port;
-
default = dbPorts.${cfg.databaseType};
-
description = ''
-
Port of the database to connect to.
-
'';
-
};
-
databaseUseSSL = lib.mkOption {
-
type = lib.types.bool;
-
default = cfg.databaseHost != "localhost";
-
description = ''
-
Whether the database connection should be secured by SSL /
-
TLS.
-
'';
-
};
-
databaseCaCert = lib.mkOption {
-
type = lib.types.nullOr lib.types.path;
-
default = null;
-
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 <literal>null</literal>, the default
-
Java keystore is used, which should suffice if the server
-
certificate is issued by an official CA.
-
'';
-
};
-
databaseCreateLocally = 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.databaseHost is customized.
-
'';
-
};
-
databaseUsername = 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 <xref
-
linkend="opt-services.keycloak.databaseCreateLocally" /> to
-
<literal>false</literal> and create the database and user
-
manually. The database should be called
-
<literal>keycloak</literal>.
-
'';
-
};
-
databasePasswordFile = lib.mkOption {
-
type = lib.types.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 = lib.mkOption {
···
config =
let
# We only want to create a database if we're actually going to connect to it.
-
databaseActuallyCreateLocally = cfg.databaseCreateLocally && cfg.databaseHost == "localhost";
-
createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.databaseType == "postgresql";
-
createLocalMySQL = databaseActuallyCreateLocally && cfg.databaseType == "mysql";
mySqlCaKeystore = pkgs.runCommandNoCC "mysql-ca-keystore" {} ''
-
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.databaseCaCert} -keystore $out -storepass notsosecretpassword -noprompt
'';
keycloakConfig' = builtins.foldl' lib.recursiveUpdate {
···
};
"subsystem=datasources"."data-source=KeycloakDS" = {
max-pool-size = "20";
-
user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.databaseUsername;
password = "@db-password@";
};
} [
-
(lib.optionalAttrs (cfg.databaseType == "postgresql") {
"subsystem=datasources" = {
"jdbc-driver=postgresql" = {
driver-module-name = "org.postgresql";
···
driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
};
"data-source=KeycloakDS" = {
-
connection-url = "jdbc:postgresql://${cfg.databaseHost}:${builtins.toString cfg.databasePort}/keycloak";
driver-name = "postgresql";
-
"connection-properties=ssl".value = lib.boolToString cfg.databaseUseSSL;
-
} // (lib.optionalAttrs (cfg.databaseCaCert != null) {
-
"connection-properties=sslrootcert".value = cfg.databaseCaCert;
"connection-properties=sslmode".value = "verify-ca";
});
};
})
-
(lib.optionalAttrs (cfg.databaseType == "mysql") {
"subsystem=datasources" = {
"jdbc-driver=mysql" = {
driver-module-name = "com.mysql";
···
driver-class-name = "com.mysql.jdbc.Driver";
};
"data-source=KeycloakDS" = {
-
connection-url = "jdbc:mysql://${cfg.databaseHost}:${builtins.toString cfg.databasePort}/keycloak";
driver-name = "mysql";
-
"connection-properties=useSSL".value = lib.boolToString cfg.databaseUseSSL;
-
"connection-properties=requireSSL".value = lib.boolToString cfg.databaseUseSSL;
-
"connection-properties=verifyServerCertificate".value = lib.boolToString cfg.databaseUseSSL;
"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.databaseCaCert != null) {
"connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
"connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
});
};
})
-
(lib.optionalAttrs (cfg.certificatePrivateKeyBundle != null) {
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
"core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = {
keystore-path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
···
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
-
keycloakConfig = pkgs.runCommandNoCC "keycloak-config" {} ''
export JBOSS_BASE_DIR="$(pwd -P)";
export JBOSS_MODULEPATH="${cfg.package}/modules";
export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log";
···
mkdir -p {deployments,ssl}
-
"${cfg.package}/bin/standalone.sh"&
attempt=1
max_attempts=30
-
while ! ${cfg.package}/bin/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
···
(( attempt++ ))
done
-
${cfg.package}/bin/jboss-cli.sh --connect --file=${jbossCliScript} --echo-command
cp configuration/standalone.xml $out
'';
···
assertions = [
{
-
assertion = (cfg.databaseUseSSL && cfg.databaseType == "postgresql") -> (cfg.databaseCaCert != null);
-
message = "A CA certificate must be specified (in 'services.keycloak.databaseCaCert') when PostgreSQL is used with SSL";
}
];
···
after = [ "postgresql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "postgresql.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
···
Group = "postgres";
};
script = ''
-
set -eu
-
PSQL=${config.services.postgresql.package}/bin/psql
-
db_password="$(<'${cfg.databasePasswordFile}')"
-
$PSQL -tAc "SELECT 1 FROM pg_roles WHERE rolname='keycloak'" | grep -q 1 || $PSQL -tAc "CREATE ROLE keycloak WITH LOGIN PASSWORD '$db_password' CREATEDB"
-
$PSQL -tAc "SELECT 1 FROM pg_database WHERE datname = 'keycloak'" | grep -q 1 || $PSQL -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
'';
};
···
after = [ "mysql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "mysql.service" ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
···
Group = config.services.mysql.group;
};
script = ''
-
set -eu
-
db_password="$(<'${cfg.databasePasswordFile}')"
( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
echo "CREATE DATABASE keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
-
) | ${config.services.mysql.package}/bin/mysql -N
'';
};
···
bindsTo = databaseServices;
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
replace-secret
];
environment = {
···
serviceConfig = {
ExecStartPre = let
startPreFullPrivileges = ''
-
set -eu
-
install -T -m 0400 -o keycloak -g keycloak '${cfg.databasePasswordFile}' /run/keycloak/secrets/db_password
-
'' + lib.optionalString (cfg.certificatePrivateKeyBundle != null) ''
-
install -T -m 0400 -o keycloak -g keycloak '${cfg.certificatePrivateKeyBundle}' /run/keycloak/secrets/ssl_cert_pk_bundle
'';
startPre = ''
-
set -eu
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
-
${cfg.package}/bin/add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}'
-
'' + lib.optionalString (cfg.certificatePrivateKeyBundle != null) ''
pushd /run/keycloak/ssl/
-
cat /run/keycloak/secrets/ssl_cert_pk_bundle <(echo) /etc/ssl/certs/ca-certificates.crt > allcerts.pem
-
${pkgs.openssl}/bin/openssl pkcs12 -export -in /run/keycloak/secrets/ssl_cert_pk_bundle -chain \
-
-name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \
-
-CAfile allcerts.pem -passout pass:notsosecretpassword
popd
'';
in [
···
};
meta.doc = ./keycloak.xml;
}
···
frontendUrl = lib.mkOption {
type = lib.types.str;
+
apply = x: if lib.hasSuffix "/" x then x else x + "/";
example = "keycloak.example.com/auth";
description = ''
The public URL used as base for all frontend requests. Should
···
'';
};
+
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";
+
description = ''
+
The type of database Keycloak should connect to.
+
'';
+
};
+
host = lib.mkOption {
+
type = lib.types.str;
+
default = "localhost";
+
description = ''
+
Hostname of the database to connect to.
+
'';
+
};
+
port =
+
let
+
dbPorts = {
+
postgresql = 5432;
+
mysql = 3306;
+
};
+
in
+
lib.mkOption {
+
type = lib.types.port;
+
default = dbPorts.${cfg.database.type};
+
description = ''
+
Port of the database to connect to.
+
'';
+
};
+
useSSL = lib.mkOption {
+
type = lib.types.bool;
+
default = cfg.database.host != "localhost";
+
description = ''
+
Whether the database connection should be secured by SSL /
+
TLS.
+
'';
+
};
+
caCert = lib.mkOption {
+
type = lib.types.nullOr lib.types.path;
+
default = null;
+
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 <literal>null</literal>, 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 <xref
+
linkend="opt-services.keycloak.database.createLocally" /> to
+
<literal>false</literal> and create the database and user
+
manually. The database should be called
+
<literal>keycloak</literal>.
+
'';
+
};
+
+
passwordFile = lib.mkOption {
+
type = lib.types.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 = lib.mkOption {
···
config =
let
# 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";
mySqlCaKeystore = pkgs.runCommandNoCC "mysql-ca-keystore" {} ''
+
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
'';
keycloakConfig' = builtins.foldl' lib.recursiveUpdate {
···
};
"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" = {
"jdbc-driver=postgresql" = {
driver-module-name = "org.postgresql";
···
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";
driver-name = "postgresql";
+
"connection-properties=ssl".value = lib.boolToString cfg.database.useSSL;
+
} // (lib.optionalAttrs (cfg.database.caCert != null) {
+
"connection-properties=sslrootcert".value = cfg.database.caCert;
"connection-properties=sslmode".value = "verify-ca";
});
};
})
+
(lib.optionalAttrs (cfg.database.type == "mysql") {
"subsystem=datasources" = {
"jdbc-driver=mysql" = {
driver-module-name = "com.mysql";
···
driver-class-name = "com.mysql.jdbc.Driver";
};
"data-source=KeycloakDS" = {
+
connection-url = "jdbc:mysql://${cfg.database.host}:${builtins.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=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) {
"connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
"connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
});
};
})
+
(lib.optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
"core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = {
keystore-path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
···
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
+
keycloakConfig = pkgs.runCommandNoCC "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";
···
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
···
(( attempt++ ))
done
+
jboss-cli.sh --connect --file=${jbossCliScript} --echo-command
cp configuration/standalone.xml $out
'';
···
assertions = [
{
+
assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
+
message = "A CA certificate must be specified (in 'services.keycloak.database.caCert') when PostgreSQL is used with SSL";
}
];
···
after = [ "postgresql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "postgresql.service" ];
+
path = [ config.services.postgresql.package ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
···
Group = "postgres";
};
script = ''
+
set -o errexit -o pipefail -o nounset -o errtrace
+
shopt -s inherit_errexit
+
create_role="$(mktemp)"
+
trap 'rm -f "$create_role"' ERR EXIT
+
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_database WHERE datname = 'keycloak'" | grep -q 1 || psql -tAc 'CREATE DATABASE "keycloak" OWNER "keycloak"'
'';
};
···
after = [ "mysql.service" ];
before = [ "keycloak.service" ];
bindsTo = [ "mysql.service" ];
+
path = [ config.services.mysql.package ];
serviceConfig = {
Type = "oneshot";
RemainAfterExit = true;
···
Group = config.services.mysql.group;
};
script = ''
+
set -o errexit -o pipefail -o nounset -o errtrace
+
shopt -s inherit_errexit
+
db_password="$(<'${cfg.database.passwordFile}')"
( echo "CREATE USER IF NOT EXISTS 'keycloak'@'localhost' IDENTIFIED BY '$db_password';"
echo "CREATE DATABASE keycloak CHARACTER SET utf8 COLLATE utf8_unicode_ci;"
echo "GRANT ALL PRIVILEGES ON keycloak.* TO 'keycloak'@'localhost';"
+
) | mysql -N
'';
};
···
bindsTo = databaseServices;
wantedBy = [ "multi-user.target" ];
path = with pkgs; [
+
cfg.package
+
openssl
replace-secret
];
environment = {
···
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 [
···
};
meta.doc = ./keycloak.xml;
+
meta.maintainers = [ lib.maintainers.talyz ];
}
+19 -18
nixos/modules/services/web-apps/keycloak.xml
···
<productname>PostgreSQL</productname> or
<productname>MySQL</productname>. Which one is used can be
configured in <xref
-
linkend="opt-services.keycloak.databaseType" />. The selected
database will automatically be enabled and a database and role
created unless <xref
-
linkend="opt-services.keycloak.databaseHost" /> is changed from
its default of <literal>localhost</literal> or <xref
-
linkend="opt-services.keycloak.databaseCreateLocally" /> is set
to <literal>false</literal>.
</para>
<para>
External database access can also be configured by setting
-
<xref linkend="opt-services.keycloak.databaseHost" />, <xref
-
linkend="opt-services.keycloak.databaseUsername" />, <xref
-
linkend="opt-services.keycloak.databaseUseSSL" /> and <xref
-
linkend="opt-services.keycloak.databaseCaCert" /> as
appropriate. Note that you need to manually create a database
called <literal>keycloak</literal> and allow the configured
database user full access to it.
</para>
<para>
-
<xref linkend="opt-services.keycloak.databasePasswordFile" />
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" />
-
and <xref linkend="opt-services.keycloak.databaseCreateLocally" />
are kept at their defaults, the database role
<literal>keycloak</literal> with that password is provisioned
on the local database instance.
···
</para>
<para>
-
For HTTPS support, a TLS certificate and private key is
-
required. They should be <link
xlink:href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail">PEM
-
formatted</link> and concatenated into a single file. The path
-
to this file should be configured in
-
<xref linkend="opt-services.keycloak.certificatePrivateKeyBundle" />.
</para>
<warning>
<para>
-
The path should be provided as a string, not a Nix path,
since Nix paths are copied into the world readable Nix store.
</para>
</warning>
···
<link linkend="opt-services.keycloak.initialAdminPassword">initialAdminPassword</link> = "e6Wcm0RrtegMEHl"; # change on first login
<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.certificatePrivateKeyBundle">certificatePrivateKeyBundle</link> = "/run/keys/ssl_cert";
-
<link linkend="opt-services.keycloak.databasePasswordFile">databasePasswordFile</link> = "/run/keys/db_password";
};
</programlisting>
</para>
···
<productname>PostgreSQL</productname> or
<productname>MySQL</productname>. Which one is used can be
configured in <xref
+
linkend="opt-services.keycloak.database.type" />. The selected
database will automatically be enabled and a database and role
created unless <xref
+
linkend="opt-services.keycloak.database.host" /> is changed from
its default of <literal>localhost</literal> or <xref
+
linkend="opt-services.keycloak.database.createLocally" /> is set
to <literal>false</literal>.
</para>
<para>
External database access can also be configured by setting
+
<xref linkend="opt-services.keycloak.database.host" />, <xref
+
linkend="opt-services.keycloak.database.username" />, <xref
+
linkend="opt-services.keycloak.database.useSSL" /> and <xref
+
linkend="opt-services.keycloak.database.caCert" /> as
appropriate. Note that you need to manually create a database
called <literal>keycloak</literal> and allow the configured
database user full access to it.
</para>
<para>
+
<xref linkend="opt-services.keycloak.database.passwordFile" />
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.database.host" />
+
and <xref linkend="opt-services.keycloak.database.createLocally" />
are kept at their defaults, the database role
<literal>keycloak</literal> with that password is provisioned
on the local database instance.
···
</para>
<para>
+
HTTPS support requires a TLS/SSL certificate and a private key,
+
both <link
xlink:href="https://en.wikipedia.org/wiki/Privacy-Enhanced_Mail">PEM
+
formatted</link>. Their paths should be set through <xref
+
linkend="opt-services.keycloak.sslCertificate" /> and <xref
+
linkend="opt-services.keycloak.sslCertificateKey" />.
</para>
<warning>
<para>
+
The paths should be provided as a strings, not a Nix paths,
since Nix paths are copied into the world readable Nix store.
</para>
</warning>
···
<link linkend="opt-services.keycloak.initialAdminPassword">initialAdminPassword</link> = "e6Wcm0RrtegMEHl"; # change on first login
<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.sslCertificate">sslCertificate</link> = "/run/keys/ssl_cert";
+
<link linkend="opt-services.keycloak.sslCertificateKey">sslCertificateKey</link> = "/run/keys/ssl_key";
+
<link linkend="opt-services.keycloak.database.passwordFile">database.passwordFile</link> = "/run/keys/db_password";
};
</programlisting>
</para>
+20 -4
nixos/tests/keycloak.nix
···
# client using their Keycloak login.
let
-
frontendUrl = "http://keycloak/auth";
initialAdminPassword = "h4IhoJFnt2iQIR9";
keycloakTest = import ./make-test-python.nix (
···
nodes = {
keycloak = { ... }: {
virtualisation.memorySize = 1024;
services.keycloak = {
enable = true;
-
inherit frontendUrl databaseType initialAdminPassword;
-
databaseUsername = "bogus";
-
databasePasswordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH";
};
environment.systemPackages = with pkgs; [
xmlstarlet
libtidy
···
# client using their Keycloak login.
let
+
certs = import ./common/acme/server/snakeoil-certs.nix;
+
frontendUrl = "https://${certs.domain}/auth";
initialAdminPassword = "h4IhoJFnt2iQIR9";
keycloakTest = import ./make-test-python.nix (
···
nodes = {
keycloak = { ... }: {
virtualisation.memorySize = 1024;
+
+
security.pki.certificateFiles = [
+
certs.ca.cert
+
];
+
+
networking.extraHosts = ''
+
127.0.0.1 ${certs.domain}
+
'';
+
services.keycloak = {
enable = true;
+
inherit frontendUrl initialAdminPassword;
+
sslCertificate = certs.${certs.domain}.cert;
+
sslCertificateKey = certs.${certs.domain}.key;
+
database = {
+
type = databaseType;
+
username = "bogus";
+
passwordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH";
+
};
};
+
environment.systemPackages = with pkgs; [
xmlstarlet
libtidy