diff options
author | Kim Lindberger <kim.lindberger@gmail.com> | 2022-01-16 11:27:29 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-01-16 11:27:29 +0100 |
commit | cdd600c430530641ceb9806ea1ac4f50c0e4512d (patch) | |
tree | cb83f29b2837b2062a82da0e529a815d38b43385 | |
parent | 2dd54612139d3bc3585b820514d407b8f6da874f (diff) | |
parent | 97a0cf62f098d21a31c4dc03294e4919e88c225f (diff) |
Merge pull request #154193 from abbradar/keycloak-changes
keycloak: 15.1.0 -> 16.1.0 + module improvements
-rw-r--r-- | nixos/modules/services/web-apps/keycloak.nix | 126 | ||||
-rw-r--r-- | nixos/modules/services/web-apps/keycloak.xml | 18 | ||||
-rw-r--r-- | pkgs/servers/keycloak/default.nix | 4 |
3 files changed, 114 insertions, 34 deletions
diff --git a/nixos/modules/services/web-apps/keycloak.nix b/nixos/modules/services/web-apps/keycloak.nix index e08f6dcabd2f5..aff4ed8dd6083 100644 --- a/nixos/modules/services/web-apps/keycloak.nix +++ b/nixos/modules/services/web-apps/keycloak.nix @@ -55,7 +55,11 @@ in frontendUrl = lib.mkOption { 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"; description = '' 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 { - type = lib.types.attrs; + type = lib.types.attrsOf lib.types.anything; default = { }; 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 ''; + # 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 { "interface=public".inet-address = cfg.bindAddress; "socket-binding-group=standard-sockets"."socket-binding=http".port = cfg.httpPort; - "subsystem=keycloak-server"."spi=hostname" = { - "provider=default" = { + "subsystem=keycloak-server" = { + "spi=hostname"."provider=default" = { enabled = true; properties = { inherit (cfg) frontendUrl forceBackendUrlToFrontendUrl; }; }; + "theme=defaults".dir = toString themesBundle; }; "subsystem=datasources"."data-source=KeycloakDS" = { max-pool-size = "20"; @@ -348,11 +395,23 @@ in }) (lib.optionalAttrs (cfg.sslCertificate != null && cfg.sslCertificateKey != null) { "socket-binding-group=standard-sockets"."socket-binding=https".port = cfg.httpsPort; - "core-service=management"."security-realm=UndertowRealm"."server-identity=ssl" = { - keystore-path = "/run/keycloak/ssl/certificate_private_key_bundle.p12"; - keystore-password = "notsosecretpassword"; + "subsystem=elytron" = lib.mkOrder 900 { + "key-store=httpsKS" = lib.mkOrder 900 { + 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 ]; @@ -441,9 +500,9 @@ in # with `expression` to evaluate. prefixExpression = string: let - match = (builtins.match ''"\$\{.*}"'' string); + matchResult = builtins.match ''"\$\{.*}"'' string; in - if match != null then + if matchResult != null then "expression " + string else string; @@ -508,52 +567,57 @@ in "" else throw "Unsupported type '${type}' for attribute '${attribute}'!"; + in lib.concatStringsSep ", " (lib.mapAttrsToList makeArg set); - /* Recurses into the `attrs` attrset, beginning at the path - resolved from `state.path ++ node`; if `node` is `null`, - starts from `state.path`. Only subattrsets that are JBoss - paths, i.e. follows the `key=value` format, are recursed + /* Recurses into the `nodeValue` attrset. Only subattrsets that + are JBoss paths, i.e. follows the `key=value` format, are recursed into - the rest are considered JBoss attributes / maps. */ - recurse = state: node: + recurse = nodePath: nodeValue: 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: let - value = lib.getAttrFromPath (path ++ [ name ]) attrs; + value = nodeContent.${name}; in if (builtins.match ".*([=]).*" name) == [ "=" ] then if builtins.isAttrs value || value == null then true 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 false; - jbossPath = "/" + (lib.concatStringsSep "/" path); - nodeValue = lib.getAttrFromPath path attrs; - children = if !builtins.isAttrs nodeValue then {} else nodeValue; + jbossPath = "/" + lib.concatStringsSep "/" nodePath; + children = if !builtins.isAttrs nodeContent then {} else nodeContent; 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; - in - state // { - text = state.text + ( - if nodeValue != null then '' + text = + if nodeContent != null then + '' if (outcome != success) of ${jbossPath}:read-resource() ${jbossPath}:add(${makeArgList jbossAttrs}) end-if - '' + (writeAttributes jbossPath jbossAttrs) - else '' + '' + writeAttributes jbossPath jbossAttrs + else + '' if (outcome == success) of ${jbossPath}:read-resource() ${jbossPath}:remove() end-if - '') + (builtins.foldl' recurse { text = ""; inherit path; } subPaths).text; - }; + ''; + in text + lib.concatMapStringsSep "\n" (name: recurse (nodePath ++ [name]) children.${name}) orderedSubPaths; in - (recurse { text = ""; path = []; } null).text; - + recurse [] attrs; jbossCliScript = pkgs.writeText "jboss-cli-script" (mkJbossScript keycloakConfig'); diff --git a/nixos/modules/services/web-apps/keycloak.xml b/nixos/modules/services/web-apps/keycloak.xml index 7ba656c20f166..cb706932f48f5 100644 --- a/nixos/modules/services/web-apps/keycloak.xml +++ b/nixos/modules/services/web-apps/keycloak.xml @@ -85,7 +85,12 @@ The frontend URL is used as base for all frontend requests and must be configured through <xref linkend="opt-services.keycloak.frontendUrl" />. 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> @@ -131,6 +136,17 @@ </warning> </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"> <title>Additional configuration</title> <para> diff --git a/pkgs/servers/keycloak/default.nix b/pkgs/servers/keycloak/default.nix index ef168272fe76d..9d8a2b31bf127 100644 --- a/pkgs/servers/keycloak/default.nix +++ b/pkgs/servers/keycloak/default.nix @@ -18,11 +18,11 @@ let in stdenv.mkDerivation rec { pname = "keycloak"; - version = "15.1.0"; + version = "16.1.0"; src = fetchzip { url = "https://github.com/keycloak/keycloak/releases/download/${version}/keycloak-${version}.zip"; - sha256 = "0s8nvp1ca30569k1a7glbn2zvvchz35s2r8d08fbs5zjngnz3276"; + sha256 = "sha256-QVFu3f+mwafoNUttLEVMdoZHMJjjH/TpZAGV7ZvIvh0="; }; nativeBuildInputs = [ makeWrapper ]; |