diff --git a/configuration/default.nix b/configuration/default.nix index f2d1615..54f17c2 100644 --- a/configuration/default.nix +++ b/configuration/default.nix @@ -27,7 +27,7 @@ ./services/wireguard.nix # ./services/starbound.nix -- Not currently used ./services/postgres.nix - ./nginx.nix + ./nginx ./sops.nix ]; diff --git a/configuration/nginx.nix b/configuration/nginx.nix deleted file mode 100644 index 935b5ac..0000000 --- a/configuration/nginx.nix +++ /dev/null @@ -1,77 +0,0 @@ -{ config, lib, ... }: -{ - services = { - nginx = { - enable = true; - recommendedTlsSettings = true; - recommendedOptimisation = true; - recommendedGzipSettings = true; - recommendedProxySettings = true; - clientMaxBodySize = "10G"; - - statusPage = true; # For metrics, should be accessible only from localhost - - commonHttpConfig = '' - log_format upstream_time '$remote_addr - $remote_user [$time_local] ' - '"$request" $status $body_bytes_sent ' - '"$http_referer" "$http_user_agent" ' - 'rt=$request_time uct="$upstream_connect_time" ' - 'uht="$upstream_header_time" urt="$upstream_response_time"'; - ''; - }; - - logrotate.settings = { - # Override the default, just keep fewer logs - nginx.rotate = 6; - } - // lib.mapAttrs' ( - virtualHost: _: - lib.nameValuePair "/var/log/nginx/${virtualHost}/access.log" { - frequency = "daily"; - rotate = 2; - compress = true; - delaycompress = true; - su = "${config.services.nginx.user} ${config.services.nginx.group}"; - postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`"; - } - ) config.services.nginx.virtualHosts; - - backups.acme = { - user = "acme"; - paths = lib.mapAttrsToList ( - virtualHost: _: "/var/lib/acme/${virtualHost}" - ) config.services.nginx.virtualHosts; - }; - }; - - systemd.tmpfiles.rules = lib.mapAttrsToList ( - virtualHost: _: - # - "d /var/log/nginx/${virtualHost} 0750 ${config.services.nginx.user} ${config.services.nginx.group}" - ) config.services.nginx.virtualHosts; - - security.acme = { - defaults.email = "tm@tlater.net"; - acceptTerms = true; - - certs."tlater.net" = { - extraDomainNames = [ - "*.tlater.net" - "tlater.com" - "*.tlater.com" - ]; - dnsProvider = "porkbun"; - group = "ssl-cert"; - 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 = { }; - - systemd.services.nginx.serviceConfig.SupplementaryGroups = [ - config.security.acme.certs."tlater.net".group - ]; -} diff --git a/configuration/nginx/default.nix b/configuration/nginx/default.nix new file mode 100644 index 0000000..dab0259 --- /dev/null +++ b/configuration/nginx/default.nix @@ -0,0 +1,22 @@ +{ lib, ... }: +{ + imports = [ + ./logging.nix + ./ssl.nix + ]; + + options.services.nginx.domain = lib.mkOption { + type = lib.types.str; + description = "The base domain name to append to virtual domain names"; + }; + + config.services.nginx = { + enable = true; + recommendedTlsSettings = true; + recommendedOptimisation = true; + recommendedGzipSettings = true; + recommendedProxySettings = true; + clientMaxBodySize = "10G"; + statusPage = true; # For metrics, should be accessible only from localhost + }; +} diff --git a/configuration/nginx/logging.nix b/configuration/nginx/logging.nix new file mode 100644 index 0000000..0c6a955 --- /dev/null +++ b/configuration/nginx/logging.nix @@ -0,0 +1,84 @@ +{ config, lib, ... }: +let + hostNames = lib.attrNames config.services.nginx.virtualHosts; + logPath = name: "/var/log/nginx/${name}/access.log"; + logFormat = lib.concatStringsSep " " [ + "$remote_addr - $remote_user [$time_local]" + ''"$request" $status $body_bytes_sent'' + ''"$http_referer" "$http_user_agent"'' + ''rt=$request_time uct="$upstream_connect_time"'' + ''uht="$upstream_header_time" urt="$upstream_response_time"'' + ]; +in +{ + # Extend the default configuration for nginx virtual hosts; we'd + # like to create log files for each of them, so that the prometheus + # nginxlog exporter can process per-host logs. + options.services.nginx.virtualHosts = lib.mkOption { + type = lib.types.attrsOf ( + lib.types.submodule ( + { name, ... }: + { + config.extraConfig = '' + access_log ${logPath name} upstream_time; + ''; + } + ) + ); + }; + + config = { + # Create directories for host-specific logs with systemd tmpfiles + systemd.tmpfiles.settings."10-nginx-logs" = lib.listToAttrs ( + map ( + name: + lib.nameValuePair "/var/log/nginx/${name}" { + d = { + inherit (config.services.nginx) user group; + mode = "0750"; + }; + } + ) hostNames + ); + + services = { + # Set the nginx-wide log format + nginx.commonHttpConfig = '' + log_format upstream_time '${logFormat}'; + ''; + + # Set up nginxlog to read the file and log format defined above + # for each virtual host + prometheus.exporters.nginxlog = { + enable = true; + listenAddress = "127.0.0.1"; + group = "nginx"; + + settings.namespaces = map (name: { + inherit name; + metrics_override.prefix = "nginxlog"; + namespace_label = "vhost"; + format = logFormat; + source.files = [ (logPath name) ]; + }) hostNames; + }; + + logrotate.settings = { + # Override the nginx module default, just keep fewer logs + nginx.rotate = 6; + + # Configure logrotate for host-specific logs + nginxVirtualHosts = { + files = map logPath hostNames; + + frequency = "daily"; + rotate = 2; + compress = true; + delaycompress = true; + su = "${config.services.nginx.user} ${config.services.nginx.group}"; + postrotate = "[ ! -f /var/run/nginx/nginx.pid ] || kill -USR1 `cat /var/run/nginx/nginx.pid`"; + }; + }; + }; + }; +} diff --git a/configuration/nginx/ssl.nix b/configuration/nginx/ssl.nix new file mode 100644 index 0000000..4cea508 --- /dev/null +++ b/configuration/nginx/ssl.nix @@ -0,0 +1,68 @@ +{ + 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}' ''; + }; + }; +} diff --git a/configuration/services/metrics/exporters.nix b/configuration/services/metrics/exporters.nix index 52c2a46..ea57c9b 100644 --- a/configuration/services/metrics/exporters.nix +++ b/configuration/services/metrics/exporters.nix @@ -1,9 +1,4 @@ -{ - config, - pkgs, - lib, - ... -}: +{ pkgs, ... }: let yaml = pkgs.formats.yaml { }; in @@ -68,28 +63,6 @@ in enable = true; listenAddress = "127.0.0.1"; }; - - nginxlog = { - enable = true; - listenAddress = "127.0.0.1"; - group = "nginx"; - - settings.namespaces = lib.mapAttrsToList (name: _: { - inherit name; - metrics_override.prefix = "nginxlog"; - namespace_label = "vhost"; - - format = lib.concatStringsSep " " [ - "$remote_addr - $remote_user [$time_local]" - ''"$request" $status $body_bytes_sent'' - ''"$http_referer" "$http_user_agent"'' - ''rt=$request_time uct="$upstream_connect_time"'' - ''uht="$upstream_header_time" urt="$upstream_response_time"'' - ]; - - source.files = [ "/var/log/nginx/${name}/access.log" ]; - }) config.services.nginx.virtualHosts; - }; }; # TODO(tlater): diff --git a/modules/default.nix b/modules/default.nix index 89f1752..9483c66 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -1,6 +1 @@ -{ - imports = [ - ./crowdsec - ./nginxExtensions.nix - ]; -} +{ imports = [ ./crowdsec ]; } diff --git a/modules/nginxExtensions.nix b/modules/nginxExtensions.nix deleted file mode 100644 index bd505d3..0000000 --- a/modules/nginxExtensions.nix +++ /dev/null @@ -1,59 +0,0 @@ -{ - config, - pkgs, - lib, - ... -}: -{ - options = { - services.nginx.domain = lib.mkOption { - type = lib.types.str; - description = "The base domain name to append to virtual domain names"; - }; - - services.nginx.virtualHosts = - let - extraVirtualHostOptions = - { name, config, ... }: - { - options = { - enableHSTS = lib.mkEnableOption "Enable HSTS"; - - addAccessLog = lib.mkOption { - type = lib.types.bool; - default = true; - description = '' - Add special logging to `/var/log/nginx/''${serverName}` - ''; - }; - }; - - config = { - extraConfig = lib.concatStringsSep "\n" [ - (lib.optionalString config.enableHSTS '' - add_header Strict-Transport-Security "max-age=15552000; includeSubDomains" always; - '') - (lib.optionalString config.addAccessLog '' - access_log /var/log/nginx/${name}/access.log upstream_time; - '') - ]; - }; - }; - in - lib.mkOption { type = lib.types.attrsOf (lib.types.submodule extraVirtualHostOptions); }; - }; - - config = { - # Don't attempt to run acme if the domain name is not tlater.net - systemd.services = - let - confirm = ''[[ "tlater.net" = ${config.services.nginx.domain} ]]''; - in - lib.mapAttrs' ( - cert: _: - lib.nameValuePair "acme-${cert}" { - serviceConfig.ExecCondition = ''${pkgs.runtimeShell} -c '${confirm}' ''; - } - ) config.security.acme.certs; - }; -}