diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index f83413b4534e..05843655b089 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -122,16 +122,32 @@ let ''; vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost: - let - ssl = vhost.enableSSL || vhost.forceSSL; - defaultPort = if ssl then 443 else 80; + let + ssl = with vhost; addSSL || onlySSL || enableSSL; - listenString = { addr, port, ... }: - "listen ${addr}:${toString (if port != null then port else defaultPort)} " + defaultListen = with vhost; + if listen != [] then listen + else if onlySSL || enableSSL then + singleton { addr = "0.0.0.0"; port = 443; ssl = true; } + ++ optional enableIPv6 { addr = "[::]"; port = 443; ssl = true; } + else singleton { addr = "0.0.0.0"; port = 80; ssl = false; } + ++ optional enableIPv6 { addr = "[::]"; port = 80; ssl = false; } + ++ optional addSSL { addr = "0.0.0.0"; port = 443; ssl = true; } + ++ optional (enableIPv6 && addSSL) { addr = "[::]"; port = 443; ssl = true; }; + + hostListen = + if !vhost.forceSSL + then defaultListen + else filter (x: x.ssl) defaultListen; + + listenString = { addr, port, ssl, ... }: + "listen ${addr}:${toString port} " + optionalString ssl "ssl http2 " - + optionalString vhost.default "default_server" + + optionalString vhost.default "default_server " + ";"; + redirectListen = filter (x: !x.ssl) defaultListen; + redirectListenString = { addr, ... }: "listen ${addr}:80 ${optionalString vhost.default "default_server"};"; @@ -152,7 +168,7 @@ let in '' ${optionalString vhost.forceSSL '' server { - ${concatMapStringsSep "\n" redirectListenString vhost.listen} + ${concatMapStringsSep "\n" redirectListenString redirectListen} server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; ${optionalString vhost.enableACME acmeLocation} @@ -163,7 +179,7 @@ let ''} server { - ${concatMapStringsSep "\n" listenString vhost.listen} + ${concatMapStringsSep "\n" listenString hostListen} server_name ${vhost.serverName} ${concatStringsSep " " vhost.serverAliases}; ${optionalString vhost.enableACME acmeLocation} ${optionalString (vhost.root != null) "root ${vhost.root};"} @@ -392,6 +408,7 @@ in example = literalExample '' { "hydra.example.com" = { + addSSL = true; forceSSL = true; enableACME = true; locations."/" = { @@ -408,11 +425,40 @@ in config = mkIf cfg.enable { # TODO: test user supplied config file pases syntax test - assertions = let hostOrAliasIsNull = l: l.root == null || l.alias == null; in [ + warnings = + let + deprecatedSSL = name: config: optional config.enableSSL + '' + config.services.nginx.virtualHosts..enableSSL is deprecated, + use config.services.nginx.virtualHosts..onlySSL instead. + ''; + + in flatten (mapAttrsToList deprecatedSSL virtualHosts); + + assertions = + let + hostOrAliasIsNull = l: l.root == null || l.alias == null; + in [ { assertion = all (host: all hostOrAliasIsNull (attrValues host.locations)) (attrValues virtualHosts); message = "Only one of nginx root or alias can be specified on a location."; } + + { + assertion = all (conf: with conf; !(addSSL && (onlySSL || enableSSL))) (attrValues virtualHosts); + message = '' + Options services.nginx.service.virtualHosts..addSSL and + services.nginx.virtualHosts..onlySSL are mutually esclusive + ''; + } + + { + assertion = all (conf: with conf; forceSSL -> addSSL) (attrValues virtualHosts); + message = '' + Option services.nginx.virtualHosts..forceSSL requires + services.nginx.virtualHosts..addSSL set to true. + ''; + } ]; systemd.services.nginx = { diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 60260512bc2f..362f8ee90524 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -27,25 +27,21 @@ with lib; }; listen = mkOption { - type = with types; listOf (submodule { - options = { - addr = mkOption { type = str; description = "IP address."; }; - port = mkOption { type = nullOr int; description = "Port number."; }; - }; - }); - default = - [ { addr = "0.0.0.0"; port = null; } ] - ++ optional config.networking.enableIPv6 - { addr = "[::]"; port = null; }; + type = with types; listOf (submodule { options = { + addr = mkOption { type = str; description = "IP address."; }; + port = mkOption { type = int; description = "Port number."; default = 80; }; + ssl = mkOption { type = bool; description = "Enable SSL."; default = false; }; + }; }); + default = []; example = [ - { addr = "195.154.1.1"; port = 443; } - { addr = "192.168.1.2"; port = 443; } + { addr = "195.154.1.1"; port = 443; ssl = true;} + { addr = "192.154.1.1"; port = 80; } ]; description = '' Listen addresses and ports for this virtual host. IPv6 addresses must be enclosed in square brackets. - Setting the port to null defaults - to 80 for http and 443 for https (i.e. when enableSSL is set). + Note: this option overrides addSSL + and onlySSL. ''; }; @@ -70,16 +66,39 @@ with lib; ''; }; - enableSSL = mkOption { + addSSL = mkOption { type = types.bool; default = false; - description = "Whether to enable SSL (https) support."; + description = '' + Whether to enable HTTPS in addition to plain HTTP. This will set defaults for + listen to listen on all interfaces on the respective default + ports (80, 443). + ''; + }; + + onlySSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS and reject plain HTTP connections. This will set + defaults for listen to listen on all interfaces on port 443. + ''; + }; + + enableSSL = mkOption { + type = types.bool; + visible = false; + default = false; }; forceSSL = mkOption { type = types.bool; default = false; - description = "Whether to always redirect to https."; + description = '' + Whether to add a separate nginx server block that permanently redirects (301) + all plain HTTP traffic to HTTPS. This option needs addSSL + to be set to true. + ''; }; sslCertificate = mkOption {