diff options
author | Rok Garbas <rok@garbas.si> | 2016-08-01 13:10:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2016-08-01 13:10:06 +0200 |
commit | 34237beca6206475dec1f1c68edf227401793382 (patch) | |
tree | 7762e265bda491d501ad8671c70eaa70e7b1c29d /nixos | |
parent | c91d07b6689c9f7bd90622a0d31530005c02256d (diff) | |
parent | a193fecf0ef3fb2d048981217d6de7a051212e44 (diff) | |
download | nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar.gz nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar.bz2 nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar.lz nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar.xz nixpkgs-34237beca6206475dec1f1c68edf227401793382.tar.zst nixpkgs-34237beca6206475dec1f1c68edf227401793382.zip |
Merge pull request #15862 from mayflower/nginx-module
Declarative nginx module with ACME support
Diffstat (limited to 'nixos')
3 files changed, 455 insertions, 10 deletions
diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index 27a33f33ff9..8385d8e6026 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -4,31 +4,222 @@ with lib; let cfg = config.services.nginx; - nginx = cfg.package; + virtualHosts = mapAttrs (vhostName: vhostConfig: + vhostConfig // (optionalAttrs vhostConfig.enableACME { + sslCertificate = "/var/lib/acme/${vhostName}/fullchain.pem"; + sslCertificateKey = "/var/lib/acme/${vhostName}/key.pem"; + }) + ) cfg.virtualHosts; + configFile = pkgs.writeText "nginx.conf" '' user ${cfg.user} ${cfg.group}; + error_log stderr; daemon off; ${cfg.config} + ${optionalString (cfg.httpConfig == "") '' + http { + include ${cfg.package}/conf/mime.types; + include ${cfg.package}/conf/fastcgi.conf; + + ${optionalString (cfg.recommendedOptimisation) '' + # optimisation + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + ''} + + ssl_protocols ${cfg.sslProtocols}; + ssl_ciphers ${cfg.sslCiphers}; + ${optionalString (cfg.sslDhparam != null) "ssl_dhparam ${cfg.sslDhparam};"} + + ${optionalString (cfg.recommendedTlsSettings) '' + ssl_session_cache shared:SSL:42m; + ssl_session_timeout 23m; + ssl_ecdh_curve secp384r1; + ssl_prefer_server_ciphers on; + ssl_stapling on; + ssl_stapling_verify on; + ''} + + ${optionalString (cfg.recommendedGzipSettings) '' + gzip on; + gzip_disable "msie6"; + gzip_proxied any; + gzip_comp_level 9; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + ''} + + ${optionalString (cfg.recommendedProxySettings) '' + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Host $host; + proxy_set_header X-Forwarded-Server $host; + proxy_set_header Accept-Encoding ""; + + proxy_redirect off; + proxy_connect_timeout 90; + proxy_send_timeout 90; + proxy_read_timeout 90; + proxy_http_version 1.0; + ''} + + client_max_body_size ${cfg.clientMaxBodySize}; + + server_tokens ${if cfg.serverTokens then "on" else "off"}; + + ${vhosts} + + ${optionalString cfg.statusPage '' + server { + listen 80; + listen [::]:80; + + server_name localhost; + + location /nginx_status { + stub_status on; + access_log off; + allow 127.0.0.1; + allow ::1; + deny all; + } + } + ''} + + ${cfg.appendHttpConfig} + }''} + ${optionalString (cfg.httpConfig != "") '' http { include ${cfg.package}/conf/mime.types; + include ${cfg.package}/conf/fastcgi.conf; ${cfg.httpConfig} - } - ''} + }''} + ${cfg.appendConfig} ''; + + vhosts = concatStringsSep "\n" (mapAttrsToList (serverName: vhost: + let + ssl = vhost.enableSSL || vhost.forceSSL; + port = if vhost.port != null then vhost.port else (if ssl then 443 else 80); + listenString = toString port + optionalString ssl " ssl http2" + + optionalString vhost.default " default"; + acmeLocation = optionalString vhost.enableACME '' + location /.well-known/acme-challenge { + try_files $uri @acme-fallback; + root ${vhost.acmeRoot}; + auth_basic off; + } + location @acme-fallback { + auth_basic off; + proxy_pass http://${vhost.acmeFallbackHost}; + } + ''; + in '' + ${optionalString vhost.forceSSL '' + server { + listen 80 ${optionalString vhost.default "default"}; + listen [::]:80 ${optionalString vhost.default "default"}; + + server_name ${serverName} ${concatStringsSep " " vhost.serverAliases}; + ${acmeLocation} + location / { + return 301 https://$host${optionalString (port != 443) ":${port}"}$request_uri; + } + } + ''} + + server { + listen ${listenString}; + listen [::]:${listenString}; + + server_name ${serverName} ${concatStringsSep " " vhost.serverAliases}; + ${acmeLocation} + ${optionalString (vhost.root != null) "root ${vhost.root};"} + ${optionalString (vhost.globalRedirect != null) '' + return 301 http${optionalString ssl "s"}://${vhost.globalRedirect}$request_uri; + ''} + ${optionalString ssl '' + ssl_certificate ${vhost.sslCertificate}; + ssl_certificate_key ${vhost.sslCertificateKey}; + ''} + + ${optionalString (vhost.basicAuth != {}) (mkBasicAuth serverName vhost.basicAuth)} + + ${mkLocations vhost.locations} + + ${vhost.extraConfig} + } + '' + ) virtualHosts); + mkLocations = locations: concatStringsSep "\n" (mapAttrsToList (location: config: '' + location ${location} { + ${optionalString (config.proxyPass != null) "proxy_pass ${config.proxyPass};"} + ${optionalString (config.root != null) "root ${config.root};"} + ${config.extraConfig} + } + '') locations); + mkBasicAuth = serverName: authDef: let + htpasswdFile = pkgs.writeText "${serverName}.htpasswd" ( + concatStringsSep "\n" (mapAttrsToList (user: password: '' + ${user}:{PLAIN}${password} + '') authDef) + ); + in '' + auth_basic secured; + auth_basic_user_file ${htpasswdFile}; + ''; in { options = { services.nginx = { - enable = mkOption { + enable = mkEnableOption "Nginx Web Server"; + + statusPage = mkOption { + default = false; + type = types.bool; + description = " + Enable status page reachable from localhost on http://127.0.0.1/nginx_status. + "; + }; + + recommendedTlsSettings = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended TLS settings. + "; + }; + + recommendedOptimisation = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended optimisation settings. + "; + }; + + recommendedGzipSettings = mkOption { + default = false; + type = types.bool; + description = " + Enable recommended gzip settings. + "; + }; + + recommendedProxySettings = mkOption { default = false; type = types.bool; description = " - Enable the nginx Web Server. + Enable recommended proxy settings. "; }; @@ -64,7 +255,22 @@ in httpConfig = mkOption { type = types.lines; default = ""; - description = "Configuration lines to be appended inside of the http {} block."; + description = " + Configuration lines to be set inside the http block. + This is mutually exclusive with the structured configuration + via virtualHosts and the recommendedXyzSettings configuration + options. See appendHttpConfig for appending to the generated http block. + "; + }; + + appendHttpConfig = mkOption { + type = types.lines; + default = ""; + description = " + Configuration lines to be appended to the generated http block. + This is mutually exclusive with using httpConfig for specifying the whole + http block verbatim. + "; }; stateDir = mkOption { @@ -86,8 +292,59 @@ in description = "Group account under which nginx runs."; }; - }; + serverTokens = mkOption { + type = types.bool; + default = false; + description = "Show nginx version in headers and error pages."; + }; + + clientMaxBodySize = mkOption { + type = types.string; + default = "10m"; + description = "Set nginx global client_max_body_size."; + }; + + sslCiphers = mkOption { + type = types.str; + default = "EECDH+aRSA+AESGCM:EDH+aRSA:EECDH+aRSA:+AES256:+AES128:+SHA1:!CAMELLIA:!SEED:!3DES:!DES:!RC4:!eNULL"; + description = "Ciphers to choose from when negotiating tls handshakes."; + }; + + sslProtocols = mkOption { + type = types.str; + default = "TLSv1.2"; + example = "TLSv1 TLSv1.1 TLSv1.2"; + description = "Allowed TLS protocol versions."; + }; + + sslDhparam = mkOption { + type = types.nullOr types.path; + default = null; + example = "/path/to/dhparams.pem"; + description = "Path to DH parameters file."; + }; + virtualHosts = mkOption { + type = types.attrsOf (types.submodule (import ./vhost-options.nix { + inherit lib; + })); + default = { + localhost = {}; + }; + example = literalExample '' + { + "hydra.example.com" = { + forceSSL = true; + enableACME = true; + locations."/" = { + proxyPass = "http://localhost:3000"; + }; + }; + }; + ''; + description = "Declarative vhost config"; + }; + }; }; config = mkIf cfg.enable { @@ -97,7 +354,6 @@ in description = "Nginx Web Server"; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; - path = [ nginx ]; preStart = '' mkdir -p ${cfg.stateDir}/logs @@ -105,14 +361,23 @@ in chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} ''; serviceConfig = { - ExecStart = "${nginx}/bin/nginx -c ${configFile} -p ${cfg.stateDir}"; + ExecStart = "${cfg.package}/bin/nginx -c ${configFile} -p ${cfg.stateDir}"; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; - Restart = "on-failure"; + Restart = "always"; RestartSec = "10s"; StartLimitInterval = "1min"; }; }; + security.acme.certs = filterAttrs (n: v: v != {}) ( + mapAttrs (vhostName: vhostConfig: + optionalAttrs vhostConfig.enableACME { + webroot = vhostConfig.acmeRoot; + extraDomains = genAttrs vhostConfig.serverAliases (alias: null); + } + ) virtualHosts + ); + users.extraUsers = optionalAttrs (cfg.user == "nginx") (singleton { name = "nginx"; group = cfg.group; diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix new file mode 100644 index 00000000000..ce3462bed0a --- /dev/null +++ b/nixos/modules/services/web-servers/nginx/location-options.nix @@ -0,0 +1,40 @@ +# This file defines the options that can be used both for the Apache +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ lib }: + +with lib; + +{ + options = { + proxyPass = mkOption { + type = types.nullOr types.str; + default = null; + example = "http://www.example.org/"; + description = '' + Adds proxy_pass directive and sets default proxy headers Host, X-Real-Ip + and X-Forwarded-For. + ''; + }; + + root = mkOption { + type = types.nullOr types.path; + default = null; + example = /your/root/directory; + description = '' + Root directory for requests. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the location verbatim. + ''; + }; + }; +} + diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix new file mode 100644 index 00000000000..ee3f68bf805 --- /dev/null +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -0,0 +1,140 @@ +# This file defines the options that can be used both for the Apache +# main server configuration, and for the virtual hosts. (The latter +# has additional options that affect the web server as a whole, like +# the user/group to run under.) + +{ lib }: + +with lib; +{ + options = { + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + port = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Port for the server. Defaults to 80 for http + and 443 for https (i.e. when enableSSL is set). + ''; + }; + + enableACME = mkOption { + type = types.bool; + default = false; + description = "Whether to ask Let's Encrypt to sign a certificate for this vhost."; + }; + + acmeRoot = mkOption { + type = types.str; + default = "/var/lib/acme/acme-challenge"; + description = "Directory to store certificates and keys managed by the ACME service."; + }; + + acmeFallbackHost = mkOption { + type = types.str; + default = "0.0.0.0"; + description = '' + Host which to proxy requests to if acme challenge is not found. Useful + if you want multiple hosts to be able to verify the same domain name. + ''; + }; + + enableSSL = mkOption { + type = types.bool; + default = false; + description = "Whether to enable SSL (https) support."; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = "Whether to always redirect to https."; + }; + + sslCertificate = mkOption { + type = types.path; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslCertificateKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + root = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of the web root directory. + ''; + }; + + default = mkOption { + type = types.bool; + default = false; + description = '' + Makes this vhost the default. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + description = '' + These lines go to the end of the vhost verbatim. + ''; + }; + + globalRedirect = mkOption { + type = types.nullOr types.str; + default = null; + example = http://newserver.example.org/; + description = '' + If set, all requests for this host are redirected permanently to + the given URL. + ''; + }; + + basicAuth = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample '' + { + user = "password"; + }; + ''; + description = '' + Basic Auth protection for a vhost. + + WARNING: This is implemented to store the password in plain text in the + nix store. + ''; + }; + + locations = mkOption { + type = types.attrsOf (types.submodule (import ./location-options.nix { + inherit lib; + })); + default = {}; + example = literalExample '' + { + "/" = { + proxyPass = "http://localhost:3000"; + }; + }; + ''; + description = "Declarative location config"; + }; + }; +} |