Compare commits

..

6 Commits

Author SHA1 Message Date
Berwn 1cb6f39ea2 Add declarative SNM mail stack on mx1 with DNS-01, DANE, MTA-STS
mx1 runs Simple NixOS Mailserver (Postfix/Dovecot/Rspamd/OpenDKIM) for
cnx.email. The TLS cert is obtained via ACME DNS-01 using a dedicated,
scoped TSIG key (acme_mx1) that ns1 authorizes for only
_acme-challenge.mx1 and _acme-challenge.mta-sts on the cnx.email zone, so
the credential can write nothing else. Mailbox passwords are auto-minted
by a clan vars generator (four-word passphrase + number).

DANE TLSA (3 1 1) is published for _25._tcp.mx1; --reuse-key keeps the
key digest stable across renewals. MTA-STS is enforced via a Caddy vhost
serving the policy on :443 from the same cert (mta-sts SAN). Firewall
opens 25/587/465/143/993/443; 80 stays closed.
2026-06-18 14:47:20 +07:00
Berwn 026a26dd53 Add ns1 to secret vars/shared/dns-acme-mx1-secret/secret 2026-06-18 14:11:40 +07:00
Berwn 7e5d50b260 Update vars via generator dns-acme-mx1-knot for machine ns1 2026-06-18 14:11:40 +07:00
Berwn 312de984c1 Update vars via generator dns-acme-rfc2136 for machine mx1 2026-06-18 14:11:40 +07:00
Berwn d76aa8cc8d Update vars via generator mail-passwd-postmaster-at-cnx-email for machine mx1 2026-06-18 14:11:36 +07:00
Berwn 0a78cad06e Update vars via generator dns-acme-mx1-secret for machine mx1 2026-06-18 14:11:36 +07:00
30 changed files with 484 additions and 19 deletions
Generated
+105
View File
@@ -1,5 +1,21 @@
{
"nodes": {
"blobs": {
"flake": false,
"locked": {
"lastModified": 1604995301,
"narHash": "sha256-wcLzgLec6SGJA8fx1OEN1yV/Py5b+U5iyYpksUY/yLw=",
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"rev": "2cccdf1ca48316f2cfd1c9a0017e8de5a7156265",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"repo": "blobs",
"type": "gitlab"
}
},
"clan-core": {
"inputs": {
"data-mesher": "data-mesher",
@@ -73,6 +89,22 @@
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1767039857,
"narHash": "sha256-vNpUSpF5Nuw8xvDLj2KCwwksIbjua2LZCqhV1LNRDns=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "5edf11c44bc78a0d334f6334cdaf7d60d732daab",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
@@ -94,6 +126,54 @@
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": [
"nixos-mailserver",
"flake-compat"
],
"gitignore": "gitignore",
"nixpkgs": [
"nixos-mailserver",
"nixpkgs"
]
},
"locked": {
"lastModified": 1772893680,
"narHash": "sha256-JDqZMgxUTCq85ObSaFw0HhE+lvdOre1lx9iI6vYyOEs=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "8baab586afc9c9b57645a734c820e4ac0a604af9",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"nixos-mailserver",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
@@ -144,6 +224,30 @@
"type": "github"
}
},
"nixos-mailserver": {
"inputs": {
"blobs": "blobs",
"flake-compat": "flake-compat",
"git-hooks": "git-hooks",
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1773912645,
"narHash": "sha256-QHzRqq6gh+t3F/QU9DkP7X63dDDcuIQmaDz12p7ANTg=",
"owner": "simple-nixos-mailserver",
"repo": "nixos-mailserver",
"rev": "25e6dbb8fca3b6e779c5a46fd03bd760b2165bb5",
"type": "gitlab"
},
"original": {
"owner": "simple-nixos-mailserver",
"ref": "nixos-25.11",
"repo": "nixos-mailserver",
"type": "gitlab"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1778003029,
@@ -163,6 +267,7 @@
"root": {
"inputs": {
"clan-core": "clan-core",
"nixos-mailserver": "nixos-mailserver",
"nixpkgs": [
"clan-core",
"nixpkgs"
+3
View File
@@ -3,6 +3,9 @@
inputs.nixpkgs.follows = "clan-core/nixpkgs";
inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
inputs.treefmt-nix.inputs.nixpkgs.follows = "nixpkgs";
# Simple NixOS Mailserver, pinned to the branch matching clan-core's nixpkgs.
inputs.nixos-mailserver.url = "gitlab:simple-nixos-mailserver/nixos-mailserver/nixos-25.11";
inputs.nixos-mailserver.inputs.nixpkgs.follows = "nixpkgs";
outputs =
{
+1
View File
@@ -24,6 +24,7 @@
# No formatter, or reformatting would corrupt them.
"*.zone" # Knot zone files
"docs/book.toml" # mdBook config; no TOML formatter enabled
"flake.lock"
".envrc"
".gitignore"
+3 -4
View File
@@ -1,9 +1,11 @@
{ config, ... }:
{ config, inputs, ... }:
let
hosts = import ../../modules/hosts.nix;
in
{
imports = [
inputs.nixos-mailserver.nixosModules.default
../../modules/mail.nix
../../modules/static-ipv6.nix
../../modules/monitoring/exporters.nix
];
@@ -17,7 +19,4 @@ in
};
services.timesyncd.enable = true;
# Mail host backing the cnx.email MX (mx1.cnx.email -> 5.223.65.38).
# SMTP/IMAP services to be configured.
}
+46 -7
View File
@@ -1,4 +1,9 @@
{ config, lib, pkgs, ... }:
{
config,
lib,
pkgs,
...
}:
let
domains = import ../../modules/dns/domains.nix;
mesh = import ../../modules/mesh-hosts.nix { inherit config lib; };
@@ -7,6 +12,7 @@ in
{
imports = [
../../modules/dns/authoritative.nix
../../modules/dns/acme-mx1-secret.nix
../../modules/static-ipv6.nix
../../modules/monitoring/exporters.nix
];
@@ -33,11 +39,9 @@ in
time.timeZone = "Etc/GMT-1"; # UTC+1 (fixed offset, no DST)
services.timesyncd.enable = true;
# ACME DNS-01 (RFC 2136): a dedicated TSIG key, scoped to ns1 only, that an
# external ACME client uses to write _acme-challenge TXT records. acl_acme
# (referenced by each zone below) limits the key to TXT updates at or under
# _acme-challenge.<zone>; Knot then signs the record and transfers it to ns2,
# which never needs this key. Retrieve the secret for the client with:
# ACME DNS-01 (RFC 2136), general key. A dedicated TSIG key scoped by acl_acme
# (referenced by every zone below) to TXT updates at or under _acme-challenge.
# Retrieve the client config with:
# clan vars get ns1 dns-acme-tsig/acme.conf
clan.core.vars.generators.dns-acme-tsig = {
files."acme.conf" = {
@@ -51,8 +55,28 @@ in
'';
};
# ACME DNS-01, dedicated mx1 key. A *separate* TSIG key (acme_mx1) that only
# mx1 holds, rendered from the shared secret (generator dns-acme-mx1-secret,
# imported above). acl_acme_mx1 scopes it to TXT updates at exactly
# _acme-challenge.mx1 and _acme-challenge.mta-sts (the mail cert and its
# MTA-STS SAN), and it is attached only to the cnx.email zone below — so this
# credential can write nothing but mx1's own cert challenges.
clan.core.vars.generators.dns-acme-mx1-knot = {
files."acme.conf" = {
secret = true;
owner = "knot";
group = "knot";
};
dependencies = [ "dns-acme-mx1-secret" ];
script = ''
printf 'key:\n - id: acme_mx1\n algorithm: hmac-sha256\n secret: %s\n' \
"$(cat "$in"/dns-acme-mx1-secret/secret)" > "$out"/acme.conf
'';
};
services.knot.keyFiles = [
config.clan.core.vars.generators.dns-acme-tsig.files."acme.conf".path
config.clan.core.vars.generators.dns-acme-mx1-knot.files."acme.conf".path
];
services.knot.settings.acl = [
@@ -65,6 +89,18 @@ in
"update-owner-match" = "sub-or-equal";
"update-owner-name" = [ "_acme-challenge" ];
}
{
id = "acl_acme_mx1";
key = "acme_mx1";
action = [ "update" ];
"update-type" = [ "TXT" ];
"update-owner" = "name";
"update-owner-match" = "sub-or-equal";
"update-owner-name" = [
"_acme-challenge.mx1"
"_acme-challenge.mta-sts"
];
}
];
# Automatic DNSSEC signing policy (primary only). ECDSA P-256/SHA-256 with
@@ -93,9 +129,12 @@ in
"dnssec-signing" = true;
"dnssec-policy" = "cnx";
notify = [ "ns2" ];
# ns2 transfers; acme_ddns does general DNS-01 updates. The dedicated
# acme_mx1 key is attached only to cnx.email, so it can't touch other zones.
acl = [
"acl_ns2"
"acl_acme"
]; # ns2 transfers; acme_ddns key does DNS-01 updates
]
++ lib.optionals (d == "cnx.email") [ "acl_acme_mx1" ];
}) domains;
}
+19
View File
@@ -0,0 +1,19 @@
# Shared TSIG secret for the dedicated acme_mx1 key.
#
# This key lets mx1 — and only mx1 — write _acme-challenge.mx1.cnx.email TXT
# records on ns1 to obtain its mail TLS cert via ACME DNS-01. ns1 scopes it with
# acl_acme_mx1 (attached only to the cnx.email zone) so the credential can touch
# nothing else. ns1 renders this secret into a Knot key file; mx1 into a lego
# rfc2136 env file; both must carry the same secret, hence one shared generator
# with a per-host renderer that depends on it. Imported by ns1 and (via mail.nix)
# mx1.
{ pkgs, ... }:
{
clan.core.vars.generators.dns-acme-mx1-secret = {
share = true;
files."secret".secret = true;
runtimeInputs = [ pkgs.openssl ];
# 32 random bytes, base64 — a valid hmac-sha256 TSIG secret.
script = ''openssl rand -base64 32 | tr -d '\n' > "$out"/secret'';
};
}
+21
View File
@@ -13,6 +13,27 @@ $TTL 3600
; ---- Mail ----
mx1 IN A 5.223.65.38
mx1 IN AAAA 2a01:4ff:2f0:1963::1
@ IN MX 10 mx1.cnx.email.
@ IN TXT "v=spf1 mx -all"
_dmarc IN TXT "v=DMARC1; p=quarantine; rua=mailto:postmaster@cnx.email"
; ---- DANE / TLSA ----
; "3 1 1" = DANE-EE, SPKI, SHA-256: the digest of mx1's certificate public key.
; Valid because the zone is DNSSEC-signed and the lego cert uses --reuse-key, so
; the key (and thus this digest) is stable across renewals. Compute it AFTER the
; first issuance and paste the hex below:
; ssh mx1 'openssl x509 -in /var/lib/acme/mx1.cnx.email/cert.pem -noout -pubkey \
; | openssl pkey -pubin -outform DER | openssl dgst -sha256 -binary | xxd -p -c256'
_25._tcp.mx1 IN TLSA 3 1 1 bd9a51f60b6d2dd20f18b3553d2795053ac52f87567a46bc892006bb58506404
; ---- MTA-STS ----
; Policy host (A/AAAA point at mx1); the _mta-sts TXT id MUST be bumped whenever
; the policy file in modules/mail.nix changes, or senders keep the cached policy.
mta-sts IN A 5.223.65.38
mta-sts IN AAAA 2a01:4ff:2f0:1963::1
_mta-sts IN TXT "v=STSv1; id=2026061801"
mail._domainkey IN TXT ( "v=DKIM1; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr9QxTs5dLtY76bo156+Tp0GUoE554rMwIooIYa2MMYHNs8zPb0thFmaCKGAINdHKNIq2phXAlk51iBTfdqXjx7gVWSrs+ftykqO3b5hUjgImsgqPWGUTzy5/bUgcDELiD9KKEyKYD3+ebZEw6d0uvBvEsA6a1CPzOsufoCDtyKjByCuQzkCBrK25TUHFolGvEYcZexR0LSF+8hMss"
"xyw9NYiPpTXVCWQJnrZZpuOBiX0K2l5CAXVyuT/B5RcBXlAUhBTp3390VEhL0wAZMTOnvtvBYK3NnsTIh96fkh6MfWmre7Fi9hEq//xGf40N5/aomMjJrJdqFZJLZpDotb/XwIDAQAB"
)
+16 -8
View File
@@ -24,16 +24,25 @@ let
description = "ICMP (ping / PMTUD)";
};
# Inbound mail only. mx1 is the MX for cnx.email, so other servers deliver on
# 25. Submission (587/465) and IMAP (993) stay closed until the mail stack and
# mailboxes exist — admin access rides the mesh, same as the other hosts.
smtp = {
# Public mail ports for mx1 (MX for cnx.email). 25 is server-to-server
# delivery; 587/465 are client submission; 143/993 are IMAP. 443 serves only the
# MTA-STS policy (https://mta-sts.cnx.email/.well-known/mta-sts.txt); the cert
# itself uses ACME DNS-01 so port 80 stays closed. Admin still rides the mesh.
mailPort = port: description: {
direction = "in";
protocol = "tcp";
port = "25";
inherit port;
source_ips = world;
description = "SMTP (inbound mail)";
inherit description;
};
mailRules = [
(mailPort "25" "SMTP (inbound mail)")
(mailPort "587" "Submission (STARTTLS)")
(mailPort "465" "Submission (implicit TLS)")
(mailPort "143" "IMAP (STARTTLS)")
(mailPort "993" "IMAP (implicit TLS)")
(mailPort "443" "MTA-STS policy (HTTPS)")
];
dnsRules = [
{
@@ -61,8 +70,7 @@ in
];
"clan-ns1" = dnsRules;
"clan-ns2" = dnsRules;
"clan-mx1" = [
smtp
"clan-mx1" = mailRules ++ [
zerotier
ping
];
+139
View File
@@ -0,0 +1,139 @@
# Declarative mail stack for mx1 (Simple NixOS Mailserver: Postfix + Dovecot +
# Rspamd + OpenDKIM). Imported by machines/mx1 alongside the SNM flake module.
#
# Mailboxes are virtual (not system users): each address below is a login account
# whose password is auto-generated by a clan vars generator as a four-word
# passphrase with a trailing number (e.g. otter-lantern-cobalt-driftwood-42). The
# generator stores both the passphrase and its sha-512 hash. To add a mailbox:
# append the address to `accounts`, run `clan vars generate mx1`, redeploy mx1,
# then hand the passphrase to the user:
# clan vars get mx1 mail-passwd-<addr>/passphrase
# (addr with @ and . replaced by -at- and -, e.g. mail-passwd-postmaster-at-cnx-email)
{
config,
lib,
pkgs,
...
}:
let
hosts = import ./hosts.nix;
fqdn = "mx1.cnx.email";
mtaStsHost = "mta-sts.cnx.email";
# MTA-STS policy served at https://mta-sts.cnx.email/.well-known/mta-sts.txt.
# enforce = a sending MTA that fetched this must use a valid, MX-matching TLS
# cert or refuse to deliver. Bump the _mta-sts TXT id (in the zone) whenever
# this changes.
mtaStsPolicy = pkgs.writeText "mta-sts.txt" ''
version: STSv1
mode: enforce
mx: ${fqdn}
max_age: 604800
'';
# The mailboxes mx1 serves. postmaster is required by RFC 5321.
accounts = [
"postmaster@cnx.email"
];
genName = addr: "mail-passwd-" + lib.replaceStrings [ "@" "." ] [ "-at-" "-" ] addr;
passwdGenerators = lib.listToAttrs (
map (addr: {
name = genName addr;
value = {
files."passphrase".secret = true; # retrievable to hand to the user
files."hash".secret = true; # consumed by SNM's hashedPasswordFile
runtimeInputs = [
pkgs.xkcdpass
pkgs.mkpasswd
];
script = ''
pass="$(xkcdpass --numwords=4 --delimiter=- --case=lower)-$((RANDOM % 90 + 10))"
printf '%s' "$pass" > "$out"/passphrase
printf '%s' "$pass" | mkpasswd -s -m sha-512 > "$out"/hash
'';
};
}) accounts
);
loginAccounts = lib.listToAttrs (
map (addr: {
name = addr;
value.hashedPasswordFile = config.clan.core.vars.generators.${genName addr}.files."hash".path;
}) accounts
);
in
{
imports = [ ./dns/acme-mx1-secret.nix ];
clan.core.vars.generators = passwdGenerators // {
# Render the shared acme_mx1 TSIG secret into a lego rfc2136 env file. lego
# (via security.acme below) uses it to write the _acme-challenge.mx1.cnx.email
# TXT record to ns1, which authorizes the acme_mx1 key for exactly that owner.
dns-acme-rfc2136 = {
files."rfc2136.env".secret = true; # root-owned; systemd reads it as root
dependencies = [ "dns-acme-mx1-secret" ];
script = ''
printf 'RFC2136_NAMESERVER=${hosts.ns1.ipv4}:53\nRFC2136_TSIG_ALGORITHM=hmac-sha256.\nRFC2136_TSIG_KEY=acme_mx1\nRFC2136_TSIG_SECRET=%s\n' \
"$(cat "$in"/dns-acme-mx1-secret/secret)" > "$out"/rfc2136.env
'';
};
};
mailserver = {
enable = true;
# Fresh install: declare the latest layout the nixos-25.11 branch ships (3),
# so SNM uses the current dovecot mail directory layout with nothing to migrate.
stateVersion = 3;
inherit fqdn;
domains = [ "cnx.email" ];
inherit loginAccounts;
# Consume a security.acme cert we obtain ourselves via DNS-01 (below); no
# web server and no inbound HTTP needed, so port 80 stays closed. Add the
# MTA-STS host as a SAN so the one cert also covers the policy endpoint.
certificateScheme = "acme";
certificateDomains = [ mtaStsHost ];
dkimSelector = "mail";
};
security.acme = {
acceptTerms = true;
defaults.email = "postmaster@cnx.email";
certs.${fqdn} = {
dnsProvider = "rfc2136";
environmentFile = config.clan.core.vars.generators.dns-acme-rfc2136.files."rfc2136.env".path;
# ns1 is the only nameserver that accepts the acme_mx1 UPDATE; check
# propagation against it directly rather than a public resolver.
dnsResolver = "${hosts.ns1.ipv4}:53";
# Keep the private key fixed across renewals so the DANE TLSA "3 1 1"
# record (public-key digest, published in the zone) stays valid.
extraLegoRenewFlags = [ "--reuse-key" ];
};
};
# The mail cert is owned group=acme (SNM adds postfix/dovecot); Caddy serves the
# MTA-STS endpoint from the same cert, so it needs to read the key too.
users.users.caddy.extraGroups = [ "acme" ];
# MTA-STS policy endpoint, served by Caddy (same web server as control's docs).
# The explicit `tls cert key` points at the lego-issued mail cert (which carries
# mta-sts.cnx.email as a SAN) and disables Caddy's automatic ACME, so no extra
# issuance happens and the DANE TLSA key stays stable. Only :443 is opened.
services.caddy = {
enable = true;
virtualHosts.${mtaStsHost}.extraConfig = ''
tls /var/lib/acme/${fqdn}/cert.pem /var/lib/acme/${fqdn}/key.pem
root * ${pkgs.writeTextDir ".well-known/mta-sts.txt" (builtins.readFile mtaStsPolicy)}
file_server
'';
};
# DKIM private keys are generated on first start under this dir. They're
# regenerable (rotate + republish the TXT), but declaring the path as clan
# state lets a borg client back it up to avoid a needless DNS round-trip on
# restore. Wiring mx1 into the borgbackup instance is a separate step.
clan.core.state.mail-dkim.folders = [ config.mailserver.dkimKeyDirectory ];
}
@@ -0,0 +1 @@
../../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../../sops/machines/mx1
@@ -0,0 +1,22 @@
{
"data": "ENC[AES256_GCM,data:Lkwniu1Pmu+kGb94ncTteN/CkBYE47+UJKRSij5APKyPa6wQkc33S00WVLSXkG6I/XGeRUAXxNpM7G3WqmkluBtJnuYdQ3+nLVdarDy015Zu207LbpaYBuDyMU4e5pxH2ekIGnyL7diDI/3/GG+fVrO4xxdrFPWaB0YDcD8+5mtpshUqa+4rDrU9CSikuRo+dkAHveX1+MFfpF0aJmRw2MMwXO4=,iv:LIOftLZQ63yEPJ5S08t97jGGkSUK1LxSMnvy9lEm070=,tag:+yYbq9RU91KB+6r9eC9/fg==,type:str]",
"sops": {
"age": [
{
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBDS0Fra0tVdHIwREVYRC9l\nZFNwek0zMWYvTkRBT1RKR3p1bTN4SmZncGxVClRoOFRNcklEeWllMW4wdzRCaXNX\nbWtNYUJNM2dGaDZqVGtVY0twZVhEY1UKLS0tIFUxZ1QyMWNLRGJrYkJTbTJCT0Fy\nd2xjbWthVG1JUW5XZmVSK3lWc2NLRGMK6/g42P7ZvAk+t2GmZammNxTLFMudK+Qv\nZt3YUF0+EYKlEENgtjku7SSZ7UElQ5NZNrldlk8ZYLIVTul+8XuvrQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1l5hw95p5h4sthrgn0usms9yfkwwmcvv34tjgrtv9s4e6x39chacshgxavs",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBJQ1ZMRzEreERmM1V3OFhC\ncEhZYjZ2ZGFWc1kveUpLM0MyYlB6S25jR3cwCkhjaTBpS3dHZG51V0RmN1ZVcmRk\nYnIwSlRKVXVoQ1NqdFQ1M2lPazEyRTgKLS0tIGRFTFRaNUMvdU5Fc3A2Nk10NWVw\nWlZOKzJLUDNwTXY4dm9uZTY0cWg4aDgKYDIEQHgomMuJFHHvtt3BbN4tuBiEcboc\nH6K4NmnDE6wMa/1EGGHrjCFb+tUdZSL/zgf5uVOXnXA6d9BEeCGLNQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1yubikey1qd859y9ehz2ya8j2cftwrtmdeqhuk7r7yc52zp64wpff6068gwrac3q6nsa",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFWcTVydyBBNk9Od2FL\neEV2Myt0eGI5emF6TlRGS3A3RGNHZ3o3WCtQUDdpYllCTFY1RwpyU2xyNkFIVmhV\nY0g1NDJHRy95SzRrTnRPTGc5ZXdZYTZtMyswWkZIUVdnCi0tLSBWU0Fsak42TUF1\ncndtNFpDUjVhb3RpeWU1eDd0UGtnUTJXeHNiVmNtUTFZCvUQDFKntKn+mZSuDR00\niTu/TdmOu7s89JvirWtFavSZhBzOoW8eXdX/SJCLVy08wdTjb2ksqDdxn0ceiqgL\nLOU=\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-06-18T07:11:40Z",
"mac": "ENC[AES256_GCM,data:8hd2LItBnmG06HnUQ0avOOnbA4+JAPkJf/Wneqo/YexT/saEK+roa+iMkL2DxtcZK5UahPkJ+wT4q3MfNkOnrdbHmQHUUIkDSX+RxLatMtOseYxg8h9wT6MZehuxkRpN4Y9RlpQu/+l3zKQNbXfsfUj6i91fjH6lcxSYPHNO8ug=,iv:T8hYVO0houthJhFmV2UoOK8d5Z3sevv1pRvhdf5qaXk=,tag:2Q5bi8y+hq1TSkQ45Cmf5Q==,type:str]",
"version": "3.12.1"
}
}
@@ -0,0 +1 @@
../../../../../../sops/users/berwn
@@ -0,0 +1 @@
../../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../../sops/machines/mx1
@@ -0,0 +1,22 @@
{
"data": "ENC[AES256_GCM,data:SZIlr7nzcs5EUMVUXQ1KJhrd9JZY7ZR9EUpJ9Eygkpd2sAyMNtV0jMFyfW82PyvLP9/bqXf/5BUR98NuwvMsRmLrb4emEraNJH4qVfS/4s0kXySIJeA6XeMHB7GSuxoh5K/3pUR4tbdFSCM=,iv:grRynVFombEdRp0LfmPHIximhh4rlbQTjqJCjbGhRlw=,tag:qZZCnN63clfKKsb/WQNkbw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFWUFGNDBLM3VCMFh2Sjc2\nY1FkOVhta3REK1Y0MUZlY1IxTHorRCtuOWxJClJqK2hjT2p0VFBEeE1xd1JDUTBk\nNzR4eG5zNGJnalVGb2ovRHVqUUpla1EKLS0tIEJMYTU3MlVKOHdvaUhhM2dsWEoy\nRVh6SHc2cVdTTW4xZlhpc3U1aTRLT0UK+WYlVCCJ1bUsuF/vy6+mSU0gpM8FGHDE\n9HfyAHPZLR30ZtkRHSq8LQ137hxmBKSUjv5ztyHc781tLkx9j3lRwQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1l5hw95p5h4sthrgn0usms9yfkwwmcvv34tjgrtv9s4e6x39chacshgxavs",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBYMVlBOWVRRitmcVBINGJK\nakxWZEdNeHh5QWIrSHJmVzhDRDl6MUhhdUVnClRFcXZNTGYrazNxWG5UVi8vVWlu\nK3NaN004MVhrR1R2YVpZcnArRmVVUjAKLS0tIG90aXpEVHZLVDMrQWhYMHg2bWZj\nN3B2SmpHTnEzNFZ4cXBmM3paRmhCMjAKZa1OlhBcW+4J0sR+aWv0lkLqDh+73Gay\nKl6ltN1EQI7ISH2azQFahzoz6XV8cyYUHAQaHaYZuyCZNb/XbG9VmQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1yubikey1qd859y9ehz2ya8j2cftwrtmdeqhuk7r7yc52zp64wpff6068gwrac3q6nsa",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFWcTVydyBBcHF1OUdP\nQk1VVTdla3ZSc1VUa2MrT0IwanpmRWNsVHlYc21jM3dXNkg4RwpSSENkYUVJa2lS\nTk1pb3NhNkVPU3hPeVVPUE1MOGxTNHpsYXJGSVRQVmVBCi0tLSBFVE96Q0luZ1Jv\nODd2eUl1aWhOR08wUFpPZHFkZEd5MktNU1R2ajBoTERzCrmM40bnvt2iHQERfrN9\n8724ZmXn4YpAiN/FwKpPJ+iF2atpPDbUb2PFG2s6s2kJISMCrpoblZHBTYbG322M\nGgw=\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-06-18T07:11:36Z",
"mac": "ENC[AES256_GCM,data:bgu8Pv90Ljc1e6uG5oSDY4IwM85ZqiEQ2Uy0xt6Iu6/f8G/K/sh/+N3ZwcXAV3cpAsa5Cde8vHO8JgmAmcYN1frEgb9+rJyfD/sVvIEmy++WgLKIoUngcRyxX7tk+lLGv03tF09HcfoA+P4d5zNxZq5hR20jkT+2t3Y0nw7XN1M=,iv:gbvg2/5SBg3a2H/bvMFuF1bWSVHTppP59s7ivthe6TI=,tag:6zSYNSG2qHKv9YMLVyvSbA==,type:str]",
"version": "3.12.1"
}
}
@@ -0,0 +1 @@
../../../../../../sops/users/berwn
@@ -0,0 +1 @@
../../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../../sops/machines/mx1
@@ -0,0 +1,22 @@
{
"data": "ENC[AES256_GCM,data:FYx5Vll+asvRpC56Wk7ZAB6tdGabWIR25fRD/fw5aTgLz3+wU6K3RkDy,iv:rPD4rRduxodp9e6PGSD9V3zDPaTTAxeSNpC3Q/Umi/k=,tag:DiIahCPXn/DOSgJunryXHg==,type:str]",
"sops": {
"age": [
{
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBrVGRuNnBYL2NJUFFFZWho\nVDM1d1YxUTJLT2lma1NuYWtaNEVpNnUxV0NrClBOb2tBR0Z2a2lMMTNMeStTK2ln\nZjZ1cEFRZjRTTld5M2pNV1c4aHZlZzAKLS0tIHdRNnB2R1E3M1hVamtJWk9wWE1Y\nWG1yKzF5QkFQbWZZUTVObS9jcUtQVmMKdj+SEz7TcCe5Fk5B65EPBvGC0OWVafax\nws4qgi3O4CNQkVoOx4Jq6aWF0I7a0dR0mG2ERPeVUJKt6Kzap/KJ7w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1l5hw95p5h4sthrgn0usms9yfkwwmcvv34tjgrtv9s4e6x39chacshgxavs",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBFbXhLYUhXSzZZNm40OUdJ\nZ0x3VTB3WDBPSzQ3RU03dS94RmNRMEM1ekg4Cmt0SDMxY1hsL0t6RXRZV29ObWJt\nUFFiZEFFL3p1RytVdXpXeU9sVlU1THMKLS0tIHptRlZqVS9NU3RydGJzbkZ4a05P\nVkRUNDErdklTV2ZWeVNIdVRmVTdxSWsKTH3olIgtm+rM7CsKeVq3GVYk+Y2JcoZ2\nE6/KdwBOsRDFvQpw6vuNVUD0LDDWh0T3+V4+3f9YBn1qdqWHFDA52Q==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1yubikey1qd859y9ehz2ya8j2cftwrtmdeqhuk7r7yc52zp64wpff6068gwrac3q6nsa",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFWcTVydyBBODdOY293\nWXZYWUNuOUxwcUh1Y05VcEFrQTZ3VnJiT3ArN1JFZTZLOFl6RApib2RnN3JnS3ZQ\nbFhFakNiVFdsR2tRa3JUVmJaZzJTY1ZOaUcyQlMrcm5jCi0tLSA1Z04vRTJ4NzVC\ncG5XYW1ZRUd4QkJMTGlJbEUwMmpTSnk4RXBTclRrOGN3Cj7t6HYbS8kdKaIYWMms\n74vQn/HJvnYnIwUqEsf3z8QzTfsXtPB4ueA1NjftyvlKMRozuKxb+ULFv6YkZNzX\nxvo=\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-06-18T07:11:36Z",
"mac": "ENC[AES256_GCM,data:Jl/TzaI0c0v3JwJHDSvDUZEGKpgMGgFD1UoWC7qbc6LC+vVOpDjcm3WlfXfy2ljHpaqd74dl2kvy2ra75htI5YuLAisTqrPXhm+8km4tLzQzZOHz3JRIW+0fgnVC8z+GFrOHsz8CCc9SCX03HTyOlugvq0PD2sp4hz6Wgmp4cMc=,iv:Q/qUtb968vhyyRQyDYIP3WE49GLWExkmbarSZz9jPhM=,tag:M0crVWRGl2xvrAJEZvfFaA==,type:str]",
"version": "3.12.1"
}
}
@@ -0,0 +1 @@
../../../../../../sops/users/berwn
@@ -0,0 +1 @@
../../../../../../sops/groups/admins
@@ -0,0 +1 @@
../../../../../../sops/machines/ns1
@@ -0,0 +1,22 @@
{
"data": "ENC[AES256_GCM,data:GYJB+ctvohxqGBWjaWFWXQ0Mr8yPoYH/e1oNgJvIKHYtu7e7QIPyReFgYI/AcK4ZbwDZs6n+eYXv/hpw8IvPdyDD5f2u6fKcHrg2SYiF/uSJtfQXkgppy2HjpBnYC392fMe/QBEqtA+TZA==,iv:eQvNHROk/7fdQ1wESRLXF00A8KZ5n75OC85TMTt9u48=,tag:j6BS/8z144iewR9yiV8RZw==,type:str]",
"sops": {
"age": [
{
"recipient": "age1fanu282vm7njjweqhrpcfcwpttuhce8js4tsyfry98l0neaqpewqs5s7nt",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2TVM1UndkNEF0YjhqUGM1\nTDltckpEVnlmQ1BQNUxycWV6RWR3S1pOT2xrCkY4Qld3d1NxeFNGdUQ2Uzd6bTFR\nRllnZ1JEaHV6NmtaUEFrQjB4QUpvTTgKLS0tIDhHclZZbGtyU2J2clo3SXYwK25w\nNlA3dVF2T3VJZUp4ZVZHSE1qTHp1eDgK0bpZc/QzBtxTpuK63t3iICbh/ppVXFgz\nBkye9movY2s/+OT6KTl8+CW7WYuyWTWPOTuzHmuj+IZPDJuhmxG2WA==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA2OEJtLzVyTHBKVmo3eUZh\ndEIxWnFiN0xoMDRZMVBYL1pITTYzY1Y3bGtVCm8xMEhRc25Xb2tLUjU1VU9PU3dL\nRG9VRFhVSWlmSCsyV2x0MVZsZjcxbmMKLS0tIGxTeDhrWjhhUVQwUjZVQUs1VU5N\nQzJSSmYxWWZUdjUxdG5mRGVHWHpLT2MKhHFZa+qAY3UCOJMWXlzqcV0w2GhY3gbY\nKKbUB8e+dpkoNsS2GPPASS7xYm9LNPn5R4+GzCBsB3HqvYFethQiaw==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1yubikey1qd859y9ehz2ya8j2cftwrtmdeqhuk7r7yc52zp64wpff6068gwrac3q6nsa",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFWcTVydyBBOG1RemV2\nWHA2aHoyZGR2d0I1WThVNmlnUklWWndjbGZWNTRoUjVIVDJTSwpabXpqMW9wUWVC\nUUlyN0VlYThkVXpKMFRlMjBsbTBCUUFEYUJSRjdMbEVrCi0tLSBxc1cwalZ3aUtp\nNTUwVTR2NjFGbUl4MzNQdStwQzR4WDQvc0txSjU5a0I4CsupjMhWkfV7N3aLXv4y\n6tjb3ukWuJLWy1x5hHllDiQU7JXjKhnxonqRxVlyrnAxybzQVmY+8ndaDCu4jOm1\npPI=\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-06-18T07:11:40Z",
"mac": "ENC[AES256_GCM,data:4olEQSz2jb25fwWMp0IdXkrqY4NYPQrKAizpvB7D/W8sq6tHKcR1JN8umrwBNk2pcDP119DXHK38b32+UzhYYVkDFGWfZaIc8sj+FY1u7394zui8JcOOiDUxxiyo+R9OeevC4xOV8gPWnAhpBRBnNHvhiU9daBahRmYf7mdubag=,iv:PsSmSS0qN0AJ9q7JfuidoL9ngu6nnkgfVSu0PGOwB3U=,tag:f7E9GTb+AHSDFyc4pJXXiw==,type:str]",
"version": "3.12.1"
}
}
@@ -0,0 +1 @@
../../../../../../sops/users/berwn
+1
View File
@@ -0,0 +1 @@
../../../../../sops/groups/admins
+1
View File
@@ -0,0 +1 @@
../../../../../sops/machines/mx1
+1
View File
@@ -0,0 +1 @@
../../../../../sops/machines/ns1
@@ -0,0 +1,27 @@
{
"data": "ENC[AES256_GCM,data:77a5VqyU7Q3Gg5ngvFK6bC+0n4pg6ndpFQWMqM3AOdjjlZfoIwHjEXZzSO8=,iv:Kw/AkO3PpHeIprdqea+ZOLrmxwr6/mDSF/5veg97sb4=,tag:brxb6U0D2Dw7Cq/tRGSh2A==,type:str]",
"sops": {
"age": [
{
"recipient": "age1fanu282vm7njjweqhrpcfcwpttuhce8js4tsyfry98l0neaqpewqs5s7nt",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBOOC9WRENVd2FoMkQrMHpa\nNFI4YmtUQkZITmF2eEV3UnBRTWpIR25ROTFZClNaM1U1eU4xYTN4OVd0SDg2Ylp0\nYTFZSEZMaVRrU3B5eWVKOWVsMVR1QkkKLS0tIDJmc2gwb1IwbWZCejdBa3d5L0xh\nNUlWbm1IbTdyTVNFK0FEMHpERDNmeDAK+9FDHeJpb6Bh+utFfLRhOrtdjzx3eJQt\nPhM6Mf1K+kFV37iMj5yOFLq51tiyen5J53ExS6ppe0XxYJfKKtb2UQ==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1hlzrpqqgndcthq5m5yj9egfgyet2fzrxwa6ynjzwx2r22uy6m3hqr3rd06",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA5dEZLaGdlbnZZRFFhSWVs\nOE1RL2pLcW5rOThaSGszUnJGTloxR3FGUEJBCmluR2xyMWdlYXRoWVFmTnVTYWFp\ndHByemJyRFNqVG80TElGSWhjbDM5K0UKLS0tIGI4R0JRdFNYa011UU9sL24xT0sx\nQVl5Q25iWW9ET3YzRUVTa3RBL09UbTQKDJ/ikDMDP3ATh8lahqmvys4gNXj7gfR5\nTMG+DMLoqdOekd+iUIU3Wb/eGg2MsUXdN4UjyTOI4hOlN03Iofhb1w==\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1yubikey1qd859y9ehz2ya8j2cftwrtmdeqhuk7r7yc52zp64wpff6068gwrac3q6nsa",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IHBpdi1wMjU2IHFWcTVydyBBN0lIa3Rj\nc2g5L2syenRZdjlyR202RjROdmpVdWVmajBHMXc2Rnovcy9pZQoxSzBwQlVKVlE4\ndUs3TzhIUmRkSjU4cER1S0wwcTE4a1U2aFVKNVZkM2RnCi0tLSB3bE0ydC93U2FG\nWmpiME1FcEJZRmw1bXI5NzVaZE1aREdJVFJCM2FWZGNZCj0dJhb69fNDEfNJUnjX\nKcWKH0kAlttmduIdnN1j9VHuNtfcmKJncCV5WYwfzwErsOc6HB0pTw6bnJBUbtbO\nmiQ=\n-----END AGE ENCRYPTED FILE-----\n"
},
{
"recipient": "age1l5hw95p5h4sthrgn0usms9yfkwwmcvv34tjgrtv9s4e6x39chacshgxavs",
"enc": "-----BEGIN AGE ENCRYPTED FILE-----\nYWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBmaTBwcmg2OEFveEQwSm9v\ndFc2V0tlY0lVejdjeGNTWGwxeExBcVljd0hjCkR4cnlndG50RGZJQm1HdTdqUmRt\nT3RzamlBeHYwcEpVMHhEKzV0eE9VSW8KLS0tIGpBdnpCdU5FSFRZSW83aWpocUJD\nWkQvYTdrb1lWTTlIZG9DK0VOTlAvYk0KG0zbImkrDloCXN/XPKR5uWsL39CagOnk\n8qZTtQepja4RQAagOoGanybY0OK8sCCK/4NSsetaProyuXGQLBEWDg==\n-----END AGE ENCRYPTED FILE-----\n"
}
],
"lastmodified": "2026-06-18T07:11:36Z",
"mac": "ENC[AES256_GCM,data:K3gMR4fz+/H9SJ3zyOk1lyskn+2eqgy28IltRKH3gSGJHLqgFhkYJZ3lSwvpAv9uDC2PQ8XFQMrM2SHAjhgAbyR5TEN/TZHk2RSWbjsj7m0r7TlCwawyxesU/+ih/pOu6XLG13BPakNEfc+ak+DiG+SfdpMpgdUEHX+ZEPgQC+I=,iv:JHLZEwtzth1nsocyrVw+iGOPbOZEBvryaxD8zW+p72M=,tag:as0o1kT5pweVRXHxP0QRrg==,type:str]",
"unencrypted_suffix": "_unencrypted",
"version": "3.12.1"
}
}
+1
View File
@@ -0,0 +1 @@
../../../../../sops/users/berwn