Merge pull request #155367 from talyz/keycloak-loadcredential

nixos/keycloak: Use LoadCredential to load secrets + module formatting
This commit is contained in:
Nikolay Amiantov 2022-01-19 00:47:58 +03:00 committed by GitHub
commit e5e160e08e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,12 +3,25 @@
let let
cfg = config.services.keycloak; cfg = config.services.keycloak;
opt = options.services.keycloak; opt = options.services.keycloak;
inherit (lib) types mkOption concatStringsSep mapAttrsToList
escapeShellArg recursiveUpdate optionalAttrs boolToString mkOrder
sort filterAttrs concatMapStringsSep concatStrings mkIf
optionalString optionals mkDefault literalExpression hasSuffix
foldl' isAttrs filter attrNames elem literalDocBook
maintainers;
inherit (builtins) match typeOf;
in in
{ {
options.services.keycloak = { options.services.keycloak =
let
enable = lib.mkOption { inherit (types) bool str nullOr attrsOf path enum anything
type = lib.types.bool; package port;
in
{
enable = mkOption {
type = bool;
default = false; default = false;
example = true; example = true;
description = '' description = ''
@ -17,8 +30,8 @@ in
''; '';
}; };
bindAddress = lib.mkOption { bindAddress = mkOption {
type = lib.types.str; type = str;
default = "\${jboss.bind.address:0.0.0.0}"; default = "\${jboss.bind.address:0.0.0.0}";
example = "127.0.0.1"; example = "127.0.0.1";
description = '' description = ''
@ -29,8 +42,8 @@ in
''; '';
}; };
httpPort = lib.mkOption { httpPort = mkOption {
type = lib.types.str; type = str;
default = "\${jboss.http.port:80}"; default = "\${jboss.http.port:80}";
example = "8080"; example = "8080";
description = '' description = ''
@ -41,8 +54,8 @@ in
''; '';
}; };
httpsPort = lib.mkOption { httpsPort = mkOption {
type = lib.types.str; type = str;
default = "\${jboss.https.port:443}"; default = "\${jboss.https.port:443}";
example = "8443"; example = "8443";
description = '' description = ''
@ -53,10 +66,10 @@ in
''; '';
}; };
frontendUrl = lib.mkOption { frontendUrl = mkOption {
type = lib.types.str; type = str;
apply = x: apply = x:
if x == "" || lib.hasSuffix "/" x then if x == "" || hasSuffix "/" x then
x x
else else
x + "/"; x + "/";
@ -71,8 +84,8 @@ in
''; '';
}; };
forceBackendUrlToFrontendUrl = lib.mkOption { forceBackendUrlToFrontendUrl = mkOption {
type = lib.types.bool; type = bool;
default = false; default = false;
example = true; example = true;
description = '' description = ''
@ -90,8 +103,8 @@ in
''; '';
}; };
sslCertificate = lib.mkOption { sslCertificate = mkOption {
type = lib.types.nullOr lib.types.path; type = nullOr path;
default = null; default = null;
example = "/run/keys/ssl_cert"; example = "/run/keys/ssl_cert";
description = '' description = ''
@ -103,8 +116,8 @@ in
''; '';
}; };
sslCertificateKey = lib.mkOption { sslCertificateKey = mkOption {
type = lib.types.nullOr lib.types.path; type = nullOr path;
default = null; default = null;
example = "/run/keys/ssl_key"; example = "/run/keys/ssl_key";
description = '' description = ''
@ -117,8 +130,8 @@ in
}; };
database = { database = {
type = lib.mkOption { type = mkOption {
type = lib.types.enum [ "mysql" "postgresql" ]; type = enum [ "mysql" "postgresql" ];
default = "postgresql"; default = "postgresql";
example = "mysql"; example = "mysql";
description = '' description = ''
@ -126,8 +139,8 @@ in
''; '';
}; };
host = lib.mkOption { host = mkOption {
type = lib.types.str; type = str;
default = "localhost"; default = "localhost";
description = '' description = ''
Hostname of the database to connect to. Hostname of the database to connect to.
@ -141,27 +154,27 @@ in
mysql = 3306; mysql = 3306;
}; };
in in
lib.mkOption { mkOption {
type = lib.types.port; type = port;
default = dbPorts.${cfg.database.type}; default = dbPorts.${cfg.database.type};
defaultText = lib.literalDocBook "default port of selected database"; defaultText = literalDocBook "default port of selected database";
description = '' description = ''
Port of the database to connect to. Port of the database to connect to.
''; '';
}; };
useSSL = lib.mkOption { useSSL = mkOption {
type = lib.types.bool; type = bool;
default = cfg.database.host != "localhost"; default = cfg.database.host != "localhost";
defaultText = lib.literalExpression ''config.${opt.database.host} != "localhost"''; defaultText = literalExpression ''config.${opt.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.
''; '';
}; };
caCert = lib.mkOption { caCert = mkOption {
type = lib.types.nullOr lib.types.path; type = nullOr 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
@ -175,8 +188,8 @@ in
''; '';
}; };
createLocally = lib.mkOption { createLocally = mkOption {
type = lib.types.bool; type = 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
@ -186,8 +199,8 @@ in
''; '';
}; };
username = lib.mkOption { username = mkOption {
type = lib.types.str; type = 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
@ -202,8 +215,8 @@ in
''; '';
}; };
passwordFile = lib.mkOption { passwordFile = mkOption {
type = lib.types.path; type = path;
example = "/run/keys/db_password"; example = "/run/keys/db_password";
description = '' description = ''
File containing the database password. File containing the database password.
@ -214,17 +227,17 @@ in
}; };
}; };
package = lib.mkOption { package = mkOption {
type = lib.types.package; type = package;
default = pkgs.keycloak; default = pkgs.keycloak;
defaultText = lib.literalExpression "pkgs.keycloak"; defaultText = literalExpression "pkgs.keycloak";
description = '' description = ''
Keycloak package to use. Keycloak package to use.
''; '';
}; };
initialAdminPassword = lib.mkOption { initialAdminPassword = mkOption {
type = lib.types.str; type = str;
default = "changeme"; default = "changeme";
description = '' description = ''
Initial password set for the <literal>admin</literal> Initial password set for the <literal>admin</literal>
@ -233,8 +246,8 @@ in
''; '';
}; };
themes = lib.mkOption { themes = mkOption {
type = lib.types.attrsOf lib.types.package; type = attrsOf package;
default = { }; default = { };
description = '' description = ''
Additional theme packages for Keycloak. Each theme is linked into Additional theme packages for Keycloak. Each theme is linked into
@ -247,10 +260,10 @@ in
''; '';
}; };
extraConfig = lib.mkOption { extraConfig = mkOption {
type = lib.types.attrsOf lib.types.anything; type = attrsOf anything;
default = { }; default = { };
example = lib.literalExpression '' example = literalExpression ''
{ {
"subsystem=keycloak-server" = { "subsystem=keycloak-server" = {
"spi=hostname" = { "spi=hostname" = {
@ -332,10 +345,11 @@ in
fi fi
done done
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: theme: "linkTheme ${theme} ${lib.escapeShellArg name}") cfg.themes)} ${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)}
''; '';
keycloakConfig' = builtins.foldl' lib.recursiveUpdate { keycloakConfig' = foldl' recursiveUpdate
{
"interface=public".inet-address = cfg.bindAddress; "interface=public".inet-address = cfg.bindAddress;
"socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort; "socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort;
"subsystem=keycloak-server" = { "subsystem=keycloak-server" = {
@ -353,7 +367,7 @@ in
password = "@db-password@"; password = "@db-password@";
}; };
} [ } [
(lib.optionalAttrs (cfg.database.type == "postgresql") { (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";
@ -361,16 +375,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.database.host}:${builtins.toString cfg.database.port}/keycloak"; connection-url = "jdbc:postgresql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
driver-name = "postgresql"; driver-name = "postgresql";
"connection-properties=ssl".value = lib.boolToString cfg.database.useSSL; "connection-properties=ssl".value = boolToString cfg.database.useSSL;
} // (lib.optionalAttrs (cfg.database.caCert != null) { } // (optionalAttrs (cfg.database.caCert != null) {
"connection-properties=sslrootcert".value = cfg.database.caCert; "connection-properties=sslrootcert".value = cfg.database.caCert;
"connection-properties=sslmode".value = "verify-ca"; "connection-properties=sslmode".value = "verify-ca";
}); });
}; };
}) })
(lib.optionalAttrs (cfg.database.type == "mysql") { (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";
@ -378,38 +392,38 @@ 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.database.host}:${builtins.toString cfg.database.port}/keycloak"; connection-url = "jdbc:mysql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
driver-name = "mysql"; driver-name = "mysql";
"connection-properties=useSSL".value = lib.boolToString cfg.database.useSSL; "connection-properties=useSSL".value = boolToString cfg.database.useSSL;
"connection-properties=requireSSL".value = lib.boolToString cfg.database.useSSL; "connection-properties=requireSSL".value = boolToString cfg.database.useSSL;
"connection-properties=verifyServerCertificate".value = lib.boolToString cfg.database.useSSL; "connection-properties=verifyServerCertificate".value = 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.database.caCert != null) { } // (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";
}); });
}; };
}) })
(lib.optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { (optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort; "socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
"subsystem=elytron" = lib.mkOrder 900 { "subsystem=elytron" = mkOrder 900 {
"key-store=httpsKS" = lib.mkOrder 900 { "key-store=httpsKS" = mkOrder 900 {
path = "/run/keycloak/ssl/certificate_private_key_bundle.p12"; path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
credential-reference.clear-text = "notsosecretpassword"; credential-reference.clear-text = "notsosecretpassword";
type = "JKS"; type = "JKS";
}; };
"key-manager=httpsKM" = lib.mkOrder 901 { "key-manager=httpsKM" = mkOrder 901 {
key-store = "httpsKS"; key-store = "httpsKS";
credential-reference.clear-text = "notsosecretpassword"; credential-reference.clear-text = "notsosecretpassword";
}; };
"server-ssl-context=httpsSSC" = lib.mkOrder 902 { "server-ssl-context=httpsSSC" = mkOrder 902 {
key-manager = "httpsKM"; key-manager = "httpsKM";
}; };
}; };
"subsystem=undertow" = lib.mkOrder 901 { "subsystem=undertow" = mkOrder 901 {
"server=default-server"."https-listener=https".ssl-context = "httpsSSC"; "server=default-server"."https-listener=https".ssl-context = "httpsSSC";
}; };
}) })
@ -500,7 +514,7 @@ in
# with `expression` to evaluate. # with `expression` to evaluate.
prefixExpression = string: prefixExpression = string:
let let
matchResult = builtins.match ''"\$\{.*}"'' string; matchResult = match ''"\$\{.*}"'' string;
in in
if matchResult != null then if matchResult != null then
"expression " + string "expression " + string
@ -509,30 +523,31 @@ in
writeAttribute = attribute: value: writeAttribute = attribute: value:
let let
type = builtins.typeOf value; type = typeOf value;
in in
if type == "set" then if type == "set" then
let let
names = builtins.attrNames value; names = attrNames value;
in in
builtins.foldl' (text: name: text + (writeAttribute "${attribute}.${name}" value.${name})) "" names foldl' (text: name: text + (writeAttribute "${attribute}.${name}" value.${name})) "" names
else if value == null then '' else if value == null then ''
if (outcome == success) of ${path}:read-attribute(name="${attribute}") if (outcome == success) of ${path}:read-attribute(name="${attribute}")
${path}:undefine-attribute(name="${attribute}") ${path}:undefine-attribute(name="${attribute}")
end-if end-if
'' ''
else if builtins.elem type [ "string" "path" "bool" ] then else if elem type [ "string" "path" "bool" ] then
let let
value' = if type == "bool" then lib.boolToString value else ''"${value}"''; value' = if type == "bool" then boolToString value else ''"${value}"'';
in '' in
''
if (result != ${prefixExpression value'}) of ${path}:read-attribute(name="${attribute}") if (result != ${prefixExpression value'}) of ${path}:read-attribute(name="${attribute}")
${path}:write-attribute(name=${attribute}, value=${value'}) ${path}:write-attribute(name=${attribute}, value=${value'})
end-if end-if
'' ''
else throw "Unsupported type '${type}' for path '${path}'!"; else throw "Unsupported type '${type}' for path '${path}'!";
in in
lib.concatStrings concatStrings
(lib.mapAttrsToList (mapAttrsToList
(attribute: value: (writeAttribute attribute value)) (attribute: value: (writeAttribute attribute value))
set); set);
@ -557,19 +572,19 @@ in
let let
makeArg = attribute: value: makeArg = attribute: value:
let let
type = builtins.typeOf value; type = typeOf value;
in in
if type == "set" then if type == "set" then
"${attribute} = { " + (makeArgList value) + " }" "${attribute} = { " + (makeArgList value) + " }"
else if builtins.elem type [ "string" "path" "bool" ] then else if elem type [ "string" "path" "bool" ] then
"${attribute} = ${if type == "bool" then lib.boolToString value else ''"${value}"''}" "${attribute} = ${if type == "bool" then boolToString value else ''"${value}"''}"
else if value == null then else if value == null then
"" ""
else else
throw "Unsupported type '${type}' for attribute '${attribute}'!"; throw "Unsupported type '${type}' for attribute '${attribute}'!";
in in
lib.concatStringsSep ", " (lib.mapAttrsToList makeArg set); concatStringsSep ", " (mapAttrsToList makeArg set);
/* Recurses into the `nodeValue` attrset. Only subattrsets that /* Recurses into the `nodeValue` attrset. Only subattrsets that
@ -579,7 +594,7 @@ in
recurse = nodePath: nodeValue: recurse = nodePath: nodeValue:
let let
nodeContent = nodeContent =
if builtins.isAttrs nodeValue && nodeValue._type or "" == "order" then if isAttrs nodeValue && nodeValue._type or "" == "order" then
nodeValue.content nodeValue.content
else else
nodeValue; nodeValue;
@ -587,21 +602,23 @@ in
let let
value = nodeContent.${name}; value = nodeContent.${name};
in in
if (builtins.match ".*([=]).*" name) == [ "=" ] then if (match ".*([=]).*" name) == [ "=" ] then
if builtins.isAttrs value || value == null then if isAttrs value || value == null then
true true
else else
throw "Parsing path '${lib.concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!" throw "Parsing path '${concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!"
else else
false; false;
jbossPath = "/" + lib.concatStringsSep "/" nodePath; jbossPath = "/" + concatStringsSep "/" nodePath;
children = if !builtins.isAttrs nodeContent then {} else nodeContent; children = if !isAttrs nodeContent then { } else nodeContent;
subPaths = builtins.filter isPath (builtins.attrNames children); subPaths = filter isPath (attrNames children);
getPriority = name: getPriority = name:
let value = children.${name}; let
in if value._type or "" == "order" then value.priority else 1000; value = children.${name};
orderedSubPaths = lib.sort (a: b: getPriority a < getPriority b) subPaths; in
jbossAttrs = lib.filterAttrs (name: _: !(isPath name)) children; if value._type or "" == "order" then value.priority else 1000;
orderedSubPaths = sort (a: b: getPriority a < getPriority b) subPaths;
jbossAttrs = filterAttrs (name: _: !(isPath name)) children;
text = text =
if nodeContent != null then if nodeContent != null then
'' ''
@ -615,15 +632,18 @@ in
${jbossPath}:remove() ${jbossPath}:remove()
end-if end-if
''; '';
in text + lib.concatMapStringsSep "\n" (name: recurse (nodePath ++ [name]) children.${name}) orderedSubPaths; in
text + concatMapStringsSep "\n" (name: recurse (nodePath ++ [ name ]) children.${name}) orderedSubPaths;
in in
recurse [ ] attrs; recurse [ ] attrs;
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig'); jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
keycloakConfig = pkgs.runCommand "keycloak-config" { keycloakConfig = pkgs.runCommand "keycloak-config"
{
nativeBuildInputs = [ cfg.package ]; nativeBuildInputs = [ cfg.package ];
} '' }
''
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";
@ -652,8 +672,8 @@ in
cp configuration/standalone.xml $out cp configuration/standalone.xml $out
''; '';
in in
lib.mkIf cfg.enable { mkIf cfg.enable
{
assertions = [ assertions = [
{ {
assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null); assertion = (cfg.database.useSSL && cfg.database.type == "postgresql") -> (cfg.database.caCert != null);
@ -663,7 +683,7 @@ in
environment.systemPackages = [ cfg.package ]; environment.systemPackages = [ cfg.package ];
systemd.services.keycloakPostgreSQLInit = lib.mkIf createLocalPostgreSQL { systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
after = [ "postgresql.service" ]; after = [ "postgresql.service" ];
before = [ "keycloak.service" ]; before = [ "keycloak.service" ];
bindsTo = [ "postgresql.service" ]; bindsTo = [ "postgresql.service" ];
@ -687,7 +707,7 @@ in
''; '';
}; };
systemd.services.keycloakMySQLInit = lib.mkIf createLocalMySQL { systemd.services.keycloakMySQLInit = mkIf createLocalMySQL {
after = [ "mysql.service" ]; after = [ "mysql.service" ];
before = [ "keycloak.service" ]; before = [ "keycloak.service" ];
bindsTo = [ "mysql.service" ]; bindsTo = [ "mysql.service" ];
@ -714,13 +734,16 @@ in
let let
databaseServices = databaseServices =
if createLocalPostgreSQL then [ if createLocalPostgreSQL then [
"keycloakPostgreSQLInit.service" "postgresql.service" "keycloakPostgreSQLInit.service"
"postgresql.service"
] ]
else if createLocalMySQL then [ else if createLocalMySQL then [
"keycloakMySQLInit.service" "mysql.service" "keycloakMySQLInit.service"
"mysql.service"
] ]
else [ ]; else [ ];
in { in
{
after = databaseServices; after = databaseServices;
bindsTo = databaseServices; bindsTo = databaseServices;
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
@ -735,52 +758,16 @@ in
JBOSS_MODULEPATH = "${cfg.package}/modules"; JBOSS_MODULEPATH = "${cfg.package}/modules";
}; };
serviceConfig = { serviceConfig = {
ExecStartPre = let LoadCredential = [
startPreFullPrivileges = '' "db_password:${cfg.database.passwordFile}"
set -o errexit -o pipefail -o nounset -o errtrace ] ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
shopt -s inherit_errexit "ssl_cert:${cfg.sslCertificate}"
"ssl_key:${cfg.sslCertificateKey}"
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 [
"+${pkgs.writeShellScript "keycloak-start-pre-full-privileges" startPreFullPrivileges}"
"${pkgs.writeShellScript "keycloak-start-pre" startPre}"
]; ];
ExecStart = "${cfg.package}/bin/standalone.sh";
User = "keycloak"; User = "keycloak";
Group = "keycloak"; Group = "keycloak";
DynamicUser = true; DynamicUser = true;
RuntimeDirectory = map (p: "keycloak/" + p) [ RuntimeDirectory = map (p: "keycloak/" + p) [
"secrets"
"configuration" "configuration"
"deployments" "deployments"
"data" "data"
@ -792,13 +779,39 @@ in
LogsDirectory = "keycloak"; LogsDirectory = "keycloak";
AmbientCapabilities = "CAP_NET_BIND_SERVICE"; AmbientCapabilities = "CAP_NET_BIND_SERVICE";
}; };
script = ''
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@' "$CREDENTIALS_DIRECTORY/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}'
'' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
pushd /run/keycloak/ssl/
cat "$CREDENTIALS_DIRECTORY/ssl_cert" <(echo) \
"$CREDENTIALS_DIRECTORY/ssl_key" <(echo) \
/etc/ssl/certs/ca-certificates.crt \
> allcerts.pem
openssl pkcs12 -export -in "$CREDENTIALS_DIRECTORY/ssl_cert" -inkey "$CREDENTIALS_DIRECTORY/ssl_key" -chain \
-name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \
-CAfile allcerts.pem -passout pass:notsosecretpassword
popd
'' + ''
${cfg.package}/bin/standalone.sh
'';
}; };
services.postgresql.enable = lib.mkDefault createLocalPostgreSQL; services.postgresql.enable = mkDefault createLocalPostgreSQL;
services.mysql.enable = lib.mkDefault createLocalMySQL; services.mysql.enable = mkDefault createLocalMySQL;
services.mysql.package = lib.mkIf createLocalMySQL pkgs.mariadb; services.mysql.package = mkIf createLocalMySQL pkgs.mariadb;
}; };
meta.doc = ./keycloak.xml; meta.doc = ./keycloak.xml;
meta.maintainers = [ lib.maintainers.talyz ]; meta.maintainers = [ maintainers.talyz ];
} }