dns: switch bind -> kresd (a.k.a. knot-resolver)

This commit is contained in:
2025-09-09 05:19:25 +00:00
parent f2d5bfcf6b
commit 5d2a73afb5
7 changed files with 276 additions and 130 deletions

View File

@@ -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

View File

@@ -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;

View File

@@ -25,6 +25,7 @@
imports = [
./bind.nix
./hickory-dns.nix
./kresd.nix
./unbound.nix
];

View File

@@ -0,0 +1,60 @@
## config
# - <https://knot-resolver.readthedocs.io/en/stable/config-overview.html>
{ 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 <pkgs/by-name/sane-scripts/src/sane-vpn>
"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: <https://www.knot-resolver.cz/documentation/stable/config-overview.html>
-- 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
'';
};
}

View File

@@ -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;
}
];

View File

@@ -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: <https://github.com/tailscale/tailscale/issues/16080>
# 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: <https://github.com/tailscale/tailscale/issues/16080>
# 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}"
)
];
};
})
];
}

View File

@@ -0,0 +1,55 @@
{
"data": "ENC[AES256_GCM,data:XxvyW3Y93qAuZ03k2FOl+y5AkQP2BqBGliad+C9UrlhXh3Uf/2f/WWViSwmHRCJfLMMjkYzFAHsquw0sbQhxeEpJJlYC2709MKYeHidfqHi82Kw6zTIj1GrnRvDkRjddjNdpx/267+R9dpZ07XG+B7B18dCyT8gvDFPDODiaj8otbAW3ykvNSV9QHh4d53eMbq+PSt1JF/ARUhVOiR2j9xd21mMv2IRsA/lwWrfiuj+3pfMG8XQXmYinCoK+uvrr3a/juubZBoi6qdA9YLVO0iyJ7IAyz3+Q3sPUMfKtdKbH5sIXDKLeNzyKeMmxKuPi/A0qKgtmZuCqU3rHhnj4hlyTP/ShBsUAWlxO8LMJ/rZRjr/rZj8IZEFPx0AtoepkzEHkGKr9YR4r3237Oc/BavHtGe8M5g8wIibWnSUHMyK65st/uhkQ9ExVatSSBHLj+3i5xNXdUNA6LNPjfszmvsMhK7CcErU/F3UU6/ytsg9kO7e/UzIwJAt9bvzwERfLBnLUx2efDgnWXZTD4FjwxZJNRdHTTrnt5oaUcXJc64DAi2y20E4LyzfS2I90+t0YNaB0suOwe9dMQwWITEKcJFwW5MX3jKwH9crzghx0U1gOg3A8e7UWuVZloV1tSsw2Ke2zQW8RzYCY7kRdNP1NiYAYbhqbxNirp8OtdI6nsxfc0vCEaDI4SWPOcY8cwwn4f1Q6kCbrv1WKcSr5dqFi6wHrXn5omNQLkeLUkhY27W5plNkiErrhp4Jod97KC2IFWjX9zentF5yx8HVX/jIwqc/hGT78X3prIfAYqs5lDVQvTXyv/GLUpOw5kyug0EDX75cW25ex2SSWCbFxRjygIN2xTDXluMEtYw7bhUAhh+rGUle8pOaUf8raDfIRRQi0WdRBOV1kwMz7V+QjI39iXkZJNoHIU9QecCSfwR+a7/eBhuBW4NkHAPemH7WEM1dyQd2mZSjX/ZRq+0Ngx75jwEjDUkYvkAduXFy+03z0sEhIxQRkwTc9isO+d82bGQUZOkX+hNpBxScYjQtjyGqJMLvxlkEZ8TAVO0c6afxNWicjso7FUIOM355g0SBcFtcemQUGLBxQf5uS7xmm7ThJqfTsgCa64OEBqU0G5HBoPeKz2+vEEX+VuAOS9NrK2Lh8oI5s25PUWsbfIg8jqq4l6sLm1ZqqNPNaKecx/c7dWHkxqkBIq4E7e5+zTwOXYQ00j5jGgX8ZgeVG0VA2+9KnJ6lcC+U83D4YvMvxPlyaPDhKyC/EIPX8AxIhWW+pOnool53A6FONOxjjE10+C50iG+9oCg2zgyrd7cnLI7qFF2rmc5WIFO9rNIl5nEitQ+Y6ermOMvEhpxGSXkMKEZMLQhUMPvo2NQFsOiV3T7bmor+w4SUD1v8WeI5Gg17vdoAyxP0zje5Vr3CcBv62WT4SfnbvEpsxjdm0BWMvjAss4z36Vrv+jSnj56u3Ve0qP39ryqAH3n3o+zsDhQT/lfFSPvBfCUVZB+PcC6L1ZV0DP/LL5mx0e/++owjuSCjufH6defBU6aqNT8MAoN0p9q4MxDv0/WT7IEsHO8D3l8t06YogMBzGtgGbKkO7FqZOrl3vPwKfBgwFwAQST9sN9ktqy3nM2fnLgH+3qkCJQQP98I7jB9DsFBbFji7BNVzPHf5zXYbItM7kmAXD1lktAX5t5xxfrdx/QiTBaKI0MPpQL0O3yUfOUBfeCMA15IY2ZEzxRwIbWXJPM35BKAV6pwuLpzvi1bK5GDI6aH+39NPgj9ag/xDyxua3/jID7UjZleDQ1PVMP1ho7O8EHQVgSr5j6XmB/aso/PrJ3pL+NTFm9IL6Yagg6XHaN3mUKyoW0nokX1fTwb7gHZcmEe6yTek4HdpWOPhe3RgZqkArjQ+WsdST3zbpKLkMMbxJJWM2IWxRWmUvQSWH2OEa9tVkZ3XbDwK38pjiVdpls2fzfHlyG1QdgcccTHFhWolcRctJ2nLwi44JLfE79BvbfR4yzytQdexiOR8Enhijs2wbe2dcnVp2Ea0gzMJYJik5OEjyMi6z14q1UJNNlO1ZcrCnbHU3Dfg4uYYq2U6doZ+NcDPzxBEoJp22wCNOpMxgslQlGtdQa5DffTsngI9032IFX8IFvAS19wUKz2AH0TZxfbzxhIiP4/R1zT7LMTzwVmDAZu/wb5KDuQ==,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"
}
}