···
cfg = config.services.keycloak;
opt = options.services.keycloak;
7
-
inherit (lib) types mkOption concatStringsSep mapAttrsToList
8
-
escapeShellArg recursiveUpdate optionalAttrs boolToString mkOrder
9
-
sort filterAttrs concatMapStringsSep concatStrings mkIf
10
-
optionalString optionals mkDefault literalExpression hasSuffix
11
-
foldl' isAttrs filter attrNames elem literalDocBook
11
+
mkChangedOptionModule
12
+
mkRenamedOptionModule
13
+
mkRemovedOptionModule
14
-
inherit (builtins) match typeOf;
39
+
prefixUnlessEmpty = prefix: string: optionalString (string != "") "${prefix}${string}";
44
+
(mkRenamedOptionModule
45
+
[ "services" "keycloak" "bindAddress" ]
46
+
[ "services" "keycloak" "settings" "http-host" ])
47
+
(mkRenamedOptionModule
48
+
[ "services" "keycloak" "forceBackendUrlToFrontendUrl"]
49
+
[ "services" "keycloak" "settings" "hostname-strict-backchannel"])
50
+
(mkChangedOptionModule
51
+
[ "services" "keycloak" "httpPort" ]
52
+
[ "services" "keycloak" "settings" "http-port" ]
54
+
builtins.fromJSON config.services.keycloak.httpPort))
55
+
(mkChangedOptionModule
56
+
[ "services" "keycloak" "httpsPort" ]
57
+
[ "services" "keycloak" "settings" "https-port" ]
59
+
builtins.fromJSON config.services.keycloak.httpsPort))
60
+
(mkRemovedOptionModule
61
+
[ "services" "keycloak" "frontendUrl" ]
63
+
Set `services.keycloak.settings.hostname' and `services.keycloak.settings.http-relative-path' instead.
64
+
NOTE: You likely want to set 'http-relative-path' to '/auth' to keep compatibility with your clients.
65
+
See its description for more information.
67
+
(mkRemovedOptionModule
68
+
[ "services" "keycloak" "extraConfig" ]
69
+
"Use `services.keycloak.settings' instead.")
options.services.keycloak =
19
-
inherit (types) bool str nullOr attrsOf path enum anything
86
+
assertStringPath = optionName: value:
87
+
if isPath value then
89
+
services.keycloak.${optionName}:
91
+
is a Nix path, but should be a string, since Nix
92
+
paths are copied into the world-readable Nix store.
···
33
-
bindAddress = mkOption {
35
-
default = "\${jboss.bind.address:0.0.0.0}";
36
-
example = "127.0.0.1";
38
-
On which address Keycloak should accept new connections.
40
-
A special syntax can be used to allow command line Java system
41
-
properties to override the value: ''${property.name:value}
45
-
httpPort = mkOption {
47
-
default = "\${jboss.http.port:80}";
50
-
On which port Keycloak should listen for new HTTP connections.
52
-
A special syntax can be used to allow command line Java system
53
-
properties to override the value: ''${property.name:value}
57
-
httpsPort = mkOption {
59
-
default = "\${jboss.https.port:443}";
62
-
On which port Keycloak should listen for new HTTPS connections.
64
-
A special syntax can be used to allow command line Java system
65
-
properties to override the value: ''${property.name:value}
69
-
frontendUrl = mkOption {
72
-
if x == "" || hasSuffix "/" x then
76
-
example = "keycloak.example.com/auth";
78
-
The public URL used as base for all frontend requests. Should
79
-
normally include a trailing <literal>/auth</literal>.
81
-
See <link xlink:href="https://www.keycloak.org/docs/latest/server_installation/#_hostname">the
82
-
Hostname section of the Keycloak server installation
83
-
manual</link> for more information.
87
-
forceBackendUrlToFrontendUrl = mkOption {
92
-
Whether Keycloak should force all requests to go through the
93
-
frontend URL configured in <xref
94
-
linkend="opt-services.keycloak.frontendUrl" />. By default,
95
-
Keycloak allows backend requests to instead use its local
96
-
hostname or IP address and may also advertise it to clients
97
-
through its OpenID Connect Discovery endpoint.
100
-
xlink:href="https://www.keycloak.org/docs/latest/server_installation/#_hostname">the
101
-
Hostname section of the Keycloak server installation
102
-
manual</link> for more information.
sslCertificate = mkOption {
example = "/run/keys/ssl_cert";
111
+
apply = assertStringPath "sslCertificate";
The path to a PEM formatted certificate to use for TLS/SSL
114
-
This should be a string, not a Nix path, since Nix paths are
115
-
copied into the world-readable Nix store.
···
example = "/run/keys/ssl_key";
122
+
apply = assertStringPath "sslCertificateKey";
The path to a PEM formatted private key to use for TLS/SSL
127
-
This should be a string, not a Nix path, since Nix paths are
128
-
copied into the world-readable Nix store.
type = lib.types.listOf lib.types.path;
136
-
Keycloak plugin jar, ear files or derivations with them
133
+
Keycloak plugin jar, ear files or derivations containing
134
+
them. Packaged plugins are available through
135
+
<literal>pkgs.keycloak.plugins</literal>.
142
-
type = enum [ "mysql" "postgresql" ];
141
+
type = enum [ "mysql" "mariadb" "postgresql" ];
143
+
example = "mariadb";
The type of database Keycloak should connect to.
···
···
212
+
default = "keycloak";
214
+
Database name to use when connecting to an external or
215
+
manually provisioned database; has no effect when a local
216
+
database is automatically provisioned.
218
+
To use this with a local database, set <xref
219
+
linkend="opt-services.keycloak.database.createLocally" /> to
220
+
<literal>false</literal> and create the database and user
···
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
221
-
manually. The database should be called
222
-
<literal>keycloak</literal>.
passwordFile = mkOption {
example = "/run/keys/db_password";
243
+
apply = assertStringPath "passwordFile";
230
-
File containing the database password.
232
-
This should be a string, not a Nix path, since Nix paths are
233
-
copied into the world-readable Nix store.
245
+
The path to a file containing the database password.
···
271
-
extraConfig = mkOption {
272
-
type = attrsOf anything;
283
+
settings = mkOption {
284
+
type = lib.types.submodule {
285
+
freeformType = attrsOf (nullOr (oneOf [ str int bool (attrsOf path) ]));
288
+
http-host = mkOption {
290
+
default = "0.0.0.0";
291
+
example = "127.0.0.1";
293
+
On which address Keycloak should accept new connections.
297
+
http-port = mkOption {
302
+
On which port Keycloak should listen for new HTTP connections.
306
+
https-port = mkOption {
311
+
On which port Keycloak should listen for new HTTPS connections.
315
+
http-relative-path = mkOption {
320
+
The path relative to <literal>/</literal> for serving
325
+
In versions of Keycloak using Wildfly (<17),
326
+
this defaulted to <literal>/auth</literal>. If
327
+
upgrading from the Wildfly version of Keycloak,
328
+
i.e. a NixOS version before 22.05, you'll likely
329
+
want to set this to <literal>/auth</literal> to
330
+
keep compatibility with your clients.
333
+
xlink:href="https://www.keycloak.org/migration/migrating-to-quarkus"
334
+
/> for more information on migrating from Wildfly
341
+
hostname = mkOption {
343
+
example = "keycloak.example.com";
345
+
The hostname part of the public URL used as base for
346
+
all frontend requests.
348
+
See <link xlink:href="https://www.keycloak.org/server/hostname" />
349
+
for more information about hostname configuration.
353
+
hostname-strict-backchannel = mkOption {
358
+
Whether Keycloak should force all requests to go
359
+
through the frontend URL. By default, Keycloak allows
360
+
backend requests to instead use its local hostname or
361
+
IP address and may also advertise it to clients
362
+
through its OpenID Connect Discovery endpoint.
364
+
See <link xlink:href="https://www.keycloak.org/server/hostname" />
365
+
for more information about hostname configuration.
370
+
type = enum [ "edge" "reencrypt" "passthrough" "none" ];
374
+
The proxy address forwarding mode if the server is
375
+
behind a reverse proxy.
382
+
Enables communication through HTTP between the
383
+
proxy and Keycloak.
388
+
<term>reencrypt</term>
391
+
Requires communication through HTTPS between the
392
+
proxy and Keycloak.
397
+
<term>passthrough</term>
400
+
Enables communication through HTTP or HTTPS between
401
+
the proxy and Keycloak.
408
+
xlink:href="https://www.keycloak.org/server/reverseproxy"
409
+
/> for more information.
example = literalExpression ''
276
-
"subsystem=keycloak-server" = {
278
-
"provider=default" = null;
279
-
"provider=fixed" = {
281
-
properties.hostname = "keycloak.example.com";
283
-
default-provider = "fixed";
417
+
hostname = "keycloak.example.com";
418
+
proxy = "reencrypt";
419
+
https-key-store-file = "/path/to/file";
420
+
https-key-store-password = { _secret = "/run/keys/store_password"; };
289
-
Additional Keycloak configuration options to set in
290
-
<literal>standalone.xml</literal>.
425
+
Configuration options corresponding to parameters set in
426
+
<filename>conf/keycloak.conf</filename>.
292
-
Options are expressed as a Nix attribute set which matches the
293
-
structure of the jboss-cli configuration. The configuration is
294
-
effectively overlayed on top of the default configuration
295
-
shipped with Keycloak. To remove existing nodes and undefine
296
-
attributes from the default configuration, set them to
297
-
<literal>null</literal>.
428
+
Most available options are documented at <link
429
+
xlink:href="https://www.keycloak.org/server/all-config" />.
299
-
The example configuration does the equivalent of the following
300
-
script, which removes the hostname provider
301
-
<literal>default</literal>, adds the deprecated hostname
302
-
provider <literal>fixed</literal> and defines it the default:
305
-
/subsystem=keycloak-server/spi=hostname/provider=default:remove()
306
-
/subsystem=keycloak-server/spi=hostname/provider=fixed:add(enabled = true, properties = { hostname = "keycloak.example.com" })
307
-
/subsystem=keycloak-server/spi=hostname:write-attribute(name=default-provider, value="fixed")
310
-
You can discover available options by using the <link
311
-
xlink:href="http://docs.wildfly.org/21/Admin_Guide.html#Command_Line_Interface">jboss-cli.sh</link>
312
-
program and by referring to the <link
313
-
xlink:href="https://www.keycloak.org/docs/latest/server_installation/index.html">Keycloak
314
-
Server Installation and Configuration Guide</link>.
431
+
Options containing secret data should be set to an attribute
432
+
set containing the attribute <literal>_secret</literal> - a
433
+
string pointing to a file containing the value the option
434
+
should be set to. See the example to get a better picture of
435
+
this: in the resulting
436
+
<filename>conf/keycloak.conf</filename> file, the
437
+
<literal>https-key-store-password</literal> key will be set
438
+
to the contents of the
439
+
<filename>/run/keys/store_password</filename> file.
322
-
# We only want to create a database if we're actually going to connect to it.
446
+
# We only want to create a database if we're actually going to
databaseActuallyCreateLocally = cfg.database.createLocally && cfg.database.host == "localhost";
createLocalPostgreSQL = databaseActuallyCreateLocally && cfg.database.type == "postgresql";
325
-
createLocalMySQL = databaseActuallyCreateLocally && cfg.database.type == "mysql";
450
+
createLocalMySQL = databaseActuallyCreateLocally && elem cfg.database.type [ "mysql" "mariadb" ];
mySqlCaKeystore = pkgs.runCommand "mysql-ca-keystore" { } ''
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
331
-
# Both theme and theme type directories need to be actual directories in one hierarchy to pass Keycloak checks.
456
+
# Both theme and theme type directories need to be actual
457
+
# directories in one hierarchy to pass Keycloak checks.
themesBundle = pkgs.runCommand "keycloak-themes" { } ''
···
350
-
for theme in ${cfg.package}/themes/*; do
476
+
for theme in ${keycloakBuild}/themes/*; do
linkTheme "$theme" "$(basename "$theme")"
···
${concatStringsSep "\n" (mapAttrsToList (name: theme: "linkTheme ${theme} ${escapeShellArg name}") cfg.themes)}
359
-
keycloakConfig' = foldl' recursiveUpdate
361
-
"interface=public".inet-address = cfg.bindAddress;
362
-
"socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort;
363
-
"subsystem=keycloak-server" = {
364
-
"spi=hostname"."provider=default" = {
367
-
inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl;
370
-
"theme=defaults".dir = toString themesBundle;
372
-
"subsystem=datasources"."data-source=KeycloakDS" = {
373
-
max-pool-size = "20";
374
-
user-name = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
375
-
password = "@db-password@";
378
-
(optionalAttrs (cfg.database.type == "postgresql") {
379
-
"subsystem=datasources" = {
380
-
"jdbc-driver=postgresql" = {
381
-
driver-module-name = "org.postgresql";
382
-
driver-name = "postgresql";
383
-
driver-xa-datasource-class-name = "org.postgresql.xa.PGXADataSource";
385
-
"data-source=KeycloakDS" = {
386
-
connection-url = "jdbc:postgresql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
387
-
driver-name = "postgresql";
388
-
"connection-properties=ssl".value = boolToString cfg.database.useSSL;
389
-
} // (optionalAttrs (cfg.database.caCert != null) {
390
-
"connection-properties=sslrootcert".value = cfg.database.caCert;
391
-
"connection-properties=sslmode".value = "verify-ca";
395
-
(optionalAttrs (cfg.database.type == "mysql") {
396
-
"subsystem=datasources" = {
397
-
"jdbc-driver=mysql" = {
398
-
driver-module-name = "com.mysql";
399
-
driver-name = "mysql";
400
-
driver-class-name = "com.mysql.jdbc.Driver";
402
-
"data-source=KeycloakDS" = {
403
-
connection-url = "jdbc:mysql://${cfg.database.host}:${toString cfg.database.port}/keycloak";
404
-
driver-name = "mysql";
405
-
"connection-properties=useSSL".value = boolToString cfg.database.useSSL;
406
-
"connection-properties=requireSSL".value = boolToString cfg.database.useSSL;
407
-
"connection-properties=verifyServerCertificate".value = boolToString cfg.database.useSSL;
408
-
"connection-properties=characterEncoding".value = "UTF-8";
409
-
valid-connection-checker-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker";
410
-
validate-on-match = true;
411
-
exception-sorter-class-name = "org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter";
412
-
} // (optionalAttrs (cfg.database.caCert != null) {
413
-
"connection-properties=trustCertificateKeyStoreUrl".value = "file:${mySqlCaKeystore}";
414
-
"connection-properties=trustCertificateKeyStorePassword".value = "notsosecretpassword";
418
-
(optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
419
-
"socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort;
420
-
"subsystem=elytron" = mkOrder 900 {
421
-
"key-store=httpsKS" = mkOrder 900 {
422
-
path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
423
-
credential-reference.clear-text = "notsosecretpassword";
426
-
"key-manager=httpsKM" = mkOrder 901 {
427
-
key-store = "httpsKS";
428
-
credential-reference.clear-text = "notsosecretpassword";
430
-
"server-ssl-context=httpsSSC" = mkOrder 902 {
431
-
key-manager = "httpsKM";
434
-
"subsystem=undertow" = mkOrder 901 {
435
-
"server=default-server"."https-listener=https".ssl-context = "httpsSSC";
485
+
keycloakConfig = lib.generators.toKeyValue {
486
+
mkKeyValue = lib.flip lib.generators.mkKeyValueDefault "=" {
487
+
mkValueString = v: with builtins;
488
+
if isInt v then toString v
489
+
else if isString v then v
490
+
else if true == v then "true"
491
+
else if false == v then "false"
492
+
else if isSecret v then hashString "sha256" v._secret
493
+
else throw "unsupported type ${typeOf v}: ${(lib.generators.toPretty {}) v}";
442
-
/* Produces a JBoss CLI script that creates paths and sets
443
-
attributes matching those described by `attrs`. When the
444
-
script is run, the existing settings are effectively overlayed
445
-
by those from `attrs`. Existing attributes can be unset by
446
-
defining them `null`.
448
-
JBoss paths and attributes / maps are distinguished by their
449
-
name, where paths follow a `key=value` scheme.
453
-
"subsystem=keycloak-server"."spi=hostname" = {
454
-
"provider=fixed" = null;
455
-
"provider=default" = {
458
-
inherit frontendUrl;
459
-
forceBackendUrlToFrontendUrl = false;
465
-
if (outcome != success) of /:read-resource()
468
-
if (outcome != success) of /subsystem=keycloak-server:read-resource()
469
-
/subsystem=keycloak-server:add()
471
-
if (outcome != success) of /subsystem=keycloak-server/spi=hostname:read-resource()
472
-
/subsystem=keycloak-server/spi=hostname:add()
474
-
if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=default:read-resource()
475
-
/subsystem=keycloak-server/spi=hostname/provider=default:add(enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" })
477
-
if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled")
478
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true)
480
-
if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl")
481
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false)
483
-
if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl")
484
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth")
486
-
if (outcome != success) of /subsystem=keycloak-server/spi=hostname/provider=fixed:read-resource()
487
-
/subsystem=keycloak-server/spi=hostname/provider=fixed:remove()
491
-
mkJbossScript = attrs:
493
-
/* From a JBoss path and an attrset, produces a JBoss CLI
494
-
snippet that writes the corresponding attributes starting
495
-
at `path`. Recurses down into subattrsets as necessary,
496
-
producing the variable name from its full path in the
500
-
writeAttributes "/subsystem=keycloak-server/spi=hostname/provider=default" {
503
-
forceBackendUrlToFrontendUrl = false;
504
-
frontendUrl = "https://keycloak.example.com/auth";
508
-
if (result != true) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="enabled")
509
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=enabled, value=true)
511
-
if (result != false) of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.forceBackendUrlToFrontendUrl")
512
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.forceBackendUrlToFrontendUrl, value=false)
514
-
if (result != "https://keycloak.example.com/auth") of /subsystem=keycloak-server/spi=hostname/provider=default:read-attribute(name="properties.frontendUrl")
515
-
/subsystem=keycloak-server/spi=hostname/provider=default:write-attribute(name=properties.frontendUrl, value="https://keycloak.example.com/auth")
519
-
writeAttributes = path: set:
521
-
# JBoss expressions like `${var}` need to be prefixed
522
-
# with `expression` to evaluate.
523
-
prefixExpression = string:
525
-
matchResult = match ''"\$\{.*}"'' string;
527
-
if matchResult != null then
528
-
"expression " + string
532
-
writeAttribute = attribute: value:
534
-
type = typeOf value;
536
-
if type == "set" then
538
-
names = attrNames value;
540
-
foldl' (text: name: text + (writeAttribute "${attribute}.${name}" value.${name})) "" names
541
-
else if value == null then ''
542
-
if (outcome == success) of ${path}:read-attribute(name="${attribute}")
543
-
${path}:undefine-attribute(name="${attribute}")
546
-
else if elem type [ "string" "path" "bool" ] then
548
-
value' = if type == "bool" then boolToString value else ''"${value}"'';
551
-
if (result != ${prefixExpression value'}) of ${path}:read-attribute(name="${attribute}")
552
-
${path}:write-attribute(name=${attribute}, value=${value'})
555
-
else throw "Unsupported type '${type}' for path '${path}'!";
559
-
(attribute: value: (writeAttribute attribute value))
563
-
/* Produces an argument list for the JBoss `add()` function,
564
-
which adds a JBoss path and takes as its arguments the
565
-
required subpaths and attributes.
571
-
forceBackendUrlToFrontendUrl = false;
572
-
frontendUrl = "https://keycloak.example.com/auth";
576
-
enabled = true, properties = { forceBackendUrlToFrontendUrl = false, frontendUrl = "https://keycloak.example.com/auth" }
581
-
makeArg = attribute: value:
583
-
type = typeOf value;
585
-
if type == "set" then
586
-
"${attribute} = { " + (makeArgList value) + " }"
587
-
else if elem type [ "string" "path" "bool" ] then
588
-
"${attribute} = ${if type == "bool" then boolToString value else ''"${value}"''}"
589
-
else if value == null then
592
-
throw "Unsupported type '${type}' for attribute '${attribute}'!";
595
-
concatStringsSep ", " (mapAttrsToList makeArg set);
598
-
/* Recurses into the `nodeValue` attrset. Only subattrsets that
599
-
are JBoss paths, i.e. follows the `key=value` format, are recursed
600
-
into - the rest are considered JBoss attributes / maps.
602
-
recurse = nodePath: nodeValue:
605
-
if isAttrs nodeValue && nodeValue._type or "" == "order" then
611
-
value = nodeContent.${name};
613
-
if (match ".*([=]).*" name) == [ "=" ] then
614
-
if isAttrs value || value == null then
617
-
throw "Parsing path '${concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!"
620
-
jbossPath = "/" + concatStringsSep "/" nodePath;
621
-
children = if !isAttrs nodeContent then { } else nodeContent;
622
-
subPaths = filter isPath (attrNames children);
623
-
getPriority = name:
625
-
value = children.${name};
627
-
if value._type or "" == "order" then value.priority else 1000;
628
-
orderedSubPaths = sort (a: b: getPriority a < getPriority b) subPaths;
629
-
jbossAttrs = filterAttrs (name: _: !(isPath name)) children;
631
-
if nodeContent != null then
633
-
if (outcome != success) of ${jbossPath}:read-resource()
634
-
${jbossPath}:add(${makeArgList jbossAttrs})
636
-
'' + writeAttributes jbossPath jbossAttrs
639
-
if (outcome == success) of ${jbossPath}:read-resource()
640
-
${jbossPath}:remove()
644
-
text + concatMapStringsSep "\n" (name: recurse (nodePath ++ [ name ]) children.${name}) orderedSubPaths;
648
-
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');
650
-
keycloakConfig = pkgs.runCommand "keycloak-config"
652
-
nativeBuildInputs = [ cfg.package ];
655
-
export JBOSS_BASE_DIR="$(pwd -P)";
656
-
export JBOSS_MODULEPATH="${cfg.package}/modules";
657
-
export JBOSS_LOG_DIR="$JBOSS_BASE_DIR/log";
659
-
cp -r ${cfg.package}/standalone/configuration .
660
-
chmod -R u+rwX ./configuration
662
-
mkdir -p {deployments,ssl}
668
-
while ! jboss-cli.sh --connect ':read-attribute(name=server-state)'; do
669
-
if [[ "$attempt" == "$max_attempts" ]]; then
670
-
echo "ERROR: Could not connect to Keycloak after $attempt attempts! Failing.." >&2
673
-
echo "Keycloak not fully started yet, retrying.. ($attempt/$max_attempts)"
678
-
jboss-cli.sh --connect --file=${jbossCliScript} --echo-command
680
-
cp configuration/standalone.xml $out
497
+
isSecret = v: isAttrs v && v ? _secret && isString v._secret;
498
+
filteredConfig = lib.converge (lib.filterAttrsRecursive (_: v: ! elem v [{ } null])) cfg.settings;
499
+
confFile = pkgs.writeText "keycloak.conf" (keycloakConfig filteredConfig);
500
+
keycloakBuild = cfg.package.override {
502
+
plugins = cfg.package.enabledPlugins ++ cfg.plugins;
···
692
-
environment.systemPackages = [ cfg.package ];
514
+
environment.systemPackages = [ keycloakBuild ];
516
+
services.keycloak.settings =
518
+
postgresParams = concatStringsSep "&" (
519
+
optionals cfg.database.useSSL [
521
+
] ++ optionals (cfg.database.caCert != null) [
522
+
"sslrootcert=${cfg.database.caCert}"
523
+
"sslmode=verify-ca"
526
+
mariadbParams = concatStringsSep "&" ([
527
+
"characterEncoding=UTF-8"
528
+
] ++ optionals cfg.database.useSSL [
531
+
"verifyServerCertificate=true"
532
+
] ++ optionals (cfg.database.caCert != null) [
533
+
"trustCertificateKeyStoreUrl=file:${mySqlCaKeystore}"
534
+
"trustCertificateKeyStorePassword=notsosecretpassword"
536
+
dbProps = if cfg.database.type == "postgresql" then postgresParams else mariadbParams;
540
+
db = if cfg.database.type == "postgresql" then "postgres" else cfg.database.type;
541
+
db-username = if databaseActuallyCreateLocally then "keycloak" else cfg.database.username;
542
+
db-password._secret = cfg.database.passwordFile;
543
+
db-url-host = "${cfg.database.host}:${toString cfg.database.port}";
544
+
db-url-database = if databaseActuallyCreateLocally then "keycloak" else cfg.database.name;
545
+
db-url-properties = prefixUnlessEmpty "?" dbProps;
548
+
(mkIf (cfg.sslCertificate != null && cfg.sslCertificateKey != null) {
549
+
https-certificate-file = "/run/keycloak/ssl/ssl_cert";
550
+
https-certificate-key-file = "/run/keycloak/ssl/ssl_key";
systemd.services.keycloakPostgreSQLInit = mkIf createLocalPostgreSQL {
after = [ "postgresql.service" ];
···
615
+
secretPaths = catAttrs "_secret" (collect isSecret cfg.settings);
616
+
mkSecretReplacement = file: ''
617
+
replace-secret ${hashString "sha256" file} $CREDENTIALS_DIRECTORY/${baseNameOf file} /run/keycloak/conf/keycloak.conf
619
+
secretReplacements = lib.concatMapStrings mkSecretReplacement secretPaths;
after = databaseServices;
bindsTo = databaseServices;
wantedBy = [ "multi-user.target" ];
766
-
JBOSS_LOG_DIR = "/var/log/keycloak";
767
-
JBOSS_BASE_DIR = "/run/keycloak";
768
-
JBOSS_MODULEPATH = "${cfg.package}/modules";
631
+
KC_HOME_DIR = "/run/keycloak";
632
+
KC_CONF_DIR = "/run/keycloak/conf";
772
-
"db_password:${cfg.database.passwordFile}"
773
-
] ++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
774
-
"ssl_cert:${cfg.sslCertificate}"
775
-
"ssl_key:${cfg.sslCertificateKey}"
636
+
map (p: "${baseNameOf p}:${p}") secretPaths
637
+
++ optionals (cfg.sslCertificate != null && cfg.sslCertificateKey != null) [
638
+
"ssl_cert:${cfg.sslCertificate}"
639
+
"ssl_key:${cfg.sslCertificateKey}"
780
-
RuntimeDirectory = map (p: "keycloak/" + p) [
644
+
RuntimeDirectory = "keycloak";
RuntimeDirectoryMode = 0700;
789
-
LogsDirectory = "keycloak";
AmbientCapabilities = "CAP_NET_BIND_SERVICE";
···
799
-
if [ -d "$1" ]; then
800
-
find "$1" -type f \( -iname \*.ear -o -iname \*.jar \) -exec install -m 0500 -o keycloak -g keycloak "{}" "/run/keycloak/deployments/" \;
802
-
install -m 0500 -o keycloak -g keycloak "$1" "/run/keycloak/deployments/"
654
+
ln -s ${themesBundle} /run/keycloak/themes
655
+
ln -s ${keycloakBuild}/providers /run/keycloak/
806
-
install -m 0600 ${cfg.package}/standalone/configuration/*.properties /run/keycloak/configuration
807
-
install -T -m 0600 ${keycloakConfig} /run/keycloak/configuration/standalone.xml
657
+
install -D -m 0600 ${confFile} /run/keycloak/conf/keycloak.conf
809
-
replace-secret '@db-password@' "$CREDENTIALS_DIRECTORY/db_password" /run/keycloak/configuration/standalone.xml
659
+
${secretReplacements}
811
-
export JAVA_OPTS=-Djboss.server.config.user.dir=/run/keycloak/configuration
812
-
add-user-keycloak.sh -u admin -p '${cfg.initialAdminPassword}'
814
-
+ lib.optionalString (cfg.plugins != []) (lib.concatStringsSep "\n" (map (pl: "install_plugin ${lib.escapeShellArg pl}") cfg.plugins)) + "\n"
815
-
+ optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
816
-
pushd /run/keycloak/ssl/
817
-
cat "$CREDENTIALS_DIRECTORY/ssl_cert" <(echo) \
818
-
"$CREDENTIALS_DIRECTORY/ssl_key" <(echo) \
819
-
/etc/ssl/certs/ca-certificates.crt \
821
-
openssl pkcs12 -export -in "$CREDENTIALS_DIRECTORY/ssl_cert" -inkey "$CREDENTIALS_DIRECTORY/ssl_key" -chain \
822
-
-name "${cfg.frontendUrl}" -out certificate_private_key_bundle.p12 \
823
-
-CAfile allcerts.pem -passout pass:notsosecretpassword
661
+
'' + optionalString (cfg.sslCertificate != null && cfg.sslCertificateKey != null) ''
662
+
mkdir -p /run/keycloak/ssl
663
+
cp $CREDENTIALS_DIRECTORY/ssl_{cert,key} /run/keycloak/ssl/
826
-
${cfg.package}/bin/standalone.sh
665
+
export KEYCLOAK_ADMIN=admin
666
+
export KEYCLOAK_ADMIN_PASSWORD=${cfg.initialAdminPassword}
services.postgresql.enable = mkDefault createLocalPostgreSQL;
services.mysql.enable = mkDefault createLocalMySQL;
832
-
services.mysql.package = mkIf createLocalMySQL pkgs.mariadb;
673
+
services.mysql.package =
675
+
dbPkg = if cfg.database.type == "mariadb" then pkgs.mariadb else pkgs.mysql80;
677
+
mkIf createLocalMySQL (mkDefault dbPkg);
meta.doc = ./keycloak.xml;