nixos/security: add acme through dns challenge
Check / Nix flake (push) Failing after 8s
Lint / Nix expressions (push) Failing after 10s

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
parent 4f248dc3cb
commit ed9ec1aecf
12 changed files with 130 additions and 13 deletions
Binary file not shown.
+2
View File
@@ -12,4 +12,6 @@ in
"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/nix.tuxcord.net/nix.tuxcord.net.key.age".publicKeys = [ tuxcord-ca ] ++ builtins.attrValues users;
}
+2
View File
@@ -28,6 +28,8 @@ in
./vm.nix
];
age.secrets.ntfy.file = "${self}/agenix/ntfy.age";
nix = {
package = inputs'.nix-super.packages.default;
+1
View File
@@ -33,5 +33,6 @@ in
tuxcord-ca = mkSystem "tuxcord-ca" "x86_64-linux";
tuxcord-test = mkSystem "tuxcord-test" "x86_64-linux";
tuxcord-acmetest = mkSystem "tuxcord-acmetest" "x86_64-linux";
};
}
+12
View File
@@ -0,0 +1,12 @@
{ config, ... }:
let
inherit (config.networking) fqdn;
in
{
acme.enable = true;
acme.rfc2136.nameserver = "tuxcord.net";
dns.enable = true;
networking.fqdn = "nix.tuxcord.net";
time.timeZone = "Europe/Madrid";
}
+4
View File
@@ -1,9 +1,13 @@
{ config, ... }:
{
imports = [
./hardware.nix
./storage.nix
];
acme.enable = true;
acme.useSelfDns = true;
dns.enable = true;
networking.fqdn = "tuxcord.net";
time.timeZone = "Canada/Eastern";
+1
View File
@@ -1,4 +1,5 @@
{
acme.enable = false;
dns.enable = true;
networking.fqdn = "tuxcord.test";
}
+87
View File
@@ -0,0 +1,87 @@
{
config,
pkgs,
lib,
...
}:
let
inherit (lib)
mkIf
mkEnableOption
mkOption
types
;
inherit (config.networking) fqdn;
cfg = config.acme;
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}"
];
group = config.services.nginx.group;
};
};
};
}
+1
View File
@@ -1,5 +1,6 @@
{
imports = [
./acme.nix
./dns.nix
./fail2ban.nix
./gitea.nix
+8 -10
View File
@@ -1,6 +1,8 @@
{ config, lib, ... }:
let
agenixDnsDir = ../../agenix/dns + "/${config.dns.domain}";
inherit (config.networking) fqdn;
agenixDnsDir = ../../agenix/dns + "/${fqdn}";
agenixKeys = builtins.attrNames (builtins.readDir agenixDnsDir);
keys = map (
@@ -18,7 +20,7 @@ let
{
name = zoneDomain;
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;
@@ -34,11 +36,6 @@ in
enable = mkEnableOption "" // {
default = true;
};
domain = mkOption {
type = with lib.types; str;
default = config.networking.fqdn;
};
};
config = mkIf cfg.enable {
@@ -53,7 +50,8 @@ in
value = {
file = path;
group = "named";
owner = "named";
owner = if config.acme.enable then "acme" else "named";
mode = "440";
};
}
) agenixKeys
@@ -65,7 +63,7 @@ in
extraConfig = builtins.concatStringsSep "\n" (map (key: "include \"${key.path}\";") keys);
zones = {
"${config.dns.domain}" = {
"${fqdn}" = {
# grant "tuxcord.net" zonesub ANY;
extraConfig = ''
update-policy {
@@ -74,7 +72,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;
};
};
+7 -2
View File
@@ -1,4 +1,9 @@
{ config, lib, ... }:
let
inherit (config.networking) fqdn;
isHTTPS = config.acme.enable;
in
{
services.gitea = {
enable = true;
@@ -8,8 +13,8 @@
lfs.enable = true;
settings.server.DOMAIN = config.networking.fqdn;
# settings.server.ROOT_URL = "https://git.tuxcord.net/"; ? would also depend on ssl status
settings.server.DOMAIN = fqdn;
settings.server.ROOT_URL = "${if isHTTPS then "https" else "http"}://${fqdn}/";
settings.server.HTTP_PORT = 3000;
settings.service.DISABLE_REGISTRATION = true;
+5 -1
View File
@@ -4,8 +4,12 @@ let
mkVhost =
attrs:
let
isAcme = config.acme.enable;
in
{
forceSSL = false; # TODO: tweak per host
forceSSL = isAcme;
useACMEHost = if isAcme then fqdn else null;
}
// attrs;