Merge pull request #154193 from abbradar/keycloak-changes

keycloak: 15.1.0 -> 16.1.0 + module improvements
This commit is contained in:
Kim Lindberger 2022-01-16 11:27:29 +01:00 committed by GitHub
commit cdd600c430
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 114 additions and 34 deletions

View File

@ -55,7 +55,11 @@ in
frontendUrl = lib.mkOption { frontendUrl = lib.mkOption {
type = lib.types.str; type = lib.types.str;
apply = x: if lib.hasSuffix "/" x then x else x + "/"; apply = x:
if x == "" || lib.hasSuffix "/" x then
x
else
x + "/";
example = "keycloak.example.com/auth"; example = "keycloak.example.com/auth";
description = '' description = ''
The public URL used as base for all frontend requests. Should The public URL used as base for all frontend requests. Should
@ -229,8 +233,22 @@ in
''; '';
}; };
themes = lib.mkOption {
type = lib.types.attrsOf lib.types.package;
default = {};
description = ''
Additional theme packages for Keycloak. Each theme is linked into
subdirectory with a corresponding attribute name.
Theme packages consist of several subdirectories which provide
different theme types: for example, <literal>account</literal>,
<literal>login</literal> etc. After adding a theme to this option you
can select it by its name in Keycloak administration console.
'';
};
extraConfig = lib.mkOption { extraConfig = lib.mkOption {
type = lib.types.attrs; type = lib.types.attrsOf lib.types.anything;
default = { }; default = { };
example = lib.literalExpression '' example = lib.literalExpression ''
{ {
@ -289,16 +307,45 @@ in
${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt ${pkgs.jre}/bin/keytool -importcert -trustcacerts -alias MySQLCACert -file ${cfg.database.caCert} -keystore $out -storepass notsosecretpassword -noprompt
''; '';
# Both theme and theme type directories need to be actual directories in one hierarchy to pass Keycloak checks.
themesBundle = pkgs.runCommand "keycloak-themes" {} ''
linkTheme() {
theme="$1"
name="$2"
mkdir "$out/$name"
for typeDir in "$theme"/*; do
if [ -d "$typeDir" ]; then
type="$(basename "$typeDir")"
mkdir "$out/$name/$type"
for file in "$typeDir"/*; do
ln -sn "$file" "$out/$name/$type/$(basename "$file")"
done
fi
done
}
mkdir -p "$out"
for theme in ${cfg.package}/themes/*; do
if [ -d "$theme" ]; then
linkTheme "$theme" "$(basename "$theme")"
fi
done
${lib.concatStringsSep "\n" (lib.mapAttrsToList (name: theme: "linkTheme ${theme} ${lib.escapeShellArg name}") cfg.themes)}
'';
keycloakConfig' = builtins.foldl' lib.recursiveUpdate { keycloakConfig' = builtins.foldl' lib.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"."spi=hostname" = { "subsystem=keycloak-server" = {
"provider=default" = { "spi=hostname"."provider=default" = {
enabled = true; enabled = true;
properties = { properties = {
inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl; inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl;
}; };
}; };
"theme=defaults".dir = toString themesBundle;
}; };
"subsystem=datasources"."data-source=KeycloakDS" = { "subsystem=datasources"."data-source=KeycloakDS" = {
max-pool-size = "20"; max-pool-size = "20";
@ -348,11 +395,23 @@ in
}) })
(lib.optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { (lib.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;
"core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = { "subsystem=elytron" = lib.mkOrder 900 {
keystore-path = "/run/keycloak/ssl/certificate_private_key_bundle.p12"; "key-store=httpsKS" = lib.mkOrder 900 {
keystore-password = "notsosecretpassword"; path = "/run/keycloak/ssl/certificate_private_key_bundle.p12";
credential-reference.clear-text = "notsosecretpassword";
type = "JKS";
};
"key-manager=httpsKM" = lib.mkOrder 901 {
key-store = "httpsKS";
credential-reference.clear-text = "notsosecretpassword";
};
"server-ssl-context=httpsSSC" = lib.mkOrder 902 {
key-manager = "httpsKM";
};
};
"subsystem=undertow" = lib.mkOrder 901 {
"server=default-server"."https-listener=https".ssl-context = "httpsSSC";
}; };
"subsystem=undertow"."server=default-server"."https-listener=https".security-realm = "UndertowRealm";
}) })
cfg.extraConfig cfg.extraConfig
]; ];
@ -441,9 +500,9 @@ in
# with `expression` to evaluate. # with `expression` to evaluate.
prefixExpression = string: prefixExpression = string:
let let
match = (builtins.match ''"\$\{.*}"'' string); matchResult = builtins.match ''"\$\{.*}"'' string;
in in
if match != null then if matchResult != null then
"expression " + string "expression " + string
else else
string; string;
@ -508,52 +567,57 @@ in
"" ""
else else
throw "Unsupported type '${type}' for attribute '${attribute}'!"; throw "Unsupported type '${type}' for attribute '${attribute}'!";
in in
lib.concatStringsSep ", " (lib.mapAttrsToList makeArg set); lib.concatStringsSep ", " (lib.mapAttrsToList makeArg set);
/* Recurses into the `attrs` attrset, beginning at the path /* Recurses into the `nodeValue` attrset. Only subattrsets that
resolved from `state.path ++ node`; if `node` is `null`, are JBoss paths, i.e. follows the `key=value` format, are recursed
starts from `state.path`. Only subattrsets that are JBoss
paths, i.e. follows the `key=value` format, are recursed
into - the rest are considered JBoss attributes / maps. into - the rest are considered JBoss attributes / maps.
*/ */
recurse = state: node: recurse = nodePath: nodeValue:
let let
path = state.path ++ (lib.optional (node != null) node); nodeContent =
if builtins.isAttrs nodeValue && nodeValue._type or "" == "order" then
nodeValue.content
else
nodeValue;
isPath = name: isPath = name:
let let
value = lib.getAttrFromPath (path ++ [ name ]) attrs; value = nodeContent.${name};
in in
if (builtins.match ".*([=]).*" name) == [ "=" ] then if (builtins.match ".*([=]).*" name) == [ "=" ] then
if builtins.isAttrs value || value == null then if builtins.isAttrs value || value == null then
true true
else else
throw "Parsing path '${lib.concatStringsSep "." (path ++ [ name ])}' failed: JBoss attributes cannot contain '='!" throw "Parsing path '${lib.concatStringsSep "." (nodePath ++ [ name ])}' failed: JBoss attributes cannot contain '='!"
else else
false; false;
jbossPath = "/" + (lib.concatStringsSep "/" path); jbossPath = "/" + lib.concatStringsSep "/" nodePath;
nodeValue = lib.getAttrFromPath path attrs; children = if !builtins.isAttrs nodeContent then {} else nodeContent;
children = if !builtins.isAttrs nodeValue then {} else nodeValue;
subPaths = builtins.filter isPath (builtins.attrNames children); subPaths = builtins.filter isPath (builtins.attrNames children);
getPriority = name:
let value = children.${name};
in if value._type or "" == "order" then value.priority else 1000;
orderedSubPaths = lib.sort (a: b: getPriority a < getPriority b) subPaths;
jbossAttrs = lib.filterAttrs (name: _: !(isPath name)) children; jbossAttrs = lib.filterAttrs (name: _: !(isPath name)) children;
in text =
state // { if nodeContent != null then
text = state.text + ( ''
if nodeValue != null then ''
if (outcome != success) of ${jbossPath}:read-resource() if (outcome != success) of ${jbossPath}:read-resource()
${jbossPath}:add(${makeArgList jbossAttrs}) ${jbossPath}:add(${makeArgList jbossAttrs})
end-if end-if
'' + (writeAttributes jbossPath jbossAttrs) '' + writeAttributes jbossPath jbossAttrs
else '' else
''
if (outcome == success) of ${jbossPath}:read-resource() if (outcome == success) of ${jbossPath}:read-resource()
${jbossPath}:remove() ${jbossPath}:remove()
end-if end-if
'') + (builtins.foldl' recurse { text = ""; inherit path; } subPaths).text; '';
}; in text + lib.concatMapStringsSep "\n" (name: recurse (nodePath ++ [name]) children.${name}) orderedSubPaths;
in in
(recurse { text = ""; path = []; } null).text; recurse [] attrs;
jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig'); jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig');

View File

@ -85,7 +85,12 @@
The frontend URL is used as base for all frontend requests and The frontend URL is used as base for all frontend requests and
must be configured through <xref linkend="opt-services.keycloak.frontendUrl" />. must be configured through <xref linkend="opt-services.keycloak.frontendUrl" />.
It should normally include a trailing <literal>/auth</literal> It should normally include a trailing <literal>/auth</literal>
(the default web context). (the default web context). If you use a reverse proxy, you need
to set this option to <literal>""</literal>, so that frontend URL
is derived from HTTP headers. <literal>X-Forwarded-*</literal> headers
support also should be enabled, using <link
xlink:href="https://www.keycloak.org/docs/latest/server_installation/index.html#identifying-client-ip-addresses">
respective guidelines</link>.
</para> </para>
<para> <para>
@ -131,6 +136,17 @@
</warning> </warning>
</section> </section>
<section xml:id="module-services-keycloak-themes">
<title>Themes</title>
<para>
You can package custom themes and make them visible to Keycloak via
<xref linkend="opt-services.keycloak.themes" />
option. See the <link xlink:href="https://www.keycloak.org/docs/latest/server_development/#_themes">
Themes section of the Keycloak Server Development Guide</link>
and respective NixOS option description for more information.
</para>
</section>
<section xml:id="module-services-keycloak-extra-config"> <section xml:id="module-services-keycloak-extra-config">
<title>Additional configuration</title> <title>Additional configuration</title>
<para> <para>

View File

@ -18,11 +18,11 @@ let
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
pname = "keycloak"; pname = "keycloak";
version = "15.1.0"; version = "16.1.0";
src = fetchzip { src = fetchzip {
url = "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-${version}.zip"; url = "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-${version}.zip";
sha256 = "0s8nvp1ca30569k1a7glbn2zvvchz35s2r8d08fbs5zjngnz3276"; sha256 = "sha256-QVFu3f+mwafoNUttLEVMdoZHMJjjH/TpZAGV7ZvIvh0=";
}; };
nativeBuildInputs = [ makeWrapper ]; nativeBuildInputs = [ makeWrapper ];