From 8148ce9f37d186abe7f850b1a9b83ea5e1b817d2 Mon Sep 17 00:00:00 2001 From: oddlama Date: Sat, 18 May 2024 02:38:38 +0200 Subject: [PATCH] feat: add netbird client (gateway server and dev machine) --- README.md | 2 + hosts/kroma/default.nix | 19 +- hosts/ward/default.nix | 1 + hosts/ward/guests/home-gateway.nix | 21 + hosts/ward/secrets/home-gateway/host.pub | 1 + modules/config/users.nix | 1 + modules/default.nix | 7 +- modules/netbird-client.nix | 513 ++++++++++++++++++ modules/optional/graphical/xserver.nix | 28 +- .../sentinel/loki-basic-auth-hashes.age | Bin 2491 -> 2615 bytes .../promtail-loki-basic-auth-password.age | Bin 0 -> 474 bytes .../telegraf-influxdb-token.age | 10 + ...4e52aece39a18f5-loki-basic-auth-hashes.age | Bin 0 -> 2521 bytes ...6992aea9e6fe02b-loki-basic-auth-hashes.age | Bin 2498 -> 0 bytes ...egraf-influxdb-token-ward-home-gateway.age | 7 + ...2467-promtail-loki-basic-auth-password.age | Bin 0 -> 337 bytes ...8ba5309da4a04f-telegraf-influxdb-token.age | 8 + 17 files changed, 600 insertions(+), 18 deletions(-) create mode 100644 hosts/ward/guests/home-gateway.nix create mode 100644 hosts/ward/secrets/home-gateway/host.pub create mode 100644 modules/netbird-client.nix create mode 100644 secrets/generated/ward-home-gateway/promtail-loki-basic-auth-password.age create mode 100644 secrets/generated/ward-home-gateway/telegraf-influxdb-token.age create mode 100644 secrets/rekeyed/sentinel/089eda6d3476434194e52aece39a18f5-loki-basic-auth-hashes.age delete mode 100644 secrets/rekeyed/sentinel/3b515237f2eec169c6992aea9e6fe02b-loki-basic-auth-hashes.age create mode 100644 secrets/rekeyed/sire-influxdb/de3953b78bf9eda16ca6dbbbc5e128af-telegraf-influxdb-token-ward-home-gateway.age create mode 100644 secrets/rekeyed/ward-home-gateway/8a47fe3ea579205dbcfc3d30bc6d2467-promtail-loki-basic-auth-password.age create mode 100644 secrets/rekeyed/ward-home-gateway/a1c23bea567237df248ba5309da4a04f-telegraf-influxdb-token.age diff --git a/README.md b/README.md index d320f45..d5db34b 100644 --- a/README.md +++ b/README.md @@ -60,6 +60,8 @@ that most people would have. The configurations are sorted into three categories 🗓️ CalDAV/CardDAV | – | Radicale | [Link](./hosts/ward/guests/radicale.nix) | Contacts, Calender and Tasks synchronization 📁 NAS | 💎 | Samba | [Link](./hosts/sire/guests/samba.nix) | Network attached storage. 💎 Cross-integration with paperless 🧱 Minecraft | 💎 | PaperMC | [Link](./hosts/sire/guests/minecraft.nix) | Minecraft game server. 💎 Autostart on connect, systemd service with background console, automatic backups +🛡️ VPN | - | Netbird | [Link](./hosts/ward/guests/netbird.nix) | Internal network gateway and wireguard VPN server with dynamic peer configuration and SSO authentication. +📧 Mailserver | 💎 | Stalwart | [Link](./hosts/envoy/stalwart-mail.nix) | Modern mail server setup with custom self-service alias management including Bitwarden integration 📈 Dashboard | – | Grafana | [Link](./hosts/sire/guests/grafana.nix) | Logs and metrics dashboard and alerting 📔 Logs DB | – | Loki | [Link](./hosts/sire/guests/loki.nix) | Central log aggregation service 📔 Logs | – | Promtail | [Link](./modules/promtail.nix) | Log shipping agent diff --git a/hosts/kroma/default.nix b/hosts/kroma/default.nix index 91ed117..4fdb82a 100644 --- a/hosts/kroma/default.nix +++ b/hosts/kroma/default.nix @@ -2,6 +2,7 @@ inputs, lib, minimal, + nodes, ... }: { @@ -74,9 +75,21 @@ # }; #}; - nixpkgs.config.permittedInsecurePackages = lib.trace "please remove insecure nix 2.16.2 very fast ok thx bye" [ - "nix-2.16.2" - ]; + # FIXME: the ui is not directly accessible via environment.systemPackages + # FIXME: to control it as a user (and to allow SSO) we need to be in the netbird-home group + services.netbird.ui.enable = true; + services.netbird.clients.home = { + port = 51820; + name = "netbird-home"; + interface = "wt-home"; + openFirewall = true; + config.ServerSSHAllowed = false; + environment = rec { + NB_MANAGEMENT_URL = "https://${nodes.sentinel.config.networking.providedDomains.netbird}"; + NB_ADMIN_URL = NB_MANAGEMENT_URL; + NB_HOSTNAME = "home-gateway"; + }; + }; topology.self.icon = "devices.desktop"; } diff --git a/hosts/ward/default.nix b/hosts/ward/default.nix index 5eb9363..9501433 100644 --- a/hosts/ward/default.nix +++ b/hosts/ward/default.nix @@ -106,6 +106,7 @@ {} // mkMicrovm "adguardhome" // mkMicrovm "forgejo" + // mkMicrovm "home-gateway" // mkMicrovm "kanidm" // mkMicrovm "netbird" // mkMicrovm "radicale" diff --git a/hosts/ward/guests/home-gateway.nix b/hosts/ward/guests/home-gateway.nix new file mode 100644 index 0000000..33c08ef --- /dev/null +++ b/hosts/ward/guests/home-gateway.nix @@ -0,0 +1,21 @@ +{nodes, ...}: { + environment.persistence."/persist".directories = [ + { + directory = "/var/lib/netbird-home"; + mode = "0700"; + } + ]; + + services.netbird.clients.home = { + port = 51820; + name = "netbird-home"; + interface = "wt-home"; + openFirewall = true; + config.ServerSSHAllowed = false; + environment = rec { + NB_MANAGEMENT_URL = "https://${nodes.sentinel.config.networking.providedDomains.netbird}"; + NB_ADMIN_URL = NB_MANAGEMENT_URL; + NB_HOSTNAME = "home-gateway"; + }; + }; +} diff --git a/hosts/ward/secrets/home-gateway/host.pub b/hosts/ward/secrets/home-gateway/host.pub new file mode 100644 index 0000000..05da559 --- /dev/null +++ b/hosts/ward/secrets/home-gateway/host.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAILlEjX8dXaeZl+ax7YphX9XNr/S9WwKLsgbV7FDjWNzY diff --git a/modules/config/users.nix b/modules/config/users.nix index 3882b0c..3fe82f2 100644 --- a/modules/config/users.nix +++ b/modules/config/users.nix @@ -33,5 +33,6 @@ maddy = uidGid 976; minecraft = uidGid 975; stalwart-mail = uidGid 974; + netbird-home = uidGid 973; }; } diff --git a/modules/default.nix b/modules/default.nix index eb13425..9e62cf3 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,5 +1,9 @@ {inputs, ...}: { - disabledModules = ["services/security/kanidm.nix"]; + disabledModules = [ + "services/security/kanidm.nix" + "services/networking/netbird.nix" + ]; + imports = [ inputs.agenix-rekey.nixosModules.default inputs.agenix.nixosModules.default @@ -34,6 +38,7 @@ ./distributed-config.nix ./kanidm.nix ./meta.nix + ./netbird-client.nix ./oauth2-proxy.nix ./promtail.nix ./provided-domains.nix diff --git a/modules/netbird-client.nix b/modules/netbird-client.nix new file mode 100644 index 0000000..79ddfe4 --- /dev/null +++ b/modules/netbird-client.nix @@ -0,0 +1,513 @@ +{ + config, + lib, + pkgs, + ... +}: let + inherit + (lib) + attrValues + concatLists + concatStringsSep + escapeShellArgs + filterAttrs + getExe + literalExpression + maintainers + makeBinPath + mapAttrs' + mapAttrsToList + mkDefault + mkIf + mkMerge + mkOption + mkOptionDefault + mkPackageOption + mkRemovedOptionModule + nameValuePair + optional + optionalString + toShellVars + versionAtLeast + versionOlder + ; + + inherit + (lib.types) + attrsOf + bool + enum + package + port + str + submodule + ; + + inherit (config.boot) kernelPackages; + inherit (config.boot.kernelPackages) kernel; + + cfg = config.services.netbird; + + toClientList = fn: map fn (attrValues cfg.clients); + toClientAttrs = fn: mapAttrs' (_: fn) cfg.clients; + + hardenedClients = filterAttrs (_: client: client.hardened) cfg.clients; + toHardenedClientList = fn: map fn (attrValues hardenedClients); + toHardenedClientAttrs = fn: mapAttrs' (_: fn) hardenedClients; +in { + meta.maintainers = with maintainers; [ + misuzu + thubrecht + nazarewk + ]; + + imports = [ + (mkRemovedOptionModule ["services" "netbird" "tunnels"] + "The option `services.netbird.tunnels` has been renamed to `services.netbird.clients`") + ]; + + options.services.netbird = { + enable = mkOption { + type = bool; + default = false; + description = '' + Enables backwards compatible Netbird client service. + + This is strictly equivalent to: + + ```nix + services.netbird.clients.wt0 = { + port = 51820; + name = "netbird"; + interface = "wt0"; + hardened = false; + }; + ``` + ''; + }; + package = mkPackageOption pkgs "netbird" {}; + + ui.enable = mkOption { + type = bool; + default = config.services.displayManager.sessionPackages != []; + defaultText = literalExpression ''config.services.displayManager.sessionPackages != [ ]''; + description = '' + Controls presence `netbird-ui` wrappers, defaults to presence of graphical sessions. + ''; + }; + ui.package = mkPackageOption pkgs "netbird-ui" {}; + + clients = mkOption { + type = attrsOf ( + submodule ( + { + name, + config, + ... + }: { + options = { + port = mkOption { + type = port; + example = literalExpression "51820"; + description = '' + Port the Netbird client listens on. + ''; + }; + + name = mkOption { + type = str; + default = "netbird-${name}"; + description = '' + Primary name for use in: + - systemd service name, + - hardened user name and group, + - [systemd `*Directory=`](https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#RuntimeDirectory=) names, + - desktop application identification, + ''; + }; + + interface = mkOption { + type = str; + default = "netbird-${name}"; + description = '' + Name of the network interface managed by this client. + ''; + }; + + environment = mkOption { + type = attrsOf str; + defaultText = literalExpression '' + { + NB_CONFIG = "/var/lib/''${config.name}/config.json"; + NB_DAEMON_ADDR = "unix:///var/run/''${config.name}/sock"; + NB_INTERFACE_NAME = config.interface; + NB_LOG_FILE = mkOptionDefault "console"; + NB_LOG_LEVEL = config.logLevel; + NB_SERVICE = config.name; + NB_WIREGUARD_PORT = toString config.port; + } + ''; + description = '' + Environment for the netbird service, used to pass configuration options. + ''; + }; + + autoStart = mkOption { + type = bool; + default = true; + description = '' + Start the service with the system. + + As of 2024-02-13 it is not possible to start a Netbird client daemon without immediately + connecting to the network, but it is [planned for a near future](https://github.com/netbirdio/netbird/projects/2#card-91718018). + ''; + }; + + openFirewall = mkOption { + type = bool; + default = true; + description = '' + Opens up firewall `port` for communication between Netbird peers directly over LAN or public IP, + without using (internet-hosted) TURN servers as intermediaries. + ''; + }; + + hardened = mkOption { + type = bool; + default = true; + description = '' + Hardened service: + - runs as a dedicated user with minimal set of permissions (see caveats), + - restricts daemon configuration socket access to dedicated user group + (you can grant access to it with `users.users."".extraGroups = [ "netbird-${name}" ]`), + + Even though the local system resources access is restricted: + - `CAP_NET_RAW`, `CAP_NET_ADMIN` and `CAP_BPF` still give unlimited network manipulation possibilites, + - older kernels don't have `CAP_BPF` and use `CAP_SYS_ADMIN` instead, + + Known security features that are not (yet) integrated into the module: + - 2024-02-14: `rosenpass` is an experimental feature configurable solely + through `--enable-rosenpass` flag on the `netbird up` command, + see [the docs](https://docs.netbird.io/how-to/enable-post-quantum-cryptography) + ''; + }; + + logLevel = mkOption { + type = enum [ + # logrus loglevels + "panic" + "fatal" + "error" + "warn" + "warning" + "info" + "debug" + "trace" + ]; + default = "info"; + description = "Log level of the Netbird daemon."; + }; + + wrapper = mkOption { + type = package; + internal = true; + default = let + makeWrapperArgs = concatLists ( + mapAttrsToList + (key: value: ["--set-default" key value]) + config.environment + ); + in + pkgs.stdenv.mkDerivation { + name = "${cfg.package.name}-wrapper-${name}"; + meta.mainProgram = "netbird-${name}"; + nativeBuildInputs = with pkgs; [makeWrapper]; + phases = ["installPhase"]; + installPhase = concatStringsSep "\n" [ + '' + mkdir -p "$out/bin" + makeWrapper ${lib.getExe cfg.package} "$out/bin/netbird-${name}" \ + ${escapeShellArgs makeWrapperArgs} + '' + (optionalString cfg.ui.enable '' + # netbird-ui doesn't support envvars + makeWrapper ${lib.getExe cfg.ui.package}-ui "$out/bin/netbird-ui-${name}" \ + --add-flags '--daemon-addr=${config.environment.NB_DAEMON_ADDR}' + + mkdir -p "$out/share/applications" + substitute ${cfg.ui.package}/share/applications/netbird.desktop \ + "$out/share/applications/netbird-${name}.desktop" \ + --replace-fail 'Name=Netbird' "Name=Netbird @ ${config.name}" \ + --replace-fail '${lib.getExe cfg.ui.package}-ui' "$out/bin/netbird-ui-${name}" + '') + ]; + }; + }; + + # see https://github.com/netbirdio/netbird/blob/88747e3e0191abc64f1e8c7ecc65e5e50a1527fd/client/internal/config.go#L49-L82 + config = mkOption { + inherit (pkgs.formats.json {}) type; + defaultText = literalExpression '' + { + DisableAutoConnect = !config.autoStart; + WgIface = config.interface; + WgPort = config.port; + } + ''; + description = '' + Additional configuration that exists before the first start and + later overrides the existing values in `config.json`. + + It is mostly helpful to manage configuration ignored/not yet implemented + outside of `netbird up` invocation. + + WARNING: this is not an upstream feature, it could break in the future + (by having lower priority) after upstream implements an equivalent. + + It is implemented as a `preStart` script which overrides `config.json` + with content of `/etc/netbird-${name}/config.d/*.json` files. + This option manages specifically `50-nixos.json` file. + + Consult [the source code](https://github.com/netbirdio/netbird/blob/88747e3e0191abc64f1e8c7ecc65e5e50a1527fd/client/internal/config.go#L49-L82) + or inspect existing file for a complete list of available configurations. + ''; + }; + }; + + config.environment = { + NB_CONFIG = "/var/lib/${config.name}/config.json"; + NB_DAEMON_ADDR = "unix:///var/run/${config.name}/sock"; + NB_INTERFACE_NAME = config.interface; + NB_LOG_FILE = mkOptionDefault "console"; + NB_LOG_LEVEL = config.logLevel; + NB_SERVICE = config.name; + NB_WIREGUARD_PORT = toString config.port; + }; + + config.config = { + DisableAutoConnect = !config.autoStart; + WgIface = config.interface; + WgPort = config.port; + }; + } + ) + ); + default = {}; + description = '' + Attribute set of Netbird client daemons, by default each one will: + + 1. be manageable using dedicated tooling: + - `netbird-` script, + - `Netbird - netbird-` graphical interface when appropriate (see `ui.enable`), + 2. run as a `netbird-.service`, + 3. listen for incoming remote connections on the port `51830` (`openFirewall` by default), + 4. manage the `netbird-` wireguard interface, + 5. use the `/var/lib/netbird-/config.json` configuration file, + 6. override `/var/lib/netbird-/config.json` with values from `/etc/netbird-/config.d/*.json`, + 7. (`hardened`) be locally manageable by `netbird-` system group, + + With following caveats: + + - multiple daemons will interfere with each other's DNS resolution of `netbird.cloud`, but + should remain fully operational otherwise. + Setting up custom (non-conflicting) DNS zone is currently possible only when self-hosting. + ''; + example = lib.literalExpression '' + { + services.netbird.clients.wt0.port = 51820; + services.netbird.clients.personal.port = 51821; + services.netbird.clients.work1.port = 51822; + } + ''; + }; + }; + + config = mkMerge [ + (mkIf cfg.enable ( + let + name = "wt0"; + client = cfg.clients."${name}"; + in { + services.netbird.clients."${name}" = { + port = mkDefault 51820; + name = mkDefault "netbird"; + interface = mkDefault "wt0"; + hardened = mkDefault false; + }; + + environment.systemPackages = [ + (lib.hiPrio (pkgs.runCommand "${name}-as-default" {} '' + mkdir -p "$out/bin" + for binary in netbird ${optionalString cfg.ui.enable "netbird-ui"} ; do + ln -s "${client.wrapper}/bin/$binary-${name}" "$out/bin/$binary" + done + '')) + ]; + } + )) + { + boot.extraModulePackages = + optional + (cfg.clients != {} && (versionOlder kernel.version "5.6")) + kernelPackages.wireguard; + + environment.systemPackages = + toClientList (client: client.wrapper) + # omitted due to https://github.com/netbirdio/netbird/issues/1562 + #++ optional (cfg.clients != { }) cfg.package + # omitted due to https://github.com/netbirdio/netbird/issues/1581 + #++ optional (cfg.clients != { } && cfg.ui.enable) cfg.ui.package + ; + + networking.dhcpcd.denyInterfaces = toClientList (client: client.interface); + networking.networkmanager.unmanaged = toClientList (client: "interface-name:${client.interface}"); + + networking.firewall.allowedUDPPorts = concatLists (toClientList (client: optional client.openFirewall client.port)); + + systemd.network.networks = mkIf config.networking.useNetworkd (toClientAttrs ( + client: + nameValuePair "50-netbird-${client.interface}" { + matchConfig = { + Name = client.interface; + }; + linkConfig = { + Unmanaged = true; + ActivationPolicy = "manual"; + }; + } + )); + + environment.etc = toClientAttrs (client: + nameValuePair "${client.name}/config.d/50-nixos.json" { + text = builtins.toJSON client.config; + mode = "0444"; + }); + + systemd.services = toClientAttrs (client: + nameValuePair client.name { + description = "A WireGuard-based mesh network that connects your devices into a single private network"; + + documentation = ["https://netbird.io/docs/"]; + + after = ["network.target"]; + wantedBy = ["multi-user.target"]; + + path = optional (!config.services.resolved.enable) pkgs.openresolv; + + serviceConfig = { + ExecStart = "${getExe client.wrapper} service run"; + Restart = "always"; + + RuntimeDirectory = client.name; + RuntimeDirectoryMode = mkDefault "0755"; + ConfigurationDirectory = client.name; + StateDirectory = client.name; + StateDirectoryMode = "0700"; + + WorkingDirectory = "/var/lib/${client.name}"; + }; + + unitConfig = { + StartLimitInterval = 5; + StartLimitBurst = 10; + }; + + stopIfChanged = false; + }); + } + # Hardening section + (mkIf (hardenedClients != {}) { + users.groups = toHardenedClientAttrs (client: nameValuePair client.name {}); + users.users = toHardenedClientAttrs (client: + nameValuePair client.name { + isSystemUser = true; + home = "/var/lib/${client.name}"; + group = client.name; + }); + + systemd.services = toHardenedClientAttrs (client: + nameValuePair client.name (mkIf client.hardened { + serviceConfig = { + RuntimeDirectoryMode = "0750"; + + User = client.name; + Group = client.name; + + # settings implied by DynamicUser=true, without actully using it, + # see https://www.freedesktop.org/software/systemd/man/latest/systemd.exec.html#DynamicUser= + RemoveIPC = true; + PrivateTmp = true; + ProtectSystem = "strict"; + ProtectHome = "yes"; + + AmbientCapabilities = + [ + # see https://man7.org/linux/man-pages/man7/capabilities.7.html + # see https://docs.netbird.io/how-to/installation#running-net-bird-in-docker + # + # seems to work fine without CAP_SYS_ADMIN and CAP_SYS_RESOURCE + # CAP_NET_BIND_SERVICE could be added to allow binding on low ports, but is not required, + # see https://github.com/netbirdio/netbird/pull/1513 + + # failed creating tunnel interface wt-priv: [operation not permitted + "CAP_NET_ADMIN" + # failed to pull up wgInterface [wt-priv]: failed to create ipv4 raw socket: socket: operation not permitted + "CAP_NET_RAW" + ] + # required for eBPF filter, used to be subset of CAP_SYS_ADMIN + ++ optional (versionAtLeast kernel.version "5.8") "CAP_BPF" + ++ optional (versionOlder kernel.version "5.8") "CAP_SYS_ADMIN"; + }; + })); + + # see https://github.com/systemd/systemd/blob/17f3e91e8107b2b29fe25755651b230bbc81a514/src/resolve/org.freedesktop.resolve1.policy#L43-L43 + security.polkit.extraConfig = mkIf config.services.resolved.enable '' + // systemd-resolved access for Netbird clients + polkit.addRule(function(action, subject) { + var actions = [ + "org.freedesktop.resolve1.set-dns-servers", + "org.freedesktop.resolve1.set-domains", + ]; + var users = ${builtins.toJSON (toHardenedClientList (client: client.name))}; + + if (actions.indexOf(action.id) >= 0 && users.indexOf(subject.user) >= 0 ) { + return polkit.Result.YES; + } + }); + ''; + }) + # migration & temporary fixups section + { + systemd.services = toClientAttrs (client: + nameValuePair client.name { + preStart = '' + set -eEuo pipefail + ${optionalString (client.logLevel == "trace" || client.logLevel == "debug") "set -x"} + + PATH="${makeBinPath (with pkgs; [coreutils jq diffutils])}:$PATH" + export ${toShellVars client.environment} + + # merge /etc/${client.name}/config.d' into "$NB_CONFIG" + { + test -e "$NB_CONFIG" || echo -n '{}' > "$NB_CONFIG" + + # merge config.d with "$NB_CONFIG" into "$NB_CONFIG.new" + jq -sS 'reduce .[] as $i ({}; . * $i)' \ + "$NB_CONFIG" \ + /etc/${client.name}/config.d/*.json \ + > "$NB_CONFIG.new" + + echo "Comparing $NB_CONFIG with $NB_CONFIG.new ..." + if ! diff <(jq -S <"$NB_CONFIG") "$NB_CONFIG.new" ; then + echo "Updating $NB_CONFIG ..." + mv "$NB_CONFIG.new" "$NB_CONFIG" + else + echo "Files are the same, not doing anything." + rm "$NB_CONFIG.new" + fi + } + ''; + }); + } + ]; +} diff --git a/modules/optional/graphical/xserver.nix b/modules/optional/graphical/xserver.nix index 07ec866..b6171ad 100644 --- a/modules/optional/graphical/xserver.nix +++ b/modules/optional/graphical/xserver.nix @@ -7,23 +7,23 @@ autoRepeatDelay = 235; autoRepeatInterval = 60; videoDrivers = ["modesetting"]; - libinput = { - enable = true; - mouse = { - accelProfile = "flat"; - accelSpeed = "0"; - middleEmulation = false; - }; - # touchpad = { - # accelProfile = "flat"; - # accelSpeed = "0.5"; - # naturalScrolling = true; - # disableWhileTyping = true; - # }; - }; xkb.layout = "de"; xkb.variant = "nodeadkeys"; }; + services.libinput = { + enable = true; + mouse = { + accelProfile = "flat"; + accelSpeed = "0"; + middleEmulation = false; + }; + # touchpad = { + # accelProfile = "flat"; + # accelSpeed = "0.5"; + # naturalScrolling = true; + # disableWhileTyping = true; + # }; + }; services.autorandr.enable = true; # Enable for Xorg debugging diff --git a/secrets/generated/sentinel/loki-basic-auth-hashes.age b/secrets/generated/sentinel/loki-basic-auth-hashes.age index 08e1a85c8741d3696bddc06d79a035d9f1c133f0..e425eb1fe832579e1e12c6b90cfcd6425409f1ae 100644 GIT binary patch delta 2610 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 delta 2485 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 diff --git a/secrets/generated/ward-home-gateway/promtail-loki-basic-auth-password.age b/secrets/generated/ward-home-gateway/promtail-loki-basic-auth-password.age new file mode 100644 index 0000000000000000000000000000000000000000..492b1668a8d3958d14586622894d745a9f70c844 GIT binary patch literal 474 zcmWm7J&V&|003Y?a0sG5!GefG%;lp=lYm!fn~yfVwrSGVG~kd+lQ$oC$@?~K(!|k8 z5CjDg7jaM!hZ~BMh@k!g1y39m9UL6|n&qB9@HoIDyu@9Wa22M>PPznSPGdx~W-%N` zi#558rmZzH0>d(^iRB7=L0k7tWR~5fi6?d2n?Z;46IG?KZeO`ZMx!ycrr<1U)fr1l z9M`7{vCI_G03`x|1H#7D(~|*)IRP22QQIKX6UMa`Z%1ug6&wULdbnn?b=?nigs)PM z>Hsak)2e|giH`VWfKj%_Aj1jEE}kfyA|sL=%~^AQp^A-6QZ0ROIIwXs?#J=Tb(H{1 z&&fPY08@~MlMx%iIh?o=^vT)8J(4Lr{geOW27C0pdEv$Fqst$U?du=l z?>7(Rw- X25519 v3+XpEnInoSpWE3VkLNhHNUNQh3PxhSuBm+p3j6MJ34 ++SQHfnlhgb1CCMkYwV/rHVuXSO+DcE8+vYLUPJfwoag +-> piv-p256 xqSe8Q A2NE5AmI0n5mSg0r/FFoZiNY17BY6fAEZBi+3J9bxKaw +a2pLqvnKezIzqL+B3gXSwabbNICIeFNujhOvNsmTPnU +-> OH012L^s-grease ~]V: {3[|q +HhrT/eZtP08VJEo2pNrzB+lDqnr+3GxzMo+BbaA +--- wYiApYLclfL5UFkVivNclukunmyxJEKbJfF89NUbgnE +殿slNFWhv$XfQ@xpIYѸ̸:-MpA +^Rg)Q$e98wF \ No newline at end of file diff --git a/secrets/rekeyed/sentinel/089eda6d3476434194e52aece39a18f5-loki-basic-auth-hashes.age b/secrets/rekeyed/sentinel/089eda6d3476434194e52aece39a18f5-loki-basic-auth-hashes.age new file mode 100644 index 0000000000000000000000000000000000000000..d0f42eba2dd41db5933b9f0cfc0423c8e71f350e GIT binary patch literal 2521 zcmV;~2`2VoXJsvAZewzJaCB*JZZ2b7dem zc3>bh3Qt3JMszn~V|O`tVOmX7SZ-u6M=L=JEiEk|PLnyg`W#gK7|mHvYS?*k`y%1B+U5=d5`q?kIm(5>@T!C#`sTnKM4B9x z4xIOlVLR77~n_D{n zSFtv-_b_d@FRZ@FVCfhH$oyyJ2C8h#Ov1)i?>bV%LZzSqja6#%2lp8Ezt9sP+^avZ zh44L8tADxz&gO|o9RHGKaLg3%n(g9r1ALDAH}AE_NeO!DUj`dH`r05Q#J*zCx(Xy_ zwP^?#aEffhowMo@GUrk4bn@*QISGBaLNESUBiNzVC|lV>vF3H#X#H3DWrxebi|s7% z-Z@pmJY>1tjFt9_?|h9qQ6-Neu`pi_M~LClA8dr=z#&vdzPD}dT`pg0kOb-fFg@dx z8IMGZSO$0B#cS=}!9^l%Q#UAsxYX)_0)CPGDE?`F84O=Lq#jgxz!q_)e(N&X$qVY- z1YE;T6=F_WaZ~(@6dp-Jo+_?-o2rM&JOWh~p<9IT{FyM{?=Ot3>5*Mfo5`-#tu=C= z{0!V{&wS01Plym+e}=Y!KYj%$4i7OI9;bPxaD`^z?j5iN6fX1}|9~-dUELAqu>cFv z7xBO4s$JeEwc48~OsmCWh;>FD8LB8AdNopm=SQl)axYKlI%j|ANGyf~Sd>l3U3>%n zT~$U8dj2)eQpoifsXNu&;UzY8OhBavd;EnIk7M_aJSrLuGC zs`3}<3FO&}Ehc#ACf>ti3_G5x9VhP&9N@pGn(kvN z5>vFu%|>hKpPZGx79H?HjjtkUU$nd0ms2~@v6-(>NQL++BCP!0@RS8V$FQ3-?*gmx zglSSsV~cMuVZLRsX?|-GE|=>y={@wEkdTBJ{Z_Z=Qv;_+ihPEC5C7iS=G+j?E5d1; zE8?nx1Q*ylAmTpvZA6OY#}6WS>Jk{GISDZ6-pTZ6x4{R~Vv1J>f6aUsn|0B4D@V6& z#4UEf7B4RBs>fUI;jY-{r((5UbZfUQLN&{%>o`gHa;<{x)u=>M-XLpYgq!IvjH2#_ zQLCX=SYZ2M=yrs&zJz5+rS51PsX2s!;~N{+{A%OidQi1(v(<#ITX+R8p12e72y<_YaEXDBHVK&iVl-7Kr@<7{h zB%fx4v6}2jWJ%=bOhY=PSS>ie(!^!GOMo5m2R7 z14jW(;?aq#*{&W(NT}SNG$P91aJ zj2Ro7v-+z|y5arxcgYOlPM1SG#-3dr!m<334UcAv-%FySLG0&<$P4t~qz6oyUkn$B zT<43eA43USUA@(Ek;KQSpnB?_6xVxufff@9zj9ZsKpQj$e_}y|CrU<9djgWCrwvz? zv=a-{%wgetfKO@Yb}u6@=h(bQAK0#ex}S~Tr1 zWyJj}7)p?!WwkF3e)y=04z8{Eq^AU^vsDEJ1tEyQnpx%=gkebcd2&{Yo~f zQOw%dDm?arpR<@ksasw-oq{33>kENx1JY=p7_6@2zMl>&-M}Aupy>}+u3S-4*BqO> ze`tGyG{+3+?V=7?pf-vueb@0tql^_xf!mCQ9~iH}hp#5hq)TxpT~`mNmWLpn9@4NW jch%I!w3*3T7+uIC*;fLp@G{D*LGeyQ5Ji{t5u~~XHY~nH literal 0 HcmV?d00001 diff --git a/secrets/rekeyed/sentinel/3b515237f2eec169c6992aea9e6fe02b-loki-basic-auth-hashes.age b/secrets/rekeyed/sentinel/3b515237f2eec169c6992aea9e6fe02b-loki-basic-auth-hashes.age deleted file mode 100644 index db279043b21093d065cfe0b63c71ccea64fd4279..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2498 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 MT()MpLV}A?LYTy)(*OVf diff --git a/secrets/rekeyed/sire-influxdb/de3953b78bf9eda16ca6dbbbc5e128af-telegraf-influxdb-token-ward-home-gateway.age b/secrets/rekeyed/sire-influxdb/de3953b78bf9eda16ca6dbbbc5e128af-telegraf-influxdb-token-ward-home-gateway.age new file mode 100644 index 0000000..9f5bfe2 --- /dev/null +++ b/secrets/rekeyed/sire-influxdb/de3953b78bf9eda16ca6dbbbc5e128af-telegraf-influxdb-token-ward-home-gateway.age @@ -0,0 +1,7 @@ +age-encryption.org/v1 +-> ssh-ed25519 1tdZKQ x15cWg4EVxPkDFiC/LX0ZkxemeZ/kBNoFb3jiJlZVk4 +vynUKyQr3lLcF9tlD6PgEl7VYwWVD7IAW7pHOK2MPow +-> Z[gKR-grease m 8Dw~,5" c@@SK%(= ED +biT3jfJ3Ay60Mw +--- YYo/rkngwrmOVNw4JujU1PURW20kJZ4LHPTfLaX/5+k +4cw7@ s~Н?c=a3F{9Mk+&zL`x]=LƛA̶ln!4 \ No newline at end of file diff --git a/secrets/rekeyed/ward-home-gateway/8a47fe3ea579205dbcfc3d30bc6d2467-promtail-loki-basic-auth-password.age b/secrets/rekeyed/ward-home-gateway/8a47fe3ea579205dbcfc3d30bc6d2467-promtail-loki-basic-auth-password.age new file mode 100644 index 0000000000000000000000000000000000000000..1064eaa1325c5ea00695bd685c0e5216be66539a GIT binary patch literal 337 zcmV-X0j~aGXJsvAZewzJaCB*JZZ2}RIahUSL0468V{lVfST`?OGI2FYNm6KfcQgu4Wh+T*Rxw0m zLuFxlHfd9MOKUS>FG+ZHad&HLH8eGCFibaNI7oRlWJL-sJ|J~FB1$f0EoX9NVRL05 zZ&f%7K{-`UW@LI)S2%H5Y-mLa=RCfw3EiE8tK}A9{Lv2J^Y(#WwOn6K-D`<0THdJ_bNiTG0F>O$EYi3$bP(*fS zZ)XYvnevgIc>~caNYt^7)e-_xF6}iUowXfe7v@G!{$i(FP7};>~;R; ssh-ed25519 AIXmkg HkcSDJOMqSmGcZRWPrztDY/DmPemcu+dXLVzoKJmGVA +FMS5I9fpG7epGW6FU+gXB5ZmVrma2iJTO/FlfReJrx0 +-> >CWFj-grease AQ(U +0Sq4jSIQVX6OUzyWZZhnYHgxHEzDb0Lt+U1wl9dVWK2mODpOMfx/Q3OihTVALOHf +3I1huPPQy+ZYlErp +--- QnFS37Svotfos23OvMLBVE2NjMqdKCAihbg6OIFhcaA +YfP)K|Mbp;/?i"TV$=XࡂHF'GI%L \ No newline at end of file