nix: add tests.nix
[sourcephile-nix.git] / servers / mermet / nginx.nix
index 8b9ef1db8a06273217c941d6ebfc65ad2f944063..2ad255701868e4d300004fc70e48fbce1908a44d 100644 (file)
@@ -1,16 +1,16 @@
 {pkgs, lib, config, system, ...}:
-let inherit (builtins) readFile;
-    inherit (builtins.extraBuiltins) pass;
-    inherit (lib) types;
-    inherit (pkgs.lib) loadFile;
-    inherit (config) networking;
-    inherit (config.services) nginx;
-    domainDir = dom: lib.concatStringsSep "/" (lib.reverseList (lib.splitString "." dom));
+let
+  inherit (builtins) readFile;
+  inherit (builtins.extraBuiltins) pass;
+  inherit (lib) types;
+  inherit (pkgs.lib) loadFile;
+  inherit (config) networking;
+  inherit (config.services) nginx;
+  domainDir = dom: lib.concatStringsSep "/" (lib.reverseList (lib.splitString "." dom));
 in
 {
 imports = [
-  nginx/gitweb.nix
-  nginx/www.nix
+  nginx/sourcephile.fr.nix
 ];
 options = {
   services.nginx = {
@@ -26,22 +26,19 @@ options = {
       type    = types.str;
       default = "/var/log/nginx";
     };
+    configs = lib.mkOption {
+      type = types.attrsOf types.lines;
+      default = {};
+      description = ''
+        Make some configs available to all virtual hosts.
+        Useful to workaround the reset of add_header:
+        https://blog.g3rt.nl/nginx-add_header-pitfall.html
+      '';
+      #apply = lib.mapAttrs (name: pkgs.writeText "${name}.conf");
+    };
   };
 };
 config = {
-  systemd.services.nginx.after = [
-    "${networking.domainBase}.key.pem-key.service"
-    #"keys.target"
-  ];
-  deployment.keys = {
-    "nginx.${networking.domainBase}.key.pem" = {
-      text        = pass "x509/${networking.domainBase}/key.pem";
-      user        = nginx.user;
-      group       = "root";
-      destDir     = "/run/keys/";
-      permissions = "0400"; # WARNING: not enforced when deployment.storeKeysOnMachine = true
-    };
-  };
   systemd.services.nginx = {
     preStart = lib.mkBefore ''
       install -D -d -o ${nginx.user} -g ${nginx.group} -m 0700 \
@@ -50,8 +47,14 @@ config = {
        ${nginx.logDir}
     '';
   };
+  users.groups."acme".members = [nginx.user];
   services.nginx = {
     enable = true;
+    package = pkgs.nginx.override {
+      modules = with pkgs.nginxModules; [
+        fancyindex
+      ];
+    };
     stateDir = "/dev/shm/nginx";
     eventsConfig = ''
       multi_accept on;
@@ -59,18 +62,71 @@ config = {
       worker_connections 1024;
     '';
     clientMaxBodySize = "20m";
+    recommendedGzipSettings = true;
+    recommendedOptimisation = false;
     recommendedProxySettings = true;
     recommendedTlsSettings = true;
+    resolver = {
+      addresses = [ "127.0.0.1:53" ];
+      valid = "";
+      ipv6 = networking.defaultGateway6 != null;
+    };
     serverTokens = false;
-    sslCiphers = "HIGH:!ADH:!MD5:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
+    # Only allow PFS-enabled ciphers with AES256
+    #sslCiphers = "AES256+EECDH:AES256+EDH:!aNULL";
+    #sslCiphers = "HIGH:!ADH:!MD5:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
+    #sslCiphers = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL";
     sslDhparam = ../../../sec/openssl/dh.pem;
-    #sslCiphers = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL;";
-    #sslProtocols = "TLSv1.2";
+    sslProtocols = "TLSv1.3 TLSv1.2";
+    configs = rec {
+      http_add_headers = ''
+        # Add HSTS header with preloading to HTTPS requests.
+        # Adding this header to HTTP requests is discouraged
+        # DOC: https://blog.qualys.com/securitylabs/2016/03/28/the-importance-of-a-proper-http-strict-transport-security-implementation-on-your-web-server
+        add_header Strict-Transport-Security $hsts_header;
+
+        # Enable CSP for your services.
+        #add_header Content-Security-Policy "script-src 'self'; object-src 'none'; base-uri 'none';" always;
+
+        # Minimize information leaked to other domains
+        add_header 'Referrer-Policy' 'origin-when-cross-origin';
+
+        # Disable embedding as a frame
+        add_header X-Frame-Options DENY;
+
+        # Prevent injection of code in other mime types (XSS Attacks)
+        add_header X-Content-Type-Options nosniff;
+
+        # Enable XSS protection of the browser.
+        # May be unnecessary when CSP is configured properly (see above)
+        add_header X-XSS-Protection "1; mode=block";
+      '';
+      https_add_headers = ''
+        ${http_add_headers}
+      '';
+    };
     commonHttpConfig = ''
       log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                       '$status $body_bytes_sent "$http_referer" '
                       '"$http_user_agent" "$http_x_forwarded_for"';
-      #charset UTF-8;
+
+      log_format json escape=json
+        '{'
+          '"time_local":"$time_local",'
+          '"remote_addr":"$remote_addr",'
+          '"status": "$status",'
+          '"request":"$request",'
+          '"body_bytes_sent":"$body_bytes_sent",'
+          '"http_referrer":"$http_referer",'
+          '"http_user_agent":"$http_user_agent",'
+          '"remote_user":"$remote_user",'
+          '"request_time":"$request_time"'
+        '}';
+      charset UTF-8;
+      types {
+        text/html html5;
+        text/plain md;
+      }
       '' +
       lib.concatStringsSep "\n" (lib.attrValues {
         default = ''
@@ -79,6 +135,11 @@ config = {
         '';
         security = ''
           #error_page 403 = 404;
+
+          ${nginx.configs.http_add_headers}
+
+          # This might create errors
+          proxy_cookie_path / "/; secure; HttpOnly; SameSite=strict";
         '';
         log = ''
           access_log ${nginx.logDir}/access.log main buffer=32k;
@@ -95,14 +156,6 @@ config = {
           fastcgi_buffers 256 4k;
           fastcgi_busy_buffers_size 256k;
           fastcgi_cache_key "$request_method $scheme://$http_host$request_uri";
-          fastcgi_cache_path ${nginx.stateDir}/fastcgi_cache
-                             inactive=10m
-                             keys_zone=microcache:2M
-                             levels=1:2
-                             loader_files=100000
-                             loader_sleep=1
-                             loader_threshold=2592000000
-                             max_size=64M;
           fastcgi_connect_timeout 60;
           fastcgi_ignore_client_abort off;
           fastcgi_intercept_errors on;
@@ -111,7 +164,7 @@ config = {
           fastcgi_param SCRIPT_FILENAME $request_filename;
           fastcgi_temp_path ${nginx.stateDir}/fastcgi_temp 1 2;
         '';
-        connexion = ''
+        connection = ''
           sendfile on;
           # If the client stops reading data,
           # free up the stale client connection after this much time.
@@ -126,10 +179,19 @@ config = {
           tcp_nodelay on;
           keepalive_timeout 20;
           reset_timedout_connection on;
+          types_hash_max_size 4096;
           server_names_hash_bucket_size 128;
-          types_hash_max_size 2048;
         '';
         map = ''
+          map $time_iso8601 $date {
+            default 'date-not-found';
+            '~^(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})' $year-$month-$day;
+          }
+
+          map $scheme $hsts_header {
+            https  "max-age=31536000; includeSubdomains; preload";
+          }
+
           # User agents that are to be blocked.
           #map $http_user_agent $bad_bot {
           #  default 0;
@@ -146,34 +208,6 @@ config = {
           #  127.0.0.1 0;
           #}
         '';
-        gzip = ''
-          gzip on;
-          gzip_buffers 16 8k;
-          gzip_comp_level 6;
-          gzip_disable "MSIE [1-6]\.";
-          gzip_http_version 1.1;
-          gzip_min_length 1024;
-          gzip_proxied any;
-          gzip_static on;
-          gzip_vary on;
-          gzip_types application/atom+xml
-                     application/javascript
-                     application/json
-                     application/rss+xml
-                     application/vnd.ms-fontobject
-                     application/x-font-ttf
-                     application/x-javascript
-                     application/xml
-                     application/xml+rss
-                     font/opentype
-                     font/truetype
-                     image/svg+xml
-                     text/css
-                     text/javascript
-                     text/plain
-                     text/x-component
-                     text/xml;
-        '';
         cache = ''
           client_body_buffer_size 4K;
             # getconf PAGESIZE
@@ -183,7 +217,7 @@ config = {
           client_header_buffer_size 1k;
           client_header_timeout 60;
           large_client_header_buffers 4 8k;
-          
+
           open_file_cache max=200000 inactive=20s;
           open_file_cache_errors on;
           open_file_cache_min_uses 2;
@@ -191,14 +225,11 @@ config = {
         '';
       });
     appendConfig = ''
-      worker_processes 4;
+      worker_processes ${toString config.nix.maxJobs};
     '';
     virtualHosts."_" = {
-      forceSSL = false;
-      # Convoluted way to load the certificate in the store and using ${networking.domainBase} to find it.
-      # NOTE: no ssl_stapling while the certificate remains self-signed.
-      sslCertificate = loadFile (../../../sec + "/openssl/${networking.domainBase}/cert.self-signed.pem");
-      sslCertificateKey = "/run/keys/nginx.${networking.domainBase}.key.pem";
+      forceSSL = true;
+      useACMEHost = networking.domain;
     };
   };
 };