From 4daf3ffd02f7cfb1c9a3c8c95bec21dd078ab26f Mon Sep 17 00:00:00 2001 From: oddlama Date: Mon, 25 Dec 2023 21:49:06 +0100 Subject: [PATCH] feat: add nginx meta module --- README.md | 9 ++++ flake.nix | 3 ++ modules/default.nix | 1 + modules/nginx.nix | 106 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 119 insertions(+) create mode 100644 modules/nginx.nix diff --git a/README.md b/README.md index cbd2b0d..5bd5ae0 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,12 @@ will be available. }; } } +``` + +## Requirements + +Certain modules may require the use of additional flakes. In particular you might need: + +- [impermanence](https://github.com/nix-community/impermanence) +- [agenix](https://github.com/ryantm/agenix) +- [agenix-rekey](https://github.com/oddlama/agenix-rekey) diff --git a/flake.nix b/flake.nix index 436d0d3..c79bbba 100644 --- a/flake.nix +++ b/flake.nix @@ -48,6 +48,9 @@ }; }; + # `nix fmt` + formatter = pkgs.alejandra; + # `nix develop` devShells.default = pkgs.devshell.mkShell { name = "extra-modules"; diff --git a/modules/default.nix b/modules/default.nix index e96e8a2..8e40808 100644 --- a/modules/default.nix +++ b/modules/default.nix @@ -2,5 +2,6 @@ imports = [ ./interface-naming.nix ./boot.nix + ./nginx.nix ]; } diff --git a/modules/nginx.nix b/modules/nginx.nix new file mode 100644 index 0000000..9c58e70 --- /dev/null +++ b/modules/nginx.nix @@ -0,0 +1,106 @@ +{ + config, + lib, + ... +}: let + inherit + (lib) + mkBefore + mkEnableOption + mkIf + mkOption + types + ; +in { + options.services.nginx = { + recommendedSetup = mkEnableOption "recommended setup parameters."; + recommendedSecurityHeaders = mkEnableOption "additional security headers by default in each location block. Can be overwritten in each location with `recommendedSecurityHeaders`."; + virtualHosts = mkOption { + type = types.attrsOf (types.submodule { + options.locations = mkOption { + type = types.attrsOf (types.submodule (submod: { + options = { + recommendedSecurityHeaders = mkOption { + type = types.bool; + default = config.services.nginx.recommendedSecurityHeaders; + description = "Whether to add additional security headers to this location."; + }; + + X-Frame-Options = mkOption { + type = types.str; + default = "DENY"; + description = "The value to use for X-Frame-Options"; + }; + }; + config = mkIf submod.config.recommendedSecurityHeaders { + extraConfig = mkBefore '' + # Enable HTTP Strict Transport Security (HSTS) + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload"; + + # Minimize information leaked to other domains + add_header Referrer-Policy "origin-when-cross-origin"; + + add_header X-XSS-Protection "1; mode=block"; + add_header X-Frame-Options "${submod.config.X-Frame-Options}"; + add_header X-Content-Type-Options "nosniff"; + ''; + }; + })); + }; + }); + }; + }; + + config = mkIf (config.services.nginx.enable && config.services.nginx.recommendedSetup) { + age.secrets."dhparams.pem" = { + generator.script = "dhparams"; + mode = "440"; + group = "nginx"; + }; + + networking.firewall.allowedTCPPorts = [80 443]; + + # Sensible defaults for nginx + services.nginx = { + recommendedBrotliSettings = true; + recommendedGzipSettings = true; + recommendedOptimisation = true; + recommendedProxySettings = true; + recommendedTlsSettings = true; + recommendedSecurityHeaders = true; + + # SSL config + sslCiphers = "EECDH+AESGCM:EDH+AESGCM:!aNULL"; + sslDhparam = config.age.secrets."dhparams.pem".path; + commonHttpConfig = '' + log_format json_combined escape=json '{' + '"time": $msec,' + '"remote_addr":"$remote_addr",' + '"status":$status,' + '"method":"$request_method",' + '"host":"$host",' + '"uri":"$request_uri",' + '"request_size":$request_length,' + '"response_size":$body_bytes_sent,' + '"response_time":$request_time,' + '"referrer":"$http_referer",' + '"user_agent":"$http_user_agent"' + '}'; + error_log syslog:server=unix:/dev/log,nohostname; + access_log syslog:server=unix:/dev/log,nohostname json_combined; + ssl_ecdh_curve secp384r1; + ''; + + # Default host that rejects everything. + # This is selected when no matching host is found for a request. + virtualHosts.dummy = { + listenAddresses = ["127.0.0.1" "[::1]"]; + default = true; + rejectSSL = true; + locations."/".extraConfig = '' + deny all; + ''; + }; + }; + }; +}