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
|
@ -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 {
|
databaseHost = lib.mkOption {
|
||||||
type = lib.types.str;
|
type = lib.types.str;
|
||||||
default = "localhost";
|
default = "localhost";
|
||||||
description = ''
|
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
|
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.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 {
|
keycloakConfig' = builtins.foldl' lib.recursiveUpdate {
|
||||||
"interface=public".inet-address = cfg.bindAddress;
|
"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" = {
|
"subsystem=datasources"."data-source=KeycloakDS" = {
|
||||||
connection-url = "jdbc:postgresql://${cfg.databaseHost}/keycloak";
|
|
||||||
driver-name = "postgresql";
|
|
||||||
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.databaseUsername;
|
||||||
password = "@db-password@";
|
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) {
|
(lib.optionalAttrs (cfg.certificatePrivateKeyBundle != null) {
|
||||||
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
|
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
|
||||||
"core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = {
|
"core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = {
|
||||||
|
@ -444,7 +531,7 @@ in
|
||||||
|
|
||||||
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
|
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_BASE_DIR="$(pwd -P)";
|
||||||
export JBOSS_MODULEPATH="${cfg.package}/modules";
|
export JBOSS_MODULEPATH="${cfg.package}/modules";
|
||||||
export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log";
|
export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log";
|
||||||
|
@ -475,9 +562,16 @@ in
|
||||||
in
|
in
|
||||||
lib.mkIf cfg.enable {
|
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 ];
|
environment.systemPackages = [ cfg.package ];
|
||||||
|
|
||||||
systemd.services.keycloakDatabaseInit = lib.mkIf databaseActuallyCreateLocally {
|
systemd.services.keycloakPostgreSQLInit = lib.mkIf createLocalPostgreSQL {
|
||||||
after = [ "postgresql.service" ];
|
after = [ "postgresql.service" ];
|
||||||
before = [ "keycloak.service" ];
|
before = [ "keycloak.service" ];
|
||||||
bindsTo = [ "postgresql.service" ];
|
bindsTo = [ "postgresql.service" ];
|
||||||
|
@ -498,13 +592,40 @@ in
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
systemd.services.keycloak = {
|
systemd.services.keycloakMySQLInit = lib.mkIf createLocalMySQL {
|
||||||
after = lib.optionals databaseActuallyCreateLocally [
|
after = [ "mysql.service" ];
|
||||||
"keycloakDatabaseInit.service" "postgresql.service"
|
before = [ "keycloak.service" ];
|
||||||
];
|
bindsTo = [ "mysql.service" ];
|
||||||
bindsTo = lib.optionals databaseActuallyCreateLocally [
|
serviceConfig = {
|
||||||
"keycloakDatabaseInit.service" "postgresql.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
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
|
||||||
|
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" ];
|
wantedBy = [ "multi-user.target" ];
|
||||||
environment = {
|
environment = {
|
||||||
JBOSS_LOG_DIR = "/var/log/keycloak";
|
JBOSS_LOG_DIR = "/var/log/keycloak";
|
||||||
|
@ -562,7 +683,9 @@ in
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
services.postgresql.enable = lib.mkDefault databaseActuallyCreateLocally;
|
services.postgresql.enable = lib.mkDefault createLocalPostgreSQL;
|
||||||
|
services.mysql.enable = lib.mkDefault createLocalMySQL;
|
||||||
|
services.mysql.package = lib.mkIf createLocalMySQL pkgs.mysql;
|
||||||
};
|
};
|
||||||
|
|
||||||
meta.doc = ./keycloak.xml;
|
meta.doc = ./keycloak.xml;
|
||||||
|
|
|
@ -37,15 +37,30 @@
|
||||||
<section xml:id="module-services-keycloak-database">
|
<section xml:id="module-services-keycloak-database">
|
||||||
<title>Database access</title>
|
<title>Database access</title>
|
||||||
<para>
|
<para>
|
||||||
<productname>Keycloak</productname> depends on
|
<productname>Keycloak</productname> can be used with either
|
||||||
<productname>PostgreSQL</productname> and will automatically
|
<productname>PostgreSQL</productname> or
|
||||||
enable it and create a database and role unless configured not
|
<productname>MySQL</productname>. Which one is used can be
|
||||||
to, either by changing <xref linkend="opt-services.keycloak.databaseHost" />
|
configured in <xref
|
||||||
from its default of <literal>localhost</literal> or setting
|
linkend="opt-services.keycloak.databaseType" />. The selected
|
||||||
<xref linkend="opt-services.keycloak.databaseCreateLocally" />
|
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>.
|
to <literal>false</literal>.
|
||||||
</para>
|
</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>
|
<para>
|
||||||
<xref linkend="opt-services.keycloak.databasePasswordFile" />
|
<xref linkend="opt-services.keycloak.databasePasswordFile" />
|
||||||
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
|
||||||
|
|
|
@ -175,7 +175,7 @@ in
|
||||||
kernel-latest = handleTest ./kernel-latest.nix {};
|
kernel-latest = handleTest ./kernel-latest.nix {};
|
||||||
kernel-lts = handleTest ./kernel-lts.nix {};
|
kernel-lts = handleTest ./kernel-lts.nix {};
|
||||||
kernel-testing = handleTest ./kernel-testing.nix {};
|
kernel-testing = handleTest ./kernel-testing.nix {};
|
||||||
keycloak = handleTest ./keycloak.nix {};
|
keycloak = discoverTests (import ./keycloak.nix);
|
||||||
keymap = handleTest ./keymap.nix {};
|
keymap = handleTest ./keymap.nix {};
|
||||||
knot = handleTest ./knot.nix {};
|
knot = handleTest ./knot.nix {};
|
||||||
krb5 = discoverTests (import ./krb5 {});
|
krb5 = discoverTests (import ./krb5 {});
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
# OIDC client and a user, and simulates the user logging in to the
|
# OIDC client and a user, and simulates the user logging in to the
|
||||||
# client using their Keycloak login.
|
# client using their Keycloak login.
|
||||||
|
|
||||||
import ./make-test-python.nix (
|
|
||||||
{ pkgs, ... }:
|
|
||||||
let
|
let
|
||||||
frontendUrl = "http://keycloak/auth";
|
frontendUrl = "http://keycloak/auth";
|
||||||
initialAdminPassword = "h4IhoJFnt2iQIR9";
|
initialAdminPassword = "h4IhoJFnt2iQIR9";
|
||||||
in
|
|
||||||
|
keycloakTest = import ./make-test-python.nix (
|
||||||
|
{ pkgs, databaseType, ... }:
|
||||||
{
|
{
|
||||||
name = "keycloak";
|
name = "keycloak";
|
||||||
meta = with pkgs.stdenv.lib.maintainers; {
|
meta = with pkgs.stdenv.lib.maintainers; {
|
||||||
|
@ -19,7 +19,7 @@ import ./make-test-python.nix (
|
||||||
virtualisation.memorySize = 1024;
|
virtualisation.memorySize = 1024;
|
||||||
services.keycloak = {
|
services.keycloak = {
|
||||||
enable = true;
|
enable = true;
|
||||||
inherit frontendUrl initialAdminPassword;
|
inherit frontendUrl databaseType initialAdminPassword;
|
||||||
databasePasswordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH";
|
databasePasswordFile = pkgs.writeText "dbPassword" "wzf6vOCbPp6cqTH";
|
||||||
};
|
};
|
||||||
environment.systemPackages = with pkgs; [
|
environment.systemPackages = with pkgs; [
|
||||||
|
@ -136,4 +136,9 @@ import ./make-test-python.nix (
|
||||||
)
|
)
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
in
|
||||||
|
{
|
||||||
|
postgres = keycloakTest { databaseType = "postgresql"; };
|
||||||
|
mysql = keycloakTest { databaseType = "mysql"; };
|
||||||
|
}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
{ stdenv, fetchzip, makeWrapper, jre, writeText, nixosTests
|
{ stdenv, lib, fetchzip, makeWrapper, jre, writeText, nixosTests
|
||||||
, postgresql_jdbc ? null
|
, postgresql_jdbc ? null, mysql_jdbc ? null
|
||||||
}:
|
}:
|
||||||
|
|
||||||
let
|
let
|
||||||
mkModuleXml = name: jarFile: writeText "module.xml" ''
|
mkModuleXml = name: jarFile: writeText "module.xml" ''
|
||||||
<?xml version="1.0" ?>
|
<?xml version="1.0" ?>
|
||||||
<module xmlns="urn:jboss:module:1.3" name="org.${name}">
|
<module xmlns="urn:jboss:module:1.3" name="${name}">
|
||||||
<resources>
|
<resources>
|
||||||
<resource-root path="${jarFile}"/>
|
<resource-root path="${jarFile}"/>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -33,17 +33,22 @@ stdenv.mkDerivation rec {
|
||||||
|
|
||||||
rm -rf $out/bin/*.{ps1,bat}
|
rm -rf $out/bin/*.{ps1,bat}
|
||||||
|
|
||||||
module_path=$out/modules/system/layers/keycloak/org
|
module_path=$out/modules/system/layers/keycloak
|
||||||
if ! [[ -d $module_path ]]; then
|
if ! [[ -d $module_path ]]; then
|
||||||
echo "The module path $module_path not found!"
|
echo "The module path $module_path not found!"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
${if postgresql_jdbc != null then ''
|
${lib.optionalString (postgresql_jdbc != null) ''
|
||||||
mkdir -p $module_path/postgresql/main
|
mkdir -p $module_path/org/postgresql/main
|
||||||
ln -s ${postgresql_jdbc}/share/java/postgresql-jdbc.jar $module_path/postgresql/main
|
ln -s ${postgresql_jdbc}/share/java/postgresql-jdbc.jar $module_path/org/postgresql/main/
|
||||||
ln -s ${mkModuleXml "postgresql" "postgresql-jdbc.jar"} $module_path/postgresql/main/module.xml
|
ln -s ${mkModuleXml "org.postgresql" "postgresql-jdbc.jar"} $module_path/org/postgresql/main/module.xml
|
||||||
'' else ""}
|
''}
|
||||||
|
${lib.optionalString (mysql_jdbc != null) ''
|
||||||
|
mkdir -p $module_path/com/mysql/main
|
||||||
|
ln -s ${mysql_jdbc}/share/java/mysql-connector-java.jar $module_path/com/mysql/main/
|
||||||
|
ln -s ${mkModuleXml "com.mysql" "mysql-connector-java.jar"} $module_path/com/mysql/main/module.xml
|
||||||
|
''}
|
||||||
|
|
||||||
wrapProgram $out/bin/standalone.sh --set JAVA_HOME ${jre}
|
wrapProgram $out/bin/standalone.sh --set JAVA_HOME ${jre}
|
||||||
wrapProgram $out/bin/add-user-keycloak.sh --set JAVA_HOME ${jre}
|
wrapProgram $out/bin/add-user-keycloak.sh --set JAVA_HOME ${jre}
|
||||||
|
|
Loading…
Reference in New Issue