Skip to content

Commit

Permalink
move templating code to lib file
Browse files Browse the repository at this point in the history
  • Loading branch information
ibizaman committed Mar 1, 2024
1 parent 6cf83e7 commit fa206d0
Show file tree
Hide file tree
Showing 10 changed files with 439 additions and 58 deletions.
7 changes: 7 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,20 @@
mergeTests (importFiles [
./test/modules/arr.nix
./test/modules/davfs.nix
./test/modules/lib.nix
./test/modules/nginx.nix
./test/modules/postgresql.nix
]);
};

lib = nix-flake-tests.lib.check {
inherit pkgs;
tests = pkgs.callPackage ./test/modules/lib.nix {};
};
}
// (vm_test "authelia" ./test/vm/authelia.nix)
// (vm_test "ldap" ./test/vm/ldap.nix)
// (vm_test "lib" ./test/vm/lib.nix)
// (vm_test "postgresql" ./test/vm/postgresql.nix)
// (vm_test "monitoring" ./test/vm/monitoring.nix)
// (vm_test "nextcloud" ./test/vm/nextcloud.nix)
Expand Down
109 changes: 103 additions & 6 deletions lib/default.nix
Original file line number Diff line number Diff line change
@@ -1,13 +1,110 @@
{ lib }:
{
template = file: newPath: replacements:
{ pkgs, lib }:
rec {
replaceSecrets = { userConfig, resultPath, generator }:
let
templatePath = newPath + ".template";
configWithTemplates = withReplacements userConfig;

nonSecretConfigFile = pkgs.writeText "${resultPath}.template" (generator configWithTemplates);

replacements = getReplacements userConfig;
in
replaceSecretsScript {
file = nonSecretConfigFile;
inherit resultPath replacements;
};

template = file: newPath: replacements: replaceSecretsScript { inherit file replacements; resultPath = newPath; };
replaceSecretsScript = { file, resultPath, replacements }:
let
templatePath = resultPath + ".template";
sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "-e \"s|${from}|${to}|\"") replacements);
in
''
set -euo pipefail
set -x
ln -fs ${file} ${templatePath}
rm ${newPath} || :
sed ${sedPatterns} ${templatePath} > ${newPath}
rm -f ${resultPath}
${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath}
${pkgs.gnused}/bin/sed ${sedPatterns} ${templatePath} > ${resultPath}
'';

secretFileType = lib.types.submodule {
options = {
source = lib.mkOption {
type = lib.types.path;
description = "File containing the value.";
};

transform = lib.mkOption {
type = lib.types.raw;
description = "An optional function to transform the secret.";
default = null;
example = lib.literalExpression ''
v: "prefix-$${v}-suffix"
'';
};
};
};

secretName = name:
"%SECRET${lib.strings.toUpper (lib.strings.concatMapStrings (s: "_" + s) name)}%";

withReplacements = attrs:
let
valueOrReplacement = name: value:
if !(builtins.isAttrs value && value ? "source")
then value
else secretName name;
in
mapAttrsRecursiveCond (v: ! v ? "source") valueOrReplacement attrs;

getReplacements = attrs:
let
addNameField = name: value:
if !(builtins.isAttrs value && value ? "source")
then value
else value // { name = name; };

secretsWithName = mapAttrsRecursiveCond (v: ! v ? "source") addNameField attrs;

allSecrets = collect (v: builtins.isAttrs v && v ? "source") secretsWithName;

t = { transform ? null, ... }: if isNull transform then x: x else transform;

genReplacement = secret:
lib.attrsets.nameValuePair (secretName secret.name) ((t secret) "$(cat ${toString secret.source})");
in
lib.attrsets.listToAttrs (map genReplacement allSecrets);

# Inspired lib.attrsets.mapAttrsRecursiveCond but also recurses on lists.
mapAttrsRecursiveCond =
# A function, given the attribute set the recursion is currently at, determine if to recurse deeper into that attribute set.
cond:
# A function, given a list of attribute names and a value, returns a new value.
f:
# Attribute set or list to recursively map over.
set:
let
recurse = path: val:
if builtins.isAttrs val && cond val
then lib.attrsets.mapAttrs (n: v: recurse (path ++ [n]) v) val
else if builtins.isList val && cond val
then lib.lists.imap0 (i: v: recurse (path ++ [(builtins.toString i)]) v) val
else f path val;
in recurse [] set;

# Like lib.attrsets.collect but also recurses on lists.
collect =
# Given an attribute's value, determine if recursion should stop.
pred:
# The attribute set to recursively collect.
attrs:
if pred attrs then
[ attrs ]
else if builtins.isAttrs attrs then
lib.lists.concatMap (collect pred) (lib.attrsets.attrValues attrs)
else if builtins.isList attrs then
lib.lists.concatMap (collect pred) attrs
else
[];
}
61 changes: 53 additions & 8 deletions modules/blocks/authelia.nix
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,54 @@ in
};

oidcClients = lib.mkOption {
type = lib.types.listOf lib.types.anything;
description = "OIDC clients";
default = [];
type = lib.types.listOf (lib.types.submodule {
freeformType = lib.types.attrsOf lib.types.anything;

options = {
id = lib.mkOption {
type = lib.types.str;
description = "Unique identifier of the OIDC client.";
};

description = lib.mkOption {
type = lib.types.nullOr lib.types.str;
description = "Human readable description of the OIDC client.";
default = null;
};

secret = lib.mkOption {
type = shblib.secretFileType;
description = "File containing the shared secret with the OIDC client.";
};

public = lib.mkOption {
type = lib.types.bool;
description = "If the OIDC client is public or not.";
default = false;
apply = v: if v then "true" else "false";
};

authorization_policy = lib.mkOption {
type = lib.types.enum [ "one_factor" "two_factor" ];
description = "Require one factor (password) or two factor (device) authentication.";
default = "one_factor";
};

redirect_uris = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "List of uris that are allowed to be redirected to.";
};

scopes = lib.mkOption {
type = lib.types.listOf lib.types.str;
description = "Scopes to ask for";
example = [ "openid" "profile" "email" "groups" ];
default = [];
};
};
});
};

smtp = lib.mkOption {
Expand Down Expand Up @@ -291,13 +336,13 @@ in
systemd.services."authelia-${fqdn}".preStart =
let
mkCfg = clients:
let
addTemplate = client: (builtins.removeAttrs client ["secretFile"]) // {secret = "%SECRET_${client.id}%";};
tmplFile = pkgs.writeText "oidc_clients.yaml" (lib.generators.toYAML {} {identity_providers.oidc.clients = map addTemplate clients;});
replace = client: {"%SECRET_${client.id}%" = "$(cat ${toString client.secretFile})";};
replacements = lib.foldl (container: client: container // (replace client) ) {} clients;
in
shblib.template tmplFile "/var/lib/authelia-${fqdn}/oidc_clients.yaml" replacements;
shblib.replaceSecrets {
userConfig = {
identity_providers.oidc.clients = clients;
};
resultPath = "/var/lib/authelia-${fqdn}/oidc_clients.yaml";
generator = lib.generators.toYAML {};
};
in
lib.mkBefore (mkCfg cfg.oidcClients);

Expand Down
78 changes: 53 additions & 25 deletions modules/services/home-assistant.nix
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ let
cfg = config.shb.home-assistant;

contracts = pkgs.callPackage ../contracts {};
shblib = pkgs.callPackage ../../lib {};

fqdn = "${cfg.subdomain}.${cfg.domain}";

Expand All @@ -18,6 +19,15 @@ let
export PATH=${pkgs.gnused}/bin:${pkgs.curl}/bin:${pkgs.jq}/bin
exec ${pkgs.bash}/bin/bash ${ldap_auth_script_repo}/example_configs/lldap-ha-auth.sh $@
'';

# Filter secrets from config. Secrets are those of the form { source = <path>; }
secrets = lib.attrsets.filterAttrs (k: v: builtins.isAttrs v) cfg.config;

nonSecrets = (lib.attrsets.filterAttrs (k: v: !(builtins.isAttrs v)) cfg.config);

configWithSecretsIncludes =
nonSecrets
// (lib.attrsets.mapAttrs (k: v: "!secret ${k}") secrets);
in
{
options.shb.home-assistant = {
Expand All @@ -41,6 +51,41 @@ in
default = null;
};

config = lib.mkOption {
description = "See all available settings at https://www.home-assistant.io/docs/configuration/basic/";
type = lib.types.submodule {
freeformType = lib.types.attrsOf lib.types.str;
options = {
name = lib.mkOption {
type = lib.types.oneOf [ lib.types.str shblib.secretFileType ];
description = "Name of the Home Assistant instance.";
};
country = lib.mkOption {
type = lib.types.oneOf [ lib.types.str shblib.secretFileType ];
description = "Two letter country code where this instance is located.";
};
latitude = lib.mkOption {
type = lib.types.oneOf [ lib.types.str shblib.secretFileType ];
description = "Latitude where this instance is located.";
};
longitude = lib.mkOption {
type = lib.types.oneOf [ lib.types.str shblib.secretFileType ];
description = "Longitude where this instance is located.";
};
time_zone = lib.mkOption {
type = lib.types.oneOf [ lib.types.str shblib.secretFileType ];
description = "Timezone of this instance.";
example = "America/Los_Angeles";
};
unit_system = lib.mkOption {
type = lib.types.oneOf [ lib.types.str (lib.types.enum [ "metric" "us_customary" ]) ];
description = "Timezone of this instance.";
example = "America/Los_Angeles";
};
};
};
};

ldap = lib.mkOption {
description = ''
LDAP Integration App. [Manual](https://docs.nextcloud.com/server/latest/admin_manual/configuration_user/user_auth_ldap.html)
Expand Down Expand Up @@ -91,12 +136,6 @@ in
};
};

sopsFile = lib.mkOption {
type = lib.types.path;
description = "Sops file location";
example = "secrets/homeassistant.yaml";
};

backupCfg = lib.mkOption {
type = lib.types.anything;
description = "Backup configuration for home-assistant";
Expand Down Expand Up @@ -144,14 +183,8 @@ in
trusted_proxies = "127.0.0.1";
};
logger.default = "info";
homeassistant = {
homeassistant = configWithSecretsIncludes // {
external_url = "https://${cfg.subdomain}.${cfg.domain}";
name = "!secret name";
country = "!secret country";
latitude = "!secret latitude_home";
longitude = "!secret longitude_home";
time_zone = "!secret time_zone";
unit_system = "metric";
auth_providers =
(lib.optionals (!cfg.ldap.enable || cfg.ldap.keepDefaultAuth) [
{
Expand Down Expand Up @@ -256,23 +289,18 @@ in
}
}
'';
storage = "${config.services.home-assistant.configDir}/.storage";
file = "${storage}/onboarding";
storage = "${config.services.home-assistant.configDir}";
file = "${storage}/.storage/onboarding";
in
''
if ! -f ${file}; then
mkdir -p ${storage} && cp ${onboarding} ${file}
fi
'');

sops.secrets."home-assistant" = {
inherit (cfg) sopsFile;
mode = "0440";
owner = "hass";
group = "hass";
path = "${config.services.home-assistant.configDir}/secrets.yaml";
restartUnits = [ "home-assistant.service" ];
};
'' + shblib.replaceSecrets {
userConfig = cfg.config;
resultPath = "${config.services.home-assistant.configDir}/secrets.yaml";
generator = lib.generators.toYAML {};
});

systemd.tmpfiles.rules = [
"f ${config.services.home-assistant.configDir}/automations.yaml 0755 hass hass"
Expand Down
26 changes: 20 additions & 6 deletions modules/services/jellyfin.nix
Original file line number Diff line number Diff line change
Expand Up @@ -348,19 +348,33 @@ in
</BrandingOptions>
'';
in
shblib.template ldapConfig "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml" {
"%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})";
shblib.replaceSecretsScript {
file = ldapConfig;
resultPath = "/var/lib/jellyfin/plugins/configurations/LDAP-Auth.xml";
userConfig = {
"%LDAP_PASSWORD%" = "$(cat ${cfg.ldapPasswordFile})";
};
}
+ shblib.template ssoConfig "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml" {
"%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})";
+ shblib.replaceSecretsScript {
file = ssoConfig;
resultPath = "/var/lib/jellyfin/plugins/configurations/SSO-Auth.xml";
userConfig = {
"%SSO_SECRET%" = "$(cat ${cfg.ssoSecretFile})";
};
}
+ shblib.template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";};
+ shblib.replaceSecretsScript {
file = brandingConfig;
resultPath = "/var/lib/jellyfin/config/branding.xml";
userConfig = {
"%a%" = "%a%";
};
};

shb.authelia.oidcClients = [
{
id = cfg.oidcClientID;
description = "Jellyfin";
secretFile = cfg.ssoSecretFile;
secret.source = cfg.ssoSecretFile;
public = false;
authorization_policy = "one_factor";
redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.oidcProvider}" ];
Expand Down
2 changes: 1 addition & 1 deletion modules/services/nextcloud-server.nix
Original file line number Diff line number Diff line change
Expand Up @@ -829,7 +829,7 @@ in
{
id = cfg.apps.sso.clientID;
description = "Nextcloud";
secretFile = cfg.apps.sso.secretFileForAuthelia;
secret.source = cfg.apps.sso.secretFileForAuthelia;
public = "false";
authorization_policy = cfg.apps.sso.authorization_policy;
redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ];
Expand Down
Loading

0 comments on commit fa206d0

Please sign in to comment.