diff --git a/hosts/common/ids.nix b/hosts/common/ids.nix index 1d81104bf..375f8f36e 100644 --- a/hosts/common/ids.nix +++ b/hosts/common/ids.nix @@ -97,6 +97,8 @@ sane.ids.named.uid = 2012; sane.ids.named.gid = 2012; sane.ids.lpadmin.gid = 2013; + sane.ids.knot-resolver.uid = 2014; + sane.ids.knot-resolver.gid = 2014; # found on graphical hosts sane.ids.nm-iodine.uid = 2101; # desko/moby/lappy diff --git a/hosts/common/net/dns/bind.nix b/hosts/common/net/dns/bind.nix index bb21e82f0..1c3247acb 100644 --- a/hosts/common/net/dns/bind.nix +++ b/hosts/common/net/dns/bind.nix @@ -22,6 +22,7 @@ let hostCfg = config.sane.hosts.by-name."${config.networking.hostName}" or null; bindCfg = config.services.bind; in +lib.optionalAttrs false #< XXX(2025-09-08): using kresd / knot-resolver now { config = lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) { services.resolved.enable = lib.mkForce false; diff --git a/hosts/common/net/dns/default.nix b/hosts/common/net/dns/default.nix index 505f366e4..30eb145ef 100644 --- a/hosts/common/net/dns/default.nix +++ b/hosts/common/net/dns/default.nix @@ -25,6 +25,7 @@ imports = [ ./bind.nix ./hickory-dns.nix + ./kresd.nix ./unbound.nix ]; diff --git a/hosts/common/net/dns/kresd.nix b/hosts/common/net/dns/kresd.nix new file mode 100644 index 000000000..4e1bdf1a0 --- /dev/null +++ b/hosts/common/net/dns/kresd.nix @@ -0,0 +1,60 @@ +## config +# - +{ config, lib, ... }: +let + hostCfg = config.sane.hosts.by-name."${config.networking.hostName}" or null; +in +{ + config = lib.mkIf (!config.sane.services.hickory-dns.asSystemResolver) { + services.resolved.enable = lib.mkForce false; + + networking.nameservers = [ + # be compatible with systemd-resolved + # "127.0.0.53" + # or don't be compatible with systemd-resolved, but with libc and pasta instead + # see + "127.0.0.1" + # enable IPv6, or don't; unbound is spammy when IPv6 is enabled but unroutable + # "::1" + ]; + + networking.resolvconf.useLocalResolver = false; #< we manage resolvconf explicitly, above + networking.resolvconf.extraConfig = '' + # DNS serviced by `kresd` (knot-resolver) recursive resolver + name_servers='127.0.0.1' + ''; + + sane.persist.sys.byPath."/var/cache/knot-resolver" = { + # TODO: store the cache in private store, and restart the service once that's been unlocked? + store = "plaintext"; + method = "bind"; + acl.mode = "0770"; + acl.user = "knot-resolver"; + }; + + services.kresd.enable = true; + services.kresd.listenPlain = [ + "127.0.0.1:53" + ] ++ lib.optionals (hostCfg != null && hostCfg.wg-home.ip != null) [ + # allow wireguard clients to use us as a recursive resolver (only needed for servo) + "${hostCfg.wg-home.ip}:53" + ]; + + # TODO: + # - [x] disable DNSSEC + # - [ ] IPv4-only + # - [ ] serve tailscale records + # - [ ] persist the on-disk cache + # - [ ] integrate with dhcp-configs + services.kresd.extraConfig = '' + -- config docs: + + -- we can't guarantee that all forwarders support DNSSEC. + -- replicating my bind config, and just disabling dnssec universally + -- dnssec = false + -- trust_anchors.remove('.') + + net.ipv6 = false + ''; + }; +} diff --git a/hosts/common/secrets.nix b/hosts/common/secrets.nix index d981a66dd..95aa5edd1 100644 --- a/hosts/common/secrets.nix +++ b/hosts/common/secrets.nix @@ -73,7 +73,6 @@ in "mx-sanebot-env".owner = config.users.users.colin.name; "rsync-net-env".owner = config.users.users.colin.name; "rsync-net-id_ed25519".owner = config.users.users.colin.name; - "tailscale-work-zones-bind.conf".owner = "named"; "transmission_passwd".owner = config.users.users.colin.name; } ]; diff --git a/hosts/modules/roles/work/tailscale.nix b/hosts/modules/roles/work/tailscale.nix index f718f18ad..6e311e3eb 100644 --- a/hosts/modules/roles/work/tailscale.nix +++ b/hosts/modules/roles/work/tailscale.nix @@ -104,134 +104,162 @@ let }; in { - config = lib.mkIf config.sane.roles.work { - sane.persist.sys.byStore.private = [ - { user = "root"; group = "root"; mode = "0700"; path = "/var/lib/tailscale"; method = "bind"; } - ]; - services.tailscale.enable = true; - - services.tailscale.package = tailscale; - systemd.services.tailscaled.environment.TS_DEBUG_USE_IP_COMMAND = "1"; - - # "statically" configure the routes to tailscale. - # tailscale doesn't use the kernel wireguard module, - # but a userspace `wireguard-go` (coupled with `/dev/net/tun`, or a pure - # pasta-style TCP/UDP userspace dev). - # - # it therefore appears as an "unmanaged" device to network managers like systemd-networkd. - # in order to configure routes, we have to script it. - systemd.services.tailscaled.serviceConfig.ExecStartPost = [ - (pkgs.writeShellScript "tailscaled-add-routes" '' - while ! ${lib.getExe' tailscale "tailscale"} status ; do - echo "tailscale not ready" - sleep 2 - done - for addr in ${lib.concatStringsSep " " routableSubnets}; do - (set -x ; ${ip} route add table main "$addr" dev tailscale0 scope global) - done - '') - ]; - systemd.services.tailscaled.preStop = '' - for addr in ${lib.concatStringsSep " " routableSubnets}; do - (set -x ; ${ip} route del table main "$addr" dev tailscale0 scope global) || true - done - ''; - # systemd.network.networks."50-tailscale" = { - # # see: `man 5 systemd.network` - # matchConfig.Name = "tailscale0"; - # routes = [ - # # { - # # Scope = "global"; - # # # 0.0.0.0/8 is a reserved-for-local-network range in IPv4 - # # Destination = "0.0.0.0/8"; - # # } - # { - # Scope = "global"; - # # Scope = "link"; - # # 10.0.0.0/8 is a reserved-for-private-networks range in IPv4 - # Destination = "10.0.0.0/8"; - # } - # { - # Scope = "global"; - # # Scope = "link"; - # # 100.64.0.0/10 is a reserved range in IPv4 - # Destination = "100.64.0.0/10"; - # } - # ]; - # # RequiredForOnline => should `systemd-networkd-wait-online` fail if this network can't come up? - # linkConfig.RequiredForOnline = false; - # linkConfig.Unmanaged = lib.mkForce false; #< tailscale nixos module declares this as unmanaged - # }; - - # services.tailscale.useRoutingFeatures = "client"; - services.tailscale.extraSetFlags = [ - # --accept-routes does _two_ things: - # 1. allows tailscale to discover, internally, how to route to peers-of-peers. - # 2. instructs tailscale to tell the kernel to route discovered routes through the tailscale0 device. - # even if i disable #2, i still need --accept-routes to provide #1. - "--accept-routes" - # "--operator=colin" #< this *should* allow non-root control, but fails: - # lock the preferences i care about, because even if they're default i think they _might_ be conditional on admin policy: - # --accept-dns=false: - # 1. i manage DNS (/etc/resolv.conf) manually, with BIND/nixos - # 2. `tailscale dns query ...` works only if `--accept-dns` is set FALSE. - # maybe because `--accept-dns=true` causes tailscaled to fail to write resolvconf, and then it aborts, or something... - "--accept-dns=false" - # "--accept-routes=false" - "--advertise-connector=false" - "--advertise-exit-node=false" - # "--auto-update=false" # "automatic updates are not supported on this platform" - "--ssh=false" - "--update-check=false" - "--webclient=false" - ]; - services.tailscale.extraDaemonFlags = [ - "-verbose" "7" - ]; - services.bind.extraConfig = '' - include "${config.sops.secrets."tailscale-work-zones-bind.conf".path}"; - ''; - - systemd.services.tailscaled = { - # systemd hardening (systemd-analyze security tailscaled.service) - serviceConfig.AmbientCapabilities = "CAP_NET_ADMIN"; - serviceConfig.CapabilityBoundingSet = "CAP_NET_ADMIN"; - serviceConfig.LockPersonality = true; - serviceConfig.MemoryDenyWriteExecute = true; - serviceConfig.NoNewPrivileges = true; - - serviceConfig.ProtectClock = true; - serviceConfig.ProtectControlGroups = true; - serviceConfig.ProtectHome = true; - serviceConfig.ProtectHostname = true; - serviceConfig.ProtectKernelLogs = true; - serviceConfig.ProtectKernelModules = true; - serviceConfig.ProtectKernelTunables = true; - serviceConfig.ProtectProc = "invisible"; - serviceConfig.ProtectSystem = "strict"; # makes read-only: all but /dev, /proc, /sys. - serviceConfig.ProcSubset = "pid"; - - # serviceConfig.PrivateIPC = true; - serviceConfig.PrivateTmp = true; - - # serviceConfig.RemoveIPC = true; #< does not apply to root - serviceConfig.RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX"; - # #VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/... - # # see `systemd-analyze filesystems` for a full list - serviceConfig.RestrictFileSystems = "@application @basic-api @common-block"; - serviceConfig.RestrictRealtime = true; - serviceConfig.RestrictSUIDSGID = true; - serviceConfig.SystemCallArchitectures = "native"; - serviceConfig.SystemCallFilter = [ - "@system-service" - "@sandbox" - "~@chown" - "~@cpu-emulation" - "~@keyring" + config = lib.mkMerge [ + (lib.mkIf config.sane.roles.work { + sane.persist.sys.byStore.private = [ + { user = "root"; group = "root"; mode = "0700"; path = "/var/lib/tailscale"; method = "bind"; } ]; - serviceConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom} - serviceConfig.DeviceAllow = "/dev/net/tun"; - serviceConfig.RestrictNamespaces = true; - }; - }; + services.tailscale.enable = true; + + services.tailscale.package = tailscale; + systemd.services.tailscaled.environment.TS_DEBUG_USE_IP_COMMAND = "1"; + + # "statically" configure the routes to tailscale. + # tailscale doesn't use the kernel wireguard module, + # but a userspace `wireguard-go` (coupled with `/dev/net/tun`, or a pure + # pasta-style TCP/UDP userspace dev). + # + # it therefore appears as an "unmanaged" device to network managers like systemd-networkd. + # in order to configure routes, we have to script it. + systemd.services.tailscaled.serviceConfig.ExecStartPost = [ + (pkgs.writeShellScript "tailscaled-add-routes" '' + while ! ${lib.getExe' tailscale "tailscale"} status ; do + echo "tailscale not ready" + sleep 2 + done + for addr in ${lib.concatStringsSep " " routableSubnets}; do + (set -x ; ${ip} route add table main "$addr" dev tailscale0 scope global) + done + '') + ]; + systemd.services.tailscaled.preStop = '' + for addr in ${lib.concatStringsSep " " routableSubnets}; do + (set -x ; ${ip} route del table main "$addr" dev tailscale0 scope global) || true + done + ''; + # systemd.network.networks."50-tailscale" = { + # # see: `man 5 systemd.network` + # matchConfig.Name = "tailscale0"; + # routes = [ + # # { + # # Scope = "global"; + # # # 0.0.0.0/8 is a reserved-for-local-network range in IPv4 + # # Destination = "0.0.0.0/8"; + # # } + # { + # Scope = "global"; + # # Scope = "link"; + # # 10.0.0.0/8 is a reserved-for-private-networks range in IPv4 + # Destination = "10.0.0.0/8"; + # } + # { + # Scope = "global"; + # # Scope = "link"; + # # 100.64.0.0/10 is a reserved range in IPv4 + # Destination = "100.64.0.0/10"; + # } + # ]; + # # RequiredForOnline => should `systemd-networkd-wait-online` fail if this network can't come up? + # linkConfig.RequiredForOnline = false; + # linkConfig.Unmanaged = lib.mkForce false; #< tailscale nixos module declares this as unmanaged + # }; + + # services.tailscale.useRoutingFeatures = "client"; + services.tailscale.extraSetFlags = [ + # --accept-routes does _two_ things: + # 1. allows tailscale to discover, internally, how to route to peers-of-peers. + # 2. instructs tailscale to tell the kernel to route discovered routes through the tailscale0 device. + # even if i disable #2, i still need --accept-routes to provide #1. + "--accept-routes" + # "--operator=colin" #< this *should* allow non-root control, but fails: + # lock the preferences i care about, because even if they're default i think they _might_ be conditional on admin policy: + # --accept-dns=false: + # 1. i manage DNS (/etc/resolv.conf) manually, with BIND/nixos + # 2. `tailscale dns query ...` works only if `--accept-dns` is set FALSE. + # maybe because `--accept-dns=true` causes tailscaled to fail to write resolvconf, and then it aborts, or something... + "--accept-dns=false" + # "--accept-routes=false" + "--advertise-connector=false" + "--advertise-exit-node=false" + # "--auto-update=false" # "automatic updates are not supported on this platform" + "--ssh=false" + "--update-check=false" + "--webclient=false" + ]; + services.tailscale.extraDaemonFlags = [ + "-verbose" "7" + ]; + + systemd.services.tailscaled = { + # systemd hardening (systemd-analyze security tailscaled.service) + serviceConfig.AmbientCapabilities = "CAP_NET_ADMIN"; + serviceConfig.CapabilityBoundingSet = "CAP_NET_ADMIN"; + serviceConfig.LockPersonality = true; + serviceConfig.MemoryDenyWriteExecute = true; + serviceConfig.NoNewPrivileges = true; + + serviceConfig.ProtectClock = true; + serviceConfig.ProtectControlGroups = true; + serviceConfig.ProtectHome = true; + serviceConfig.ProtectHostname = true; + serviceConfig.ProtectKernelLogs = true; + serviceConfig.ProtectKernelModules = true; + serviceConfig.ProtectKernelTunables = true; + serviceConfig.ProtectProc = "invisible"; + serviceConfig.ProtectSystem = "strict"; # makes read-only: all but /dev, /proc, /sys. + serviceConfig.ProcSubset = "pid"; + + # serviceConfig.PrivateIPC = true; + serviceConfig.PrivateTmp = true; + + # serviceConfig.RemoveIPC = true; #< does not apply to root + serviceConfig.RestrictAddressFamilies = "AF_INET AF_INET6 AF_NETLINK AF_UNIX"; + # #VVV this includes anything it reads from, e.g. /bin/sh; /nix/store/... + # # see `systemd-analyze filesystems` for a full list + serviceConfig.RestrictFileSystems = "@application @basic-api @common-block"; + serviceConfig.RestrictRealtime = true; + serviceConfig.RestrictSUIDSGID = true; + serviceConfig.SystemCallArchitectures = "native"; + serviceConfig.SystemCallFilter = [ + "@system-service" + "@sandbox" + "~@chown" + "~@cpu-emulation" + "~@keyring" + ]; + serviceConfig.DevicePolicy = "closed"; # only allow /dev/{null,zero,full,random,urandom} + serviceConfig.DeviceAllow = "/dev/net/tun"; + serviceConfig.RestrictNamespaces = true; + }; + }) + + (lib.mkIf config.services.bind.enable { + # make DNS resolvable, if using BIND + sops.secrets."tailscale-work-zones-bind.conf".owner = "named"; + services.bind.extraConfig = '' + include "${config.sops.secrets."tailscale-work-zones-bind.conf".path}"; + ''; + }) + + (lib.mkIf config.services.kresd.enable { + # make DNS resolvable, if using kresd + sops.secrets."tailscale-work-zones-kresd.conf".owner = "knot-resolver"; + + systemd.services."kresd@".serviceConfig = let + package = config.services.kresd.package; + in { + ExecStart = lib.mkForce [ + "" #< clear previous assignment + ( + # override default CLI so as to inject `-c` for secret config portion + # TODO: refactor for cleaner integration with hosts/common/net/dns/kresd.nix + "${package}/bin/kresd --noninteractive" + + " -c ${package}/lib/knot-resolver/distro-preconfig.lua" + + " -c /etc/knot-resolver/kresd.conf" + + " -c ${config.sops.secrets."tailscale-work-zones-kresd.conf".path}" + ) + ]; + }; + }) + ]; } diff --git a/secrets/common/tailscale-work-zones-kresd.conf.bin b/secrets/common/tailscale-work-zones-kresd.conf.bin new file mode 100644 index 000000000..386cf5fad --- /dev/null +++ b/secrets/common/tailscale-work-zones-kresd.conf.bin @@ -0,0 +1,55 @@ +{ + "data": "ENC[AES256_GCM,data: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,iv:67Y8DB6QlwEIGKEjUEIhyTDOsDuxPCzkh5KxvWxaGfM=,tag:xxzr41eHKnUYTIkExlkRLA==,type:str]", + "sops": { + "age": [ + { + "recipient": "age1tnl4jfgacwkargzeqnhzernw29xx8mkv73xh6ufdyde6q7859slsnzf24x", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBxNUVxekE2SnhSTzFvVjF4\nSmpvaTFNQXFIbmRXRVRvQ2ZyVXB2VENoV1hBCkd2QmhlRjhFYkROOERNbTdyN0xC\nR1EwNU9mb3BTZHZXcGVFSDNZNWtwK2MKLS0tIEIxdWpUZjdBdWNXbldxRjQ1OVdy\nUzdsdUh6eHhBeXNUMlZuQjF1YW8vaUEKWBMqFuDFRGOoQhVu7mwLHIiPDlgJjBlM\nVOSPfSS9FaaG83uzmwy/2Dj6dilOZNJD/IP0X7Jr2ECZY2o+hmfyUg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1nw3z25gn6l8gxneqw43tp8d2354c83d9sn3r0dqy5tapakdwhyvse0j2cc", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIOHR0YTNyY3NNSkU5VDNj\nR2FITmtIUVE0eWEyaXZRd3ZWbm44Ti8xTEMwCnZPaFpwVVR2QUZRTTQvRDVCM1VZ\nMFpTSHJhdGpNVUttNEk3NWVYeXcvRjAKLS0tIGErQzFTY1hFVVVnazAxN3dldW9i\nWW41WG9MWHQ1bXhTUmt0RFoxbEFCdmsK+s4IU0UilNVoTXV6Q886wE+mRJ86cX1Q\nRc5+zHi1DMtilaLhQ1IcNbiaDVHqJPSM1RAZXPNS2X3OfIvdw9nE8g==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1j2pqnl8j0krdzk6npe93s4nnqrzwx978qrc0u570gzlamqpnje9sc8le2g", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBRdmFxaExMZ3puTjE2V2Uw\nYmVIWjhGSDdrNTNLdnFPdHNrZzdUOWNJK0hZCldRY1ZjRVQ3TW5UT2RqVG5DdUs2\nMG9oNzZlcFFuajV4T1hLNnRSZ1laUkUKLS0tIFZsRHBtWmh0MFVZRkJFSE5uR3hP\nZHdiT1VBSjZMVC9hWEhnZnRCcFl6K3cKUiNlpoLHeiWp6Erl9QJt8hdZwOp2siYy\nAxqiGVT32ceuuGWrdmwJ3MMZoLMXv7GlZnUU2eaXfT3rPOwgsnL2hQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1z8fauff34cdecr6sjkre260luzxcca05kpcwvhx988d306tpcejsp63znu", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmVlJPL3ZBM0tWTXlack4x\nbGdMQXJDbGpZTHNSMTJSN2lUOXhpRkN5Z1VvCnU2M3h2SkZ5d2dSOEVhdk5zOXdP\nTTZYWElvQU43eEJtZFVZeEhvamZCSFEKLS0tIHRVNGdoYmZteHFkcC9lOXV2TjBL\nTXlid2crM1J2ZGhtUTljR0Y4SENuOGsKy42dvjwW9Af8vHpqxo9GJM7fn0mroRj3\nsCPjFpNK/fm1PX5Cfr3pDs5NAASSbPNKiHevH3O7idhYTKy5nt3uIQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1zsrsvd7j6l62fjxpfd2qnhqlk8wk4p8r0dtxpe4sdgnh2474095qdu7xj9", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA4RG9BT0cwb2ZjeWtQTGx1\ndThyMW9IVndIV25NMTdLT0JteitqcDMycFEwCkxZLzh6N3pObU81Q3ozMEtUVzA2\nU2pra1dvOENGY0NvcmpFdVg3ZW1Md1kKLS0tIDkyYzYzYWRyVDV5NmlPcEZPdVhl\ncGxXSjFITzlFVk85ZUE2Nk14Vm1lbWMKECvzNVEyDdgV8puTFSh8CFZqDEiwZX+b\n1SAYR28EwCFd0J6bLs/DMM3w5V8hMAb7MTq/L5JKsbn7xlkcTvkTAw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1hl50ufuxnqy0jnk8fqeu4tclh4vte2xn2d59pxff0gun20vsmv5sp78chj", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBadHoxd0dUUm5wck8vRktQ\nTktQdDlQVVIxRXVDL1lCN3lGYWl5dHhBdW5zCkQ2cnZ0SmN3NitHUE1Wem5JcG54\nMU5MSTJZcWRwc2pPeHdXYmEybGpxMVkKLS0tIDllVURmdElvcVZvaklXdTlYM3Fj\naGlNWkxWakpqNHQ5SFZoTzJTNFZyQVUKOA0CoxrvShn+kphpzbLt45WwWePpdvKY\n15d31zJ3eCtojKKUw8dWaJebK6BO32Bhcrnj8prFl2e4JoVKEsTZEg==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1vnw7lnfpdpjn62l3u5nyv5xt2c965k96p98kc43mcnyzpetrts9q54mc9v", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBwbVlyZTNBeWRIb05nbWc0\nVE5ZSnJnZk81MXlCd0Z6WEsraHQ4a1FhUFJnCkc0WnVmZmdPQ20rMDZCRFhWTGJq\nV1czVlF1Ym9xTlhwZk1LQlNrczR3QjQKLS0tIDEzUitRVTNLemF5ZzZ6YmhqRytI\nS2hwYTBDa0xydDFGQlFuM3lDNVNJS0EKuQwKlaYSf0UiHO6EUJ0pDcdnOkG8YdPo\nBYIcVA1lRQhU00W/r8UnyZ17x2lpEh6ubIXMhKyeR98qxfCla0fRWw==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1azm6carlm6tdjup37u5dr40585vjujajev70u4glwd9sv7swa99sk6mswx", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBEa3pIam8yNmpxT3ozTEYv\nYWxyend0QVQ3RTdVL3RlSSt0cVY4VlZrODFjCkxRZkExcVAzUzVlN2xhZEtoVFZU\nalhEWklJY21KUExtcjZNRVFUbFpFRUEKLS0tIEdEV3FPZWJ2T0g3MnZSQVpXWmM0\naXh5SHNVM3VPQVhkaktNWXRhZkJHaFkKc3/qRkxYWrP+lTYYdUPCA7Skrqoz/BkA\ndxunfEUmlkPz6ieF19WQrbHfGAobXi7p1Dby+XBnOeSVLnCRqq/rKQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1w7mectcjku6x3sd8plm8wkn2qfrhv9n6zhzlf329e2r2uycgke8qkf9dyn", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA0MlFKR3Z1blpxRWovYVRo\nMVZ1TWxtSWZjOUFBNFVpa0ZXWlNVQ0lXSlJvCjZWMjZYZ1pxeTFwQTBSRU1ESW4w\nRGZLZUJLQlhLNGFKOTZSNE5GUTNUOE0KLS0tIC9lNUxIaVpyc3dqU1VwOG5UNTJr\nN3EzSmRrWGN6MmRWRVhLNERpQmNERGcKnruwGd36Uty65Y7UCDgZdHfqwuBd7f+k\n6k5zcpW8+omd3E0psXzTA2dVCV12dCgGcM/1nsdDeUfnoP6uSPI8uQ==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age1tzlyex2z6t88tg9h82943e39shxhmqeyr7ywhlwpdjmyqsndv3qq27x0rf", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBIWm5tVWVqS1NuWnlkRkc3\nMkFIQWJFV21zd3E0c2hLekgzRHFuUktEYzEwCk50eFhHenZmdEtGK1YwYm1nK09l\nTXN6NGYrWmVGUUZtVDBKNDQzWDhnRzAKLS0tIEJveUdwcGpKM01DckgyaUVGaHFh\nUUxTemh6aitvaTBZcGdCRG5WVU9HSzAKCHt1N1BVPdwpFOVyjN9VK4h4pYqS4xJ/\n308+keE/7nHNE7guIqNBPABj509jIbPLkb3iX/1EttXsKUhH/OKtXA==\n-----END AGE ENCRYPTED FILE-----\n" + }, + { + "recipient": "age18vq5ktwgeaysucvw9t67drqmg5zd5c5k3le34yqxckkfj7wqdqgsd4ejmt", + "enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBNSStLeUJxU2s1QXJLQlEy\nWFFpZE5TM1d0RHBZb2JVazc0ekVxL28zdURRCld6WTQvUVdNKzNEOVhJRitjdXVk\nazZyYlpCRkV4QVlFS211YkVEbytZd3MKLS0tIGxVMjFVaW81ZVc5RnB2c3oydk5U\nTE5WYTlvNktNcHJPdkNUYTU4SUdJbmcKvrHh72I9j/dXnb/SEfIeOL5KCS0xPVTj\nKfRydp738qzprV376xVPIn71Z6ktDoC/7BJ++EpMoRU7vkm51/cs2Q==\n-----END AGE ENCRYPTED FILE-----\n" + } + ], + "lastmodified": "2025-09-09T05:18:26Z", + "mac": "ENC[AES256_GCM,data:aOGK7yhF0wuqmt9RSLQw9IwITxVmANz9v6uqLPEbZGmKkVjjbIUEtyDsmmdB8NG7O1M1ByU2lW+GtHcUMFaptJCLIaIKvJk8kq/36dDRz4J/+YzBRUbYngDHykdPqy9a8L6z7DMVtxiZQElcwUwXQNF8og7QeplgcSUfuRIXhN0=,iv:J4wabL0oOnc7bYjvxyljamoDAHsVLL+pkNBW/BU1rhg=,tag:q3OPTfQa4gznMFWVBbkRyA==,type:str]", + "unencrypted_suffix": "_unencrypted", + "version": "3.10.2" + } +}