From 4fb8253e575f9a8d2f2504d971ccea22f860d4d4 Mon Sep 17 00:00:00 2001 From: Daniel Barlow Date: Mon, 12 Aug 2024 22:57:21 +0100 Subject: [PATCH] first pass at outboard secrets - a module to fetch them with http(s) - a service using templating to consume them - update an example to use it needs service restarts needs other services to use the template mechanism needs tidying up --- examples/router-with-l2tp.nix | 28 +++++++++++++++++++++++++-- modules/hostapd/service.nix | 30 +++++++++++++++++++++-------- modules/secrets/default.nix | 36 +++++++++++++++++++++++++++++++++++ modules/secrets/outboard.nix | 19 ++++++++++++++++++ 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 modules/secrets/default.nix create mode 100644 modules/secrets/outboard.nix diff --git a/examples/router-with-l2tp.nix b/examples/router-with-l2tp.nix index 92aa496..263b380 100644 --- a/examples/router-with-l2tp.nix +++ b/examples/router-with-l2tp.nix @@ -54,10 +54,23 @@ in rec { ../modules/ppp ../modules/round-robin ../modules/health-check + ../modules/secrets ../modules/profiles/gateway.nix ]; hostname = "thing"; + services.wan-address-for-secrets = svc.network.address.build { + interface = config.hardware.networkInterfaces.wan; + family = "inet"; address ="10.0.0.10"; prefixLength = 24; + }; + + services.secrets = svc.secrets.outboard.build { + name = "secret-service"; + url = "http://10.0.0.1/liminix/examples/secrets.json"; + interval = 5; + dependencies = [ services.wan-address-for-secrets ]; + }; + services.wwan = svc.wwan.huawei-e3372.build { apn = "data.uk"; username = "user"; @@ -139,7 +152,13 @@ in rec { hw_mode = "g"; channel = "6"; ieee80211n = 1; - } // wirelessConfig; + } // wirelessConfig //{ + wpa_passphrase = { + service = config.services.secrets; + path = "wpa_passphrase"; + }; + }; + "${rsecrets.ssid}5" = rec { interface = config.hardware.networkInterfaces.wlan5; hw_mode = "a"; @@ -149,7 +168,12 @@ in rec { vht_oper_centr_freq_seg0_idx = channel + 6; ieee80211n = 1; ieee80211ac = 1; - } // wirelessConfig; + } // wirelessConfig // { + wpa_passphrase = { + service = config.services.secrets; + path = "wpa_passphrase"; + }; + }; }; }; diff --git a/modules/hostapd/service.nix b/modules/hostapd/service.nix index 20f7558..1fea256 100644 --- a/modules/hostapd/service.nix +++ b/modules/hostapd/service.nix @@ -1,6 +1,7 @@ { liminix , hostapd +, output-template , writeText , lib }: @@ -23,15 +24,28 @@ let ctrl_interface = "/run/hostapd"; ctrl_interface_group = 0; }; - - conf = writeText "hostapd.conf" - (concatStringsSep - "\n" - (mapAttrsToList - (name: value: "${name}=${toString value}") - (defaults // params))); + attrs = defaults // params ; + literal_or_output = o: + let typ = builtins.typeOf o; + in if typ == "string" + then builtins.toJSON o + else if typ == "int" + then builtins.toJSON o + else "output(${builtins.toJSON o.service}, ${builtins.toJSON o.path})"; + format_value = n : v: + "${n}={{ ${literal_or_output v} }}"; + conf = + (writeText "hostapd.conf.in" + ((concatStringsSep + "\n" + (mapAttrsToList + format_value + attrs)) + "\n")); in longrun { inherit name; dependencies = [ interface ]; - run = "${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S ${conf}"; + run = '' + ${output-template}/bin/output-template '{{' '}}' < ${conf} > /run/${name}.conf + exec ${hostapd}/bin/hostapd -i $(output ${interface} ifname) -P /run/${name}.pid -S /run/${name}.conf + ''; } diff --git a/modules/secrets/default.nix b/modules/secrets/default.nix new file mode 100644 index 0000000..fd830be --- /dev/null +++ b/modules/secrets/default.nix @@ -0,0 +1,36 @@ +## Secrets + +## various ways to manage secrets without writing them to the +## nix store + +{ lib, pkgs, config, ...}: +let + inherit (lib) mkOption types; + inherit (pkgs) liminix; + inherit (pkgs.liminix.services) longrun; +in { + options.system.service.secrets = { + outboard = mkOption { + description = "fetch secrets from external vault with https"; + type = liminix.lib.types.serviceDefn; + }; + + }; + config.system.service.secrets = { + outboard = config.system.callService ./outboard.nix { + url = mkOption { + description = "source url"; + type = types.strMatching "https?://.*"; + }; + name = mkOption { + description = "service name"; + type = types.str; + }; + interval = mkOption { + type = types.int; + default = 30; + description = "how often to check the source, in minutes"; + }; + }; + }; +} diff --git a/modules/secrets/outboard.nix b/modules/secrets/outboard.nix new file mode 100644 index 0000000..d5a8e1e --- /dev/null +++ b/modules/secrets/outboard.nix @@ -0,0 +1,19 @@ +{ + liminix, lib, http-fstree, serviceFns +}: +{ name, url, interval } : +let + inherit (liminix.services) oneshot longrun; +in longrun { + inherit name; + buildInputs = [ http-fstree ]; + # this script runs once and expects the service superviser + # to restart it + run = '' + . ${serviceFns} + ( in_outputs ${name} + ${http-fstree}/bin/http-fstree ${url} . + sleep ${builtins.toString (interval * 60)} + ) + ''; +}