{ flake-inputs, pkgs, config, lib, ... }: { options = { # Add a custom per-host option to enable HSTS services.nginx.virtualHosts = lib.mkOption { type = lib.types.attrsOf ( lib.types.submodule ( { config, ... }: { options.enableHSTS = lib.mkEnableOption "HSTS"; config.extraConfig = lib.mkIf config.enableHSTS '' add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; ''; } ) ); }; }; config = { # Certificate settings security.acme = { defaults.email = "tm@tlater.net"; acceptTerms = true; certs."tlater.net" = { extraDomainNames = [ "*.tlater.net" "tlater.com" "*.tlater.com" ]; dnsProvider = "porkbun"; group = config.users.groups.ssl-cert.name; credentialFiles = { PORKBUN_API_KEY_FILE = config.sops.secrets."porkbun/api-key".path; PORKBUN_SECRET_API_KEY_FILE = config.sops.secrets."porkbun/secret-api-key".path; }; }; }; users.groups.ssl-cert = { }; # Back up the SSL certificate, just in case services.backups.acme = { user = "acme"; paths = [ "/var/lib/acme/tlater.net" ]; }; systemd.services = { nginx.serviceConfig.SupplementaryGroups = [ config.security.acme.certs."tlater.net".group ]; # Don't attempt to retrieve a certificate if the domain name # doesn't *actually* match the cert name # # TODO(tlater): Set up pebble to retrieve certs "properly" # instead "acme-tlater.net".serviceConfig.ExecCondition = let confirm = ''[[ "tlater.net" = "${config.services.nginx.domain}" ]]''; in ''${pkgs.runtimeShell} -c '${confirm}' ''; }; sops.secrets = { "porkbun/api-key".owner = "acme"; "porkbun/secret-api-key".owner = "acme"; }; serviceTests = let testHostConfig = { config, ... }: { imports = [ ./. ../../modules/serviceTests/mocks.nix ]; networking.firewall.allowedTCPPorts = [ 80 443 ]; security.acme.certs."tlater.net".extraDomainNames = [ config.services.nginx.domain ]; services.nginx = { domain = "testHost"; virtualHosts."${config.services.nginx.domain}" = { useACMEHost = "tlater.net"; enableHSTS = true; locations."/".return = "200 ok"; }; }; }; in { testNginxSSL = pkgs.testers.runNixOSTest { name = "test-nginx-ssl"; node.specialArgs = { inherit flake-inputs; }; nodes = { testHost = testHostConfig; client = { pkgs, ... }: { environment.systemPackages = [ pkgs.curl ]; }; }; testScript = '' start_all() testHost.wait_for_unit("nginx.service") testHost.copy_from_vm("/var/lib/acme/tlater.net/", "certs") client.copy_from_host(f"{testHost.out_dir}/certs", "/certs") res = client.succeed(" ".join([ "curl", "--show-error", "--silent", "--dump-header -", "--cacert /certs/tlater.net/fullchain.pem", "https://testHost", "-o /dev/null" ])) assert "strict-transport-security: max-age=15552000; includeSubDomains" in res ''; }; }; }; }