From d5969ca92336e889fbc363c4af56424849bb4544 Mon Sep 17 00:00:00 2001 From: Alejandro Angulo Date: Sun, 16 Jul 2023 10:53:02 -0700 Subject: [PATCH] Refactored how SSL certs are configured for nginx Made a separate ACME module to handle requesting certs from multiple machines. Right now, the module only supports exactly one wildcard cert. It might make sense to have cache.kilonull.com use a cert specific to its subdomain rather than also requesting a wildcard cert (or maybe the nginx on its host shouldn't care about TLS and it should be node's responsibility). --- modules/nix/default.nix | 2 +- modules/security/acme/default.nix | 56 +++++++++++++++++++++++ modules/services/adguardhome/default.nix | 47 +++++++------------ modules/services/nextcloud/default.nix | 12 ++++- modules/services/nix-serve/default.nix | 19 ++++++-- secrets/cf_dns_kilonull.age | Bin 566 -> 687 bytes secrets/secrets.nix | 3 +- systems/x86_64-linux/gospel/default.nix | 12 ++++- systems/x86_64-linux/node/default.nix | 18 +++++++- 9 files changed, 128 insertions(+), 41 deletions(-) create mode 100644 modules/security/acme/default.nix diff --git a/modules/nix/default.nix b/modules/nix/default.nix index c8be95e..a603959 100644 --- a/modules/nix/default.nix +++ b/modules/nix/default.nix @@ -7,7 +7,7 @@ }: with lib; let cfg = config.aa.nix; - selfHostedCacheHost = "http://192.168.113.69/"; + selfHostedCacheHost = "https://cache.kilonull.com/"; in { options.aa.nix = with types; { enable = mkEnableOption "manage nix configuration."; diff --git a/modules/security/acme/default.nix b/modules/security/acme/default.nix new file mode 100644 index 0000000..740eea9 --- /dev/null +++ b/modules/security/acme/default.nix @@ -0,0 +1,56 @@ +{ + options, + config, + lib, + pkgs, + format, + ... +}: +with lib; let + cfg = config.aa.security.acme; +in { + options.aa.security.acme = with types; { + enable = mkEnableOption "Automatic Certificate Management Environment (ACME)"; + useStaging = mkOption { + type = bool; + description = '' + Use the staging environment (use when configuring for the first time to + avoid being locked out). + ''; + default = false; + }; + domainName = mkOption { + type = str; + description = "The domain to request a wildcard cert for."; + }; + dnsCredentialsFile = mkOption { + type = path; + description = "The path to the credentials file for the DNS provider."; + }; + }; + + # Only supports exactly one wildcard cert using Cloudflare (only use case I have) + config = mkIf cfg.enable { + security.acme = { + acceptTerms = true; + defaults = { + email = config.aa.user.email; + group = "nginx"; + server = mkIf cfg.useStaging "https://acme-staging-v02.api.letsencrypt.org/directory"; + }; + + # Wildcard cert + certs."${cfg.domainName}" = { + group = "nginx"; + dnsProvider = "cloudflare"; + # Private network resolves *.kilonull.com to private servers but `lego` + # (acme client under the hood) needs to find the cloudflare nameservers + # to determine the correct zone to apply changes in. Use cloudflare's + # own DNS to make `lego` happy (will resolve names to a public IP). + dnsResolver = "1.1.1.1:53"; + credentialsFile = cfg.dnsCredentialsFile; + extraDomainNames = [("*." + cfg.domainName)]; + }; + }; + }; +} diff --git a/modules/services/adguardhome/default.nix b/modules/services/adguardhome/default.nix index e9dcc9b..6cecd6e 100644 --- a/modules/services/adguardhome/default.nix +++ b/modules/services/adguardhome/default.nix @@ -11,6 +11,14 @@ with lib; let in { options.aa.services.adguardhome = with types; { enable = mkEnableOption "adguardhome"; + acmeCertName = mkOption { + type = str; + default = ""; + description = '' + If set to a non-empty string, forces SSL with the supplied acme + certificate. + ''; + }; }; config = mkIf cfg.enable { @@ -26,37 +34,16 @@ in { services.nginx = { enable = true; recommendedProxySettings = true; - virtualHosts."adguardhome.kilonull.com" = { - forceSSL = true; - useACMEHost = "kilonull.com"; - locations."/" = { - proxyPass = "http://127.0.0.1:3000"; + virtualHosts."adguardhome.kilonull.com" = + { + locations."/" = { + proxyPass = "http://127.0.0.1:3000"; + }; + } + // lib.optionalAttrs (cfg.acmeCertName != "") { + forceSSL = true; + useACMEHost = cfg.acmeCertName; }; - }; - }; - - # So that nginx has access to the ACME certs - users.users.nginx.extraGroups = ["acme"]; - - age.secrets.cf_dns_kilonull.file = ../../../secrets/cf_dns_kilonull.age; - - security.acme = { - # NOTE: Uncomment line below when testing changes - # defaults.server = "https://acme-staging-v02.api.letsencrypt.org/directory"; - acceptTerms = true; - defaults.email = "iam@alejandr0angul0.dev"; - - # Wildcard cert - certs."kilonull.com" = { - dnsProvider = "cloudflare"; - # Private network resolves *.kilonull.com to private servers but `lego` - # (acme client under the hood) needs to find the cloudflare nameservers - # to determine the correct zone to apply changes in. Use cloudflare's - # own DNS to make `lego` happy (will resolve names to a public IP). - dnsResolver = "1.1.1.1:53"; - credentialsFile = config.age.secrets.cf_dns_kilonull.path; - extraDomainNames = ["*.kilonull.com"]; - }; }; networking.firewall = { diff --git a/modules/services/nextcloud/default.nix b/modules/services/nextcloud/default.nix index 69a6685..5463605 100644 --- a/modules/services/nextcloud/default.nix +++ b/modules/services/nextcloud/default.nix @@ -11,6 +11,14 @@ with lib; let in { options.aa.services.nextcloud = with types; { enable = mkEnableOption "nextcloud"; + acmeCertName = mkOption { + type = str; + default = ""; + description = '' + If set to a non-empty string, forces SSL with the supplied acme + certificate. + ''; + }; }; config = mkIf cfg.enable { @@ -47,9 +55,9 @@ in { }; # nextcloud module configures nginx, just need to specify SSL stuffs here - services.nginx.virtualHosts.${config.services.nextcloud.hostName} = { + services.nginx.virtualHosts.${config.services.nextcloud.hostName} = mkIf (cfg.acmeCertName != "") { forceSSL = true; - useACMEHost = "kilonull.com"; + useACMEHost = cfg.acmeCertName; }; networking.firewall.allowedTCPPorts = [80 443]; diff --git a/modules/services/nix-serve/default.nix b/modules/services/nix-serve/default.nix index d8c24b2..894ef18 100644 --- a/modules/services/nix-serve/default.nix +++ b/modules/services/nix-serve/default.nix @@ -19,6 +19,14 @@ in { type = str; description = "The subdomain to use."; }; + acmeCertName = mkOption { + type = str; + default = ""; + description = '' + If set to a non-empty string, forces SSL with the supplied acme + certificate. + ''; + }; }; config = mkIf cfg.enable { @@ -38,8 +46,8 @@ in { nginx = { enable = true; - virtualHosts = { - "${cfg.subdomain_name}.${cfg.domain_name}" = { + virtualHosts."${cfg.subdomain_name}.${cfg.domain_name}" = + { serverAliases = ["${cfg.subdomain_name}"]; locations."/".extraConfig = '' proxy_pass http://localhost:${toString config.services.nix-serve.port}; @@ -47,13 +55,16 @@ in { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ''; + } + // lib.optionalAttrs (cfg.acmeCertName != "") { + forceSSL = true; + useACMEHost = cfg.acmeCertName; }; - }; }; }; networking.firewall = { - allowedTCPPorts = [80]; + allowedTCPPorts = [80 443]; }; }; } diff --git a/secrets/cf_dns_kilonull.age b/secrets/cf_dns_kilonull.age index 315f0911b0d13d15b1f27c8532176ef4643213d9..249d5cea4946d5354183a1cba9364ccb7c221558 100644 GIT binary patch delta 655 zcmdnSvYvHE0?aFLUD11 zZfc5=si~o*LU5#CuwS5pSyHH5N{NBCcU8ELXGXS#N4>LAWN=Pdkb9^{g=J1sTCq>5 zXNqyEr$w$Sm%f*UZ-!s8UsAbyiLtw%TX;~RQ@(3ykfTvbW@%Wct7o~hUsiZZX_%=6 z$hv~eGTj0rQ!|B7PgnP0?XQsbn&R1fcp zloGII=?=Q-MX8C!sS2stp?V7Dd6vOkA%QObX&LVR`Qd>E&Ox3b5h+#11;ItBp~k); ziB9^#5l*EAfkxiWOrM^*-80mWjos-hrjwCR}F0C5|D%-t{ZO zgVk5vn7M81ldi5uNUn!zN3FyN@P+=m|MDWu(?NRlA&pstAS}`Sdxi> zBbP^hSW>8dd2*hmn}149q`7g5S7BaMwyBe6P)ccjYK293W{PK2K!mot>%@} zRfPe0;U)PlMHW8gUSY|F6(wF_sfCdNWx?r=5!oRgu11y-srsRgTs|pYS+1#}IT?uo zPUSf%o_Qf90UntJ$q_+*PW~AM?#_wMY5rl!7KP#ET)K7&j!C-dMX8C!sS37HReCmg zT*+Rk;pIW*#pZ5irTQ5a{yt7l^^RpF`l;G38Ib{LVWx#4=}x{Gp04F-{^r{LepSiI z?h!u5Y5Jx)Nlqn6;qF|%8CjVrS^5PP#YsMHu0G+e2Fcpl9{6~-KCVN=^Hga;^KW{oWpNwMufzy|Fn_5heo4dsYHgEQMZ