mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-07-14 14:10:33 +03:00
nixos/keycloak: Add support for MySQL and external DBs with SSL
- Add support for using MySQL as an option to PostgreSQL. - Enable connecting to external DBs with SSL - Add a database port config option
This commit is contained in:
parent
d1d3c86c70
commit
89e83833af
5 changed files with 243 additions and 95 deletions
|
@ -97,11 +97,59 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
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 PostgreSQL database to connect to.
|
||||
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.
|
||||
'';
|
||||
};
|
||||
|
||||
|
@ -208,6 +256,12 @@ in
|
|||
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 {
|
||||
"interface=public".inet-address = cfg.bindAddress;
|
||||
|
@ -220,19 +274,52 @@ in
|
|||
};
|
||||
};
|
||||
};
|
||||
"subsystem=datasources"."jdbc-driver=postgresql" = {
|
||||
driver-module-name = "org.postgresql";
|
||||
driver-name = "postgresql";
|
||||
driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
|
||||
};
|
||||
"subsystem=datasources"."data-source=KeycloakDS" = {
|
||||
connection-url = "jdbc:postgresql://${cfg.databaseHost}/keycloak";
|
||||
driver-name = "postgresql";
|
||||
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-name = "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-name = "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" = {
|
||||
|
@ -444,7 +531,7 @@ in
|
|||
|
||||
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
|
||||
|
||||
keycloakConfig = pkgs.runCommand "keycloak-config" {} ''
|
||||
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";
|
||||
|
@ -475,9 +562,16 @@ in
|
|||
in
|
||||
lib.mkIf cfg.enable {
|
||||
|
||||
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'';
|
||||
}
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
systemd.services.keycloakDatabaseInit = lib.mkIf databaseActuallyCreateLocally {
|
||||
systemd.services.keycloakPostgreSQLInit = lib.mkIf createLocalPostgreSQL {
|
||||
after = [ "postgresql.service" ];
|
||||
before = [ "keycloak.service" ];
|
||||
bindsTo = [ "postgresql.service" ];
|
||||
|
@ -498,71 +592,100 @@ in
|
|||
'';
|
||||
};
|
||||
|
||||
systemd.services.keycloak = {
|
||||
after = lib.optionals databaseActuallyCreateLocally [
|
||||
"keycloakDatabaseInit.service" "postgresql.service"
|
||||
];
|
||||
bindsTo = lib.optionals databaseActuallyCreateLocally [
|
||||
"keycloakDatabaseInit.service" "postgresql.service"
|
||||
];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
JBOSS_LOG_DIR = "/var/log/keycloak";
|
||||
JBOSS_BASE_DIR = "/run/keycloak";
|
||||
JBOSS_MODULEPATH = "${cfg.package}/modules";
|
||||
};
|
||||
systemd.services.keycloakMySQLInit = lib.mkIf createLocalMySQL {
|
||||
after = [ "mysql.service" ];
|
||||
before = [ "keycloak.service" ];
|
||||
bindsTo = [ "mysql.service" ];
|
||||
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
|
||||
|
||||
db_password="$(</run/keycloak/secrets/db_password)"
|
||||
${pkgs.replace}/bin/replace-literal -fe '@db-password@' "$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 [
|
||||
"+${pkgs.writeShellScript "keycloak-start-pre-full-privileges" startPreFullPrivileges}"
|
||||
"${pkgs.writeShellScript "keycloak-start-pre" startPre}"
|
||||
];
|
||||
ExecStart = "${cfg.package}/bin/standalone.sh";
|
||||
User = "keycloak";
|
||||
Group = "keycloak";
|
||||
DynamicUser = true;
|
||||
RuntimeDirectory = map (p: "keycloak/" + p) [
|
||||
"secrets"
|
||||
"configuration"
|
||||
"deployments"
|
||||
"data"
|
||||
"ssl"
|
||||
"log"
|
||||
"tmp"
|
||||
];
|
||||
RuntimeDirectoryMode = 0700;
|
||||
LogsDirectory = "keycloak";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
User = config.services.mysql.user;
|
||||
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
|
||||
'';
|
||||
};
|
||||
|
||||
services.postgresql.enable = lib.mkDefault databaseActuallyCreateLocally;
|
||||
systemd.services.keycloak =
|
||||
let
|
||||
databaseServices =
|
||||
if createLocalPostgreSQL then [
|
||||
"keycloakPostgreSQLInit.service" "postgresql.service"
|
||||
]
|
||||
else if createLocalMySQL then [
|
||||
"keycloakMySQLInit.service" "mysql.service"
|
||||
]
|
||||
else [ ];
|
||||
in {
|
||||
after = databaseServices;
|
||||
bindsTo = databaseServices;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment = {
|
||||
JBOSS_LOG_DIR = "/var/log/keycloak";
|
||||
JBOSS_BASE_DIR = "/run/keycloak";
|
||||
JBOSS_MODULEPATH = "${cfg.package}/modules";
|
||||
};
|
||||
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
|
||||
|
||||
db_password="$(</run/keycloak/secrets/db_password)"
|
||||
${pkgs.replace}/bin/replace-literal -fe '@db-password@' "$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 [
|
||||
"+${pkgs.writeShellScript "keycloak-start-pre-full-privileges" startPreFullPrivileges}"
|
||||
"${pkgs.writeShellScript "keycloak-start-pre" startPre}"
|
||||
];
|
||||
ExecStart = "${cfg.package}/bin/standalone.sh";
|
||||
User = "keycloak";
|
||||
Group = "keycloak";
|
||||
DynamicUser = true;
|
||||
RuntimeDirectory = map (p: "keycloak/" + p) [
|
||||
"secrets"
|
||||
"configuration"
|
||||
"deployments"
|
||||
"data"
|
||||
"ssl"
|
||||
"log"
|
||||
"tmp"
|
||||
];
|
||||
RuntimeDirectoryMode = 0700;
|
||||
LogsDirectory = "keycloak";
|
||||
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
|
||||
};
|
||||
};
|
||||
|
||||
services.postgresql.enable = lib.mkDefault createLocalPostgreSQL;
|
||||
services.mysql.enable = lib.mkDefault createLocalMySQL;
|
||||
services.mysql.package = lib.mkIf createLocalMySQL pkgs.mysql;
|
||||
};
|
||||
|
||||
meta.doc = ./keycloak.xml;
|
||||
|
|
|
@ -37,15 +37,30 @@
|
|||
<section xml:id="module-services-keycloak-database">
|
||||
<title>Database access</title>
|
||||
<para>
|
||||
<productname>Keycloak</productname> depends on
|
||||
<productname>PostgreSQL</productname> and will automatically
|
||||
enable it and create a database and role unless configured not
|
||||
to, either by changing <xref linkend="opt-services.keycloak.databaseHost" />
|
||||
from its default of <literal>localhost</literal> or setting
|
||||
<xref linkend="opt-services.keycloak.databaseCreateLocally" />
|
||||
<productname>Keycloak</productname> can be used with either
|
||||
<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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue