nixos/security: add acme through dns challenge

few side refactors of this:
- no more `dns.domain`, it all must rely on `fqdn`, prevents
  inconsistencies.
- also added an specific host `tuxcord-acmetest` that uses the key zone
  for `nix.tuxcord.net` to test certificate pulling.
This commit is contained in:
2026-05-03 23:12:06 +02:00
committed by ErrorNoInternet
parent 455753a192
commit e939c28c9c
12 changed files with 159 additions and 13 deletions
@@ -0,0 +1,19 @@
age-encryption.org/v1
-> ssh-ed25519 Wl2fDA sLlStq5Hzb2JNQubLtMk5/kyIp81aTyjUB/Ysv1gRR0
lTLWsvT5Oxk8ut/g+o9o0DMOIQVDmi9o4EO0fYeIToo
-> ssh-ed25519 zNC8SA G0XcS2gzF0RopI2DqkWaTYXwjUpkVtdrxSQ+p8bfE0w
pZVIg/1P8BbnpRfV5F0FG3xgLSiA8M+nosQ8iNeYmcU
-> ssh-ed25519 EiAAKw qWmO0IjKoUFVxbxFUx36JIhME2PU7lkD+3agKO7+6nA
yEIw8IzQmM8C9dZoPajtvdUOF5kJ/C+rtgLczcmP1bs
-> ssh-rsa eFi+Zw
O5XRvS+Y/1mm9nQ7IZmxEp7RmFjAH0OTKPkRTME7BybnePPZLL0l6wMP26hx88Nv
dOqdaS07Xb26EIgCS/4xCY4sPWZNEfAfnDVoF4/SNbmfbN0XpNpR981AWcxiTL35
Fngk0lPa1NtuUH4S4zTda21kXHE0zv2mYLNMuek8dTrUd2piC+Z0WJJdrG1LK0hN
dDuLzX/mNibNXDvYxyD6mtkO2S1wO9QL88ucNZptT29vcaD48EZM/SsAwgf3OoqH
kd7jSTTdZ/yk8ccTMiT5eskQ3ZZcqc7JaF+M2d88DP6LcSaJnNzyVSEMAHHfpoY1
/kHxZ88/ehwPDXrp0bL448jdPuWqPSerzCWyyZFbc8Jj6zRUtC5joL0Vq2Rqs+EH
rmKMfi1l2+utleGYfCyHI5/czsMhJ2jXLGPguWQQdixNtb/RWFw6DeRP9xdO9QJR
LkoAFgv0ykP+L+C6sA7bpJqIGNftl4x8OUQxrKtf3YQ8K2LhUZb23JPn4Ob/QXo/
--- W7eUDhB/RBUYV1gaM4ktPEOVU6l5IRgOoRDpKKpvAnM
O\òŒMúOýJä ÍÅ!ÒHûËÄpK¥Õ:gßÙ[6™M¡VÞÃbYæÌí;è€F'¡#ä7&Žs1„ 󨱉6º²5_nÂ’Ô.¼qñ'à˜ƒq®6Êf-ÖÄW™w›½ß#5³¯6ˆ¼ŸêªrÒð„Ð#œÅÄ€=4EÎËlžÕÿ[
+2
View File
@@ -12,4 +12,6 @@ in
"dns/tuxcord.test/tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users; "dns/tuxcord.test/tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
"dns/tuxcord.test/sub.tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users; "dns/tuxcord.test/sub.tuxcord.test.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
"dns/nix.tuxcord.net/nix.tuxcord.net.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
} }
+2
View File
@@ -28,6 +28,8 @@ in
./vm.nix ./vm.nix
]; ];
age.secrets.ntfy.file = "${self}/agenix/ntfy.age";
nix = { nix = {
package = inputs'.nix-super.packages.default; package = inputs'.nix-super.packages.default;
+1
View File
@@ -33,5 +33,6 @@ in
tuxcord-ca = mkSystem "tuxcord-ca" "x86_64-linux"; tuxcord-ca = mkSystem "tuxcord-ca" "x86_64-linux";
tuxcord-test = mkSystem "tuxcord-test" "x86_64-linux"; tuxcord-test = mkSystem "tuxcord-test" "x86_64-linux";
tuxcord-acmetest = mkSystem "tuxcord-acmetest" "x86_64-linux";
}; };
} }
+11
View File
@@ -0,0 +1,11 @@
{
acme = {
enable = true;
rfc2136.nameserver = "tuxcord.net";
};
dns.enable = true;
networking.fqdn = "nix.tuxcord.net";
time.timeZone = "Europe/Madrid";
}
+5
View File
@@ -4,6 +4,11 @@
./storage.nix ./storage.nix
]; ];
acme = {
enable = true;
useSelfDns = true;
};
dns.enable = true; dns.enable = true;
networking.fqdn = "tuxcord.net"; networking.fqdn = "tuxcord.net";
time.timeZone = "Canada/Eastern"; time.timeZone = "Canada/Eastern";
+1
View File
@@ -1,4 +1,5 @@
{ {
acme.enable = false;
dns.enable = true; dns.enable = true;
networking.fqdn = "tuxcord.test"; networking.fqdn = "tuxcord.test";
} }
+89
View File
@@ -0,0 +1,89 @@
{
config,
pkgs,
lib,
...
}:
let
cfg = config.acme;
inherit (lib)
mkIf
mkEnableOption
mkOption
types
;
inherit (config.networking) fqdn;
in
{
# we'll only support rfc2136 based challenges
options.acme = {
enable = mkEnableOption "" // {
default = true;
};
useSelfDns = mkOption {
default = false;
description = "Sets values of the self DNS if enabled, otherwise requires manual `rfc2136` nameserver and key values.";
};
rfc2136 = {
key = mkOption {
type = types.path;
default = config.age.secrets."dns/${fqdn}.key.age".path;
};
nameserver = mkOption {
type = types.str;
default = if cfg.useSelfDns then fqdn else null;
};
};
};
config = mkIf cfg.enable {
assertions = [
{
assertion = with cfg.rfc2136; nameserver != null && key != null;
message = "ACME needs rfc2136 parameters to work, consider using `useSelfDns` option.";
}
];
environment.persistence."/persist".directories = [
{
directory = "/var/lib/acme";
group = "acme";
user = "acme";
}
];
security.acme = {
acceptTerms = true;
defaults = {
email = "error@tuxcord.net";
reloadServices = [ "nginx" ];
postRun = ''
source ${config.age.secrets.ntfy.path}
${pkgs.ntfy-sh}/bin/ntfy publish -T recycle -t "${config.host.name}" "HTTPS certificate has been renewed"
'';
};
certs."${fqdn}" = {
dnsProvider = "rfc2136";
environmentFile =
with cfg.rfc2136;
builtins.toFile "dns-01-challenge.cfg" ''
RFC2136_NAMESERVER=${nameserver}
RFC2136_TSIG_FILE="${key}"
'';
extraDomainNames = [
"*.${fqdn}"
"${fqdn}"
];
inherit (config.services.nginx) group;
};
};
};
}
+1
View File
@@ -1,5 +1,6 @@
{ {
imports = [ imports = [
./acme.nix
./dns.nix ./dns.nix
./fail2ban.nix ./fail2ban.nix
./gitea.nix ./gitea.nix
+16 -10
View File
@@ -1,6 +1,16 @@
{ config, lib, ... }: { config, lib, ... }:
let let
agenixDnsDir = ../../agenix/dns + "/${config.dns.domain}"; cfg = config.dns;
inherit (lib)
mkEnableOption
mkIf
strings
;
inherit (config.networking) fqdn;
agenixDnsDir = "${self}/agenix/dns/${fqdn}";
agenixKeys = builtins.attrNames (builtins.readDir agenixDnsDir); agenixKeys = builtins.attrNames (builtins.readDir agenixDnsDir);
keys = map ( keys = map (
@@ -18,7 +28,7 @@ let
{ {
name = zoneDomain; name = zoneDomain;
path = config.age.secrets."dns/${filename}".path; path = config.age.secrets."dns/${filename}".path;
type = if zoneDomain == config.dns.domain then zonesub else subdomain; type = if zoneDomain == fqdn then zonesub else subdomain;
} }
) agenixKeys; ) agenixKeys;
@@ -34,11 +44,6 @@ in
enable = mkEnableOption "" // { enable = mkEnableOption "" // {
default = true; default = true;
}; };
domain = mkOption {
type = with lib.types; str;
default = config.networking.fqdn;
};
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
@@ -53,7 +58,8 @@ in
value = { value = {
file = path; file = path;
group = "named"; group = "named";
owner = "named"; owner = if config.acme.enable then "acme" else "named";
mode = "440";
}; };
} }
) agenixKeys ) agenixKeys
@@ -65,7 +71,7 @@ in
extraConfig = builtins.concatStringsSep "\n" (map (key: "include \"${key.path}\";") keys); extraConfig = builtins.concatStringsSep "\n" (map (key: "include \"${key.path}\";") keys);
zones = { zones = {
"${config.dns.domain}" = { "${fqdn}" = {
# grant "tuxcord.net" zonesub ANY; # grant "tuxcord.net" zonesub ANY;
extraConfig = '' extraConfig = ''
update-policy { update-policy {
@@ -74,7 +80,7 @@ in
)} )}
}; };
''; '';
file = "/var/dns/${config.dns.domain}.zone"; # need to put default stuff file = "/var/dns/${fqdn}.zone"; # need to put default stuff
master = true; master = true;
}; };
}; };
+7 -2
View File
@@ -1,4 +1,9 @@
{ config, lib, ... }: { config, lib, ... }:
let
inherit (config.networking) fqdn;
acmeEnabled = config.acme.enable;
in
{ {
services.gitea = { services.gitea = {
enable = true; enable = true;
@@ -8,8 +13,8 @@
lfs.enable = true; lfs.enable = true;
settings.server.DOMAIN = config.networking.fqdn; settings.server.DOMAIN = fqdn;
# settings.server.ROOT_URL = "https://git.tuxcord.net/"; ? would also depend on ssl status settings.server.ROOT_URL = "${if isHTTPS then "https" else "http"}://${fqdn}/";
settings.server.HTTP_PORT = 3000; settings.server.HTTP_PORT = 3000;
settings.service.DISABLE_REGISTRATION = true; settings.service.DISABLE_REGISTRATION = true;
+5 -1
View File
@@ -4,8 +4,12 @@ let
mkVhost = mkVhost =
attrs: attrs:
let
acmeEnabled = config.acme.enable;
in
{ {
forceSSL = false; # TODO: tweak per host forceSSL = acmeEnabled;
useACMEHost = if acmeEnabled then fqdn else null;
} }
// attrs; // attrs;