diff options
author | Léo Gaspard <leo@gaspard.io> | 2020-01-09 03:49:03 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-01-09 03:49:03 +0100 |
commit | b31660e5bbe2ee2acbec3c1552d516c4d387c392 (patch) | |
tree | 04ac350ab4dc29eb903a28aff6eb829eb4a27dc7 /nixos/modules/services/web-servers | |
parent | 665fee312a03aac0db9a61cc302a51626288c7c2 (diff) | |
parent | 1b28e47ae107ded7e2837787a376e5abb15152b5 (diff) | |
download | nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar.gz nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar.bz2 nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar.lz nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar.xz nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.tar.zst nixpkgs-b31660e5bbe2ee2acbec3c1552d516c4d387c392.zip |
Merge branch 'master' into ihatemoney
Diffstat (limited to 'nixos/modules/services/web-servers')
13 files changed, 873 insertions, 565 deletions
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index 098160ee369..4460f89ec5c 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -6,124 +6,50 @@ let mainCfg = config.services.httpd; + runtimeDir = "/run/httpd"; + httpd = mainCfg.package.out; httpdConf = mainCfg.configFile; php = mainCfg.phpPackage.override { apacheHttpd = httpd.dev; /* otherwise it only gets .out */ }; - phpMajorVersion = head (splitString "." php.version); + phpMajorVersion = lib.versions.major (lib.getVersion php); mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = httpd; }; - defaultListen = cfg: if cfg.enableSSL - then [{ip = "*"; port = 443;}] - else [{ip = "*"; port = 80;}]; - - getListen = cfg: - if cfg.listen == [] - then defaultListen cfg - else cfg.listen; - - listenToString = l: "${l.ip}:${toString l.port}"; - - extraModules = attrByPath ["extraModules"] [] mainCfg; - extraForeignModules = filter isAttrs extraModules; - extraApacheModules = filter isString extraModules; - - - makeServerInfo = cfg: { - # Canonical name must not include a trailing slash. - canonicalNames = - let defaultPort = (head (defaultListen cfg)).port; in - map (port: - (if cfg.enableSSL then "https" else "http") + "://" + - cfg.hostName + - (if port != defaultPort then ":${toString port}" else "") - ) (map (x: x.port) (getListen cfg)); - - # Admin address: inherit from the main server if not specified for - # a virtual host. - adminAddr = if cfg.adminAddr != null then cfg.adminAddr else mainCfg.adminAddr; - - vhostConfig = cfg; - serverConfig = mainCfg; - fullConfig = config; # machine config - }; - - - allHosts = [mainCfg] ++ mainCfg.virtualHosts; - - - callSubservices = serverInfo: defs: - let f = svc: - let - svcFunction = - if svc ? function then svc.function - # instead of using serviceType="mediawiki"; you can copy mediawiki.nix to any location outside nixpkgs, modify it at will, and use serviceExpression=./mediawiki.nix; - else if svc ? serviceExpression then import (toString svc.serviceExpression) - else import (toString "${toString ./.}/${if svc ? serviceType then svc.serviceType else svc.serviceName}.nix"); - config = (evalModules - { modules = [ { options = res.options; config = svc.config or svc; } ]; - check = false; - }).config; - defaults = { - extraConfig = ""; - extraModules = []; - extraModulesPre = []; - extraPath = []; - extraServerPath = []; - globalEnvVars = []; - robotsEntries = ""; - startupScript = ""; - enablePHP = false; - enablePerl = false; - phpOptions = ""; - options = {}; - documentRoot = null; - }; - res = defaults // svcFunction { inherit config lib pkgs serverInfo php; }; - in res; - in map f defs; + vhosts = attrValues mainCfg.virtualHosts; + mkListenInfo = hostOpts: + if hostOpts.listen != [] then hostOpts.listen + else ( + optional (hostOpts.onlySSL || hostOpts.addSSL || hostOpts.forceSSL) { ip = "*"; port = 443; ssl = true; } ++ + optional (!hostOpts.onlySSL) { ip = "*"; port = 80; ssl = false; } + ); - # !!! callSubservices is expensive - subservicesFor = cfg: callSubservices (makeServerInfo cfg) cfg.extraSubservices; + listenInfo = unique (concatMap mkListenInfo vhosts); - mainSubservices = subservicesFor mainCfg; + enableSSL = any (listen: listen.ssl) listenInfo; - allSubservices = mainSubservices ++ concatMap subservicesFor mainCfg.virtualHosts; + enableUserDir = any (vhost: vhost.enableUserDir) vhosts; - - enableSSL = any (vhost: vhost.enableSSL) allHosts; - - - # Names of modules from ${httpd}/modules that we want to load. - apacheModules = - [ # HTTP authentication mechanisms: basic and digest. - "auth_basic" "auth_digest" - - # Authentication: is the user who he claims to be? - "authn_file" "authn_dbm" "authn_anon" "authn_core" - - # Authorization: is the user allowed access? - "authz_user" "authz_groupfile" "authz_host" "authz_core" - - # Other modules. - "ext_filter" "include" "log_config" "env" "mime_magic" - "cern_meta" "expires" "headers" "usertrack" /* "unique_id" */ "setenvif" - "mime" "dav" "status" "autoindex" "asis" "info" "dav_fs" - "vhost_alias" "negotiation" "dir" "imagemap" "actions" "speling" - "userdir" "alias" "rewrite" "proxy" "proxy_http" - "unixd" "cache" "cache_disk" "slotmem_shm" "socache_shmcb" + # NOTE: generally speaking order of modules is very important + modules = + [ # required apache modules our httpd service cannot run without + "authn_core" "authz_core" + "log_config" + "mime" "autoindex" "negotiation" "dir" + "alias" "rewrite" + "unixd" "slotmem_shm" "socache_shmcb" "mpm_${mainCfg.multiProcessingModule}" - - # For compatibility with old configurations, the new module mod_access_compat is provided. - "access_compat" ] ++ (if mainCfg.multiProcessingModule == "prefork" then [ "cgi" ] else [ "cgid" ]) ++ optional enableSSL "ssl" - ++ extraApacheModules; + ++ optional enableUserDir "userdir" + ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } + ++ optional mainCfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } + ++ optional mainCfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } + ++ mainCfg.extraModules; allDenied = "Require all denied"; @@ -147,20 +73,22 @@ let browserHacks = '' - BrowserMatch "Mozilla/2" nokeepalive - BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 - BrowserMatch "RealPlayer 4\.0" force-response-1.0 - BrowserMatch "Java/1\.0" force-response-1.0 - BrowserMatch "JDK/1\.0" force-response-1.0 - BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully - BrowserMatch "^WebDrive" redirect-carefully - BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully - BrowserMatch "^gnome-vfs" redirect-carefully + <IfModule mod_setenvif.c> + BrowserMatch "Mozilla/2" nokeepalive + BrowserMatch "MSIE 4\.0b2;" nokeepalive downgrade-1.0 force-response-1.0 + BrowserMatch "RealPlayer 4\.0" force-response-1.0 + BrowserMatch "Java/1\.0" force-response-1.0 + BrowserMatch "JDK/1\.0" force-response-1.0 + BrowserMatch "Microsoft Data Access Internet Publishing Provider" redirect-carefully + BrowserMatch "^WebDrive" redirect-carefully + BrowserMatch "^WebDAVFS/1.[012]" redirect-carefully + BrowserMatch "^gnome-vfs" redirect-carefully + </IfModule> ''; sslConf = '' - SSLSessionCache shmcb:${mainCfg.stateDir}/ssl_scache(512000) + SSLSessionCache shmcb:${runtimeDir}/ssl_scache(512000) Mutex posixsem @@ -185,130 +113,144 @@ let </IfModule> ''; - - perServerConf = isMainServer: cfg: let - - serverInfo = makeServerInfo cfg; - - subservices = callSubservices serverInfo cfg.extraSubservices; - - maybeDocumentRoot = fold (svc: acc: - if acc == null then svc.documentRoot else assert svc.documentRoot == null; acc - ) null ([ cfg ] ++ subservices); - - documentRoot = if maybeDocumentRoot != null then maybeDocumentRoot else - pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out"; - - documentRootConf = '' - DocumentRoot "${documentRoot}" - - <Directory "${documentRoot}"> - Options Indexes FollowSymLinks - AllowOverride None - ${allGranted} - </Directory> - ''; - - robotsTxt = - concatStringsSep "\n" (filter (x: x != "") ( - # If this is a vhost, the include the entries for the main server as well. - (if isMainServer then [] else [mainCfg.robotsEntries] ++ map (svc: svc.robotsEntries) mainSubservices) - ++ [cfg.robotsEntries] - ++ (map (svc: svc.robotsEntries) subservices))); - - in '' - ${concatStringsSep "\n" (map (n: "ServerName ${n}") serverInfo.canonicalNames)} - - ${concatMapStrings (alias: "ServerAlias ${alias}\n") cfg.serverAliases} - - ${if cfg.sslServerCert != null then '' - SSLCertificateFile ${cfg.sslServerCert} - SSLCertificateKeyFile ${cfg.sslServerKey} - ${if cfg.sslServerChain != null then '' - SSLCertificateChainFile ${cfg.sslServerChain} - '' else ""} - '' else ""} - - ${if cfg.enableSSL then '' - SSLEngine on - '' else if enableSSL then /* i.e., SSL is enabled for some host, but not this one */ - '' - SSLEngine off - '' else ""} - - ${if isMainServer || cfg.adminAddr != null then '' - ServerAdmin ${cfg.adminAddr} - '' else ""} - - ${if !isMainServer && mainCfg.logPerVirtualHost then '' - ErrorLog ${mainCfg.logDir}/error-${cfg.hostName}.log - CustomLog ${mainCfg.logDir}/access-${cfg.hostName}.log ${cfg.logFormat} - '' else ""} - - ${optionalString (robotsTxt != "") '' - Alias /robots.txt ${pkgs.writeText "robots.txt" robotsTxt} - ''} - - ${if isMainServer || maybeDocumentRoot != null then documentRootConf else ""} - - ${if cfg.enableUserDir then '' - - UserDir public_html - UserDir disabled root - - <Directory "/home/*/public_html"> - AllowOverride FileInfo AuthConfig Limit Indexes - Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec - <Limit GET POST OPTIONS> - ${allGranted} - </Limit> - <LimitExcept GET POST OPTIONS> - ${allDenied} - </LimitExcept> - </Directory> - - '' else ""} - - ${if cfg.globalRedirect != null && cfg.globalRedirect != "" then '' - RedirectPermanent / ${cfg.globalRedirect} - '' else ""} - - ${ - let makeFileConf = elem: '' - Alias ${elem.urlPath} ${elem.file} - ''; - in concatMapStrings makeFileConf cfg.servedFiles - } - - ${ - let makeDirConf = elem: '' - Alias ${elem.urlPath} ${elem.dir}/ - <Directory ${elem.dir}> - Options +Indexes - ${allGranted} - AllowOverride All - </Directory> - ''; - in concatMapStrings makeDirConf cfg.servedDirs - } - - ${concatMapStrings (svc: svc.extraConfig) subservices} - - ${cfg.extraConfig} - ''; + mkVHostConf = hostOpts: + let + adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr; + listen = filter (listen: !listen.ssl) (mkListenInfo hostOpts); + listenSSL = filter (listen: listen.ssl) (mkListenInfo hostOpts); + + useACME = hostOpts.enableACME || hostOpts.useACMEHost != null; + sslCertDir = + if hostOpts.enableACME then config.security.acme.certs.${hostOpts.hostName}.directory + else if hostOpts.useACMEHost != null then config.security.acme.certs.${hostOpts.useACMEHost}.directory + else abort "This case should never happen."; + + sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert; + sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey; + sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain; + + acmeChallenge = optionalString useACME '' + Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/" + <Directory "${hostOpts.acmeRoot}"> + AllowOverride None + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + Require method GET POST OPTIONS + Require all granted + </Directory> + ''; + in + optionalString (listen != []) '' + <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listen}> + ServerName ${hostOpts.hostName} + ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} + ServerAdmin ${adminAddr} + <IfModule mod_ssl.c> + SSLEngine off + </IfModule> + ${acmeChallenge} + ${if hostOpts.forceSSL then '' + <IfModule mod_rewrite.c> + RewriteEngine on + RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC] + RewriteCond %{HTTPS} off + RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} + </IfModule> + '' else mkVHostCommonConf hostOpts} + </VirtualHost> + '' + + optionalString (listenSSL != []) '' + <VirtualHost ${concatMapStringsSep " " (listen: "${listen.ip}:${toString listen.port}") listenSSL}> + ServerName ${hostOpts.hostName} + ${concatMapStrings (alias: "ServerAlias ${alias}\n") hostOpts.serverAliases} + ServerAdmin ${adminAddr} + SSLEngine on + SSLCertificateFile ${sslServerCert} + SSLCertificateKeyFile ${sslServerKey} + ${optionalString (sslServerChain != null) "SSLCertificateChainFile ${sslServerChain}"} + ${acmeChallenge} + ${mkVHostCommonConf hostOpts} + </VirtualHost> + '' + ; + + mkVHostCommonConf = hostOpts: + let + documentRoot = if hostOpts.documentRoot != null + then hostOpts.documentRoot + else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out" + ; + in + '' + ${optionalString mainCfg.logPerVirtualHost '' + ErrorLog ${mainCfg.logDir}/error-${hostOpts.hostName}.log + CustomLog ${mainCfg.logDir}/access-${hostOpts.hostName}.log ${hostOpts.logFormat} + ''} + + ${optionalString (hostOpts.robotsEntries != "") '' + Alias /robots.txt ${pkgs.writeText "robots.txt" hostOpts.robotsEntries} + ''} + + DocumentRoot "${documentRoot}" + + <Directory "${documentRoot}"> + Options Indexes FollowSymLinks + AllowOverride None + ${allGranted} + </Directory> + + ${optionalString hostOpts.enableUserDir '' + UserDir public_html + UserDir disabled root + <Directory "/home/*/public_html"> + AllowOverride FileInfo AuthConfig Limit Indexes + Options MultiViews Indexes SymLinksIfOwnerMatch IncludesNoExec + <Limit GET POST OPTIONS> + Require all granted + </Limit> + <LimitExcept GET POST OPTIONS> + Require all denied + </LimitExcept> + </Directory> + ''} + + ${optionalString (hostOpts.globalRedirect != null && hostOpts.globalRedirect != "") '' + RedirectPermanent / ${hostOpts.globalRedirect} + ''} + + ${ + let makeFileConf = elem: '' + Alias ${elem.urlPath} ${elem.file} + ''; + in concatMapStrings makeFileConf hostOpts.servedFiles + } + ${ + let makeDirConf = elem: '' + Alias ${elem.urlPath} ${elem.dir}/ + <Directory ${elem.dir}> + Options +Indexes + ${allGranted} + AllowOverride All + </Directory> + ''; + in concatMapStrings makeDirConf hostOpts.servedDirs + } + + ${hostOpts.extraConfig} + '' + ; confFile = pkgs.writeText "httpd.conf" '' ServerRoot ${httpd} + ServerName ${config.networking.hostName} + DefaultRuntimeDir ${runtimeDir}/runtime - DefaultRuntimeDir ${mainCfg.stateDir}/runtime - - PidFile ${mainCfg.stateDir}/httpd.pid + PidFile ${runtimeDir}/httpd.pid ${optionalString (mainCfg.multiProcessingModule != "prefork") '' # mod_cgid requires this. - ScriptSock ${mainCfg.stateDir}/cgisock + ScriptSock ${runtimeDir}/cgisock ''} <IfModule prefork.c> @@ -317,26 +259,21 @@ let </IfModule> ${let - listen = concatMap getListen allHosts; - toStr = listen: "Listen ${listenToString listen}\n"; - uniqueListen = uniqList {inputList = map toStr listen;}; - in concatStrings uniqueListen + toStr = listen: "Listen ${listen.ip}:${toString listen.port} ${if listen.ssl then "https" else "http"}"; + uniqueListen = uniqList {inputList = map toStr listenInfo;}; + in concatStringsSep "\n" uniqueListen } User ${mainCfg.user} Group ${mainCfg.group} ${let - load = {name, path}: "LoadModule ${name}_module ${path}\n"; - allModules = - concatMap (svc: svc.extraModulesPre) allSubservices - ++ map (name: {inherit name; path = "${httpd}/modules/mod_${name}.so";}) apacheModules - ++ optional mainCfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } - ++ optional enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } - ++ optional enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } - ++ concatMap (svc: svc.extraModules) allSubservices - ++ extraForeignModules; - in concatMapStrings load (unique allModules) + mkModule = module: + if isString module then { name = module; path = "${httpd}/modules/mod_${module}.so"; } + else if isAttrs module then { inherit (module) name path; } + else throw "Expecting either a string or attribute set including a name and path."; + in + concatMapStringsSep "\n" (module: "LoadModule ${module.name}_module ${module.path}") (unique (map mkModule modules)) } AddHandler type-map var @@ -372,30 +309,15 @@ let ${allGranted} </Directory> - # Generate directives for the main server. - ${perServerConf true mainCfg} + ${mainCfg.extraConfig} - ${let - makeVirtualHost = vhost: '' - <VirtualHost ${concatStringsSep " " (map listenToString (getListen vhost))}> - ${perServerConf false vhost} - </VirtualHost> - ''; - in concatMapStrings makeVirtualHost mainCfg.virtualHosts - } + ${concatMapStringsSep "\n" mkVHostConf vhosts} ''; - - enablePHP = mainCfg.enablePHP || any (svc: svc.enablePHP) allSubservices; - - enablePerl = mainCfg.enablePerl || any (svc: svc.enablePerl) allSubservices; - - # Generate the PHP configuration file. Should probably be factored # out into a separate module. phpIni = pkgs.runCommand "php.ini" - { options = concatStringsSep "\n" - ([ mainCfg.phpOptions ] ++ (map (svc: svc.phpOptions) allSubservices)); + { options = mainCfg.phpOptions; preferLocalBuild = true; } '' @@ -408,6 +330,26 @@ in { + imports = [ + (mkRemovedOptionModule [ "services" "httpd" "extraSubservices" ] "Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") + (mkRemovedOptionModule [ "services" "httpd" "stateDir" ] "The httpd module now uses /run/httpd as a runtime directory.") + + # virtualHosts options + (mkRemovedOptionModule [ "services" "httpd" "documentRoot" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "enableSSL" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "enableUserDir" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "globalRedirect" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "hostName" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "listen" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "robotsEntries" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "servedDirs" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "servedFiles" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "serverAliases" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerCert" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerChain" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + (mkRemovedOptionModule [ "services" "httpd" "sslServerKey" ] "Please define a virtual host using `services.httpd.virtualHosts`.") + ]; + ###### interface options = { @@ -444,7 +386,7 @@ in type = types.lines; default = ""; description = '' - Cnfiguration lines appended to the generated Apache + Configuration lines appended to the generated Apache configuration file. Note that this mechanism may not work when <option>configFile</option> is overridden. ''; @@ -453,7 +395,12 @@ in extraModules = mkOption { type = types.listOf types.unspecified; default = []; - example = literalExample ''[ "proxy_connect" { name = "php5"; path = "''${pkgs.php}/modules/libphp5.so"; } ]''; + example = literalExample '' + [ + "proxy_connect" + { name = "jk"; path = "''${pkgs.tomcat_connectors}/modules/mod_jk.so"; } + ] + ''; description = '' Additional Apache modules to be used. These can be specified as a string in the case of modules distributed @@ -463,9 +410,25 @@ in ''; }; + adminAddr = mkOption { + type = types.str; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for log files. Possible values are: combined, common, referer, agent. + See <link xlink:href="https://httpd.apache.org/docs/2.4/logs.html"/> for more details. + ''; + }; + logPerVirtualHost = mkOption { type = types.bool; - default = false; + default = true; description = '' If enabled, each virtual host gets its own <filename>access.log</filename> and @@ -500,37 +463,29 @@ in ''; }; - stateDir = mkOption { - type = types.path; - default = "/run/httpd"; - description = '' - Directory for Apache's transient runtime state (such as PID - files). It is created automatically. Note that the default, - <filename>/run/httpd</filename>, is deleted at boot time. - ''; - }; - virtualHosts = mkOption { - type = types.listOf (types.submodule ( - { options = import ./per-server-options.nix { - inherit lib; - forMainServer = false; + type = with types; attrsOf (submodule (import ./per-server-options.nix)); + default = { + localhost = { + documentRoot = "${httpd}/htdocs"; + }; + }; + example = literalExample '' + { + "foo.example.com" = { + forceSSL = true; + documentRoot = "/var/www/foo.example.com" + }; + "bar.example.com" = { + addSSL = true; + documentRoot = "/var/www/bar.example.com"; }; - })); - default = []; - example = [ - { hostName = "foo"; - documentRoot = "/data/webroot-foo"; - } - { hostName = "bar"; - documentRoot = "/data/webroot-bar"; } - ]; + ''; description = '' - Specification of the virtual hosts served by Apache. Each + Specification of the virtual hosts served by Apache. Each element should be an attribute set specifying the - configuration of the virtual host. The available options - are the non-global options permissible for the main host. + configuration of the virtual host. ''; }; @@ -612,17 +567,11 @@ in sslProtocols = mkOption { type = types.str; - default = "All -SSLv2 -SSLv3 -TLSv1"; + default = "All -SSLv2 -SSLv3 -TLSv1 -TLSv1.1"; example = "All -SSLv2 -SSLv3"; description = "Allowed SSL/TLS protocol versions."; }; - } - - # Include the options shared between the main server and virtual hosts. - // (import ./per-server-options.nix { - inherit lib; - forMainServer = true; - }); + }; }; @@ -631,27 +580,54 @@ in config = mkIf config.services.httpd.enable { - assertions = [ { assertion = mainCfg.enableSSL == true - -> mainCfg.sslServerCert != null - && mainCfg.sslServerKey != null; - message = "SSL is enabled for httpd, but sslServerCert and/or sslServerKey haven't been specified."; } - ]; - - warnings = map (cfg: "apache-httpd's extraSubservices option is deprecated. Most existing subservices have been ported to the NixOS module system. Please update your configuration accordingly.") (lib.filter (cfg: cfg.extraSubservices != []) allHosts); + assertions = [ + { + assertion = all (hostOpts: !hostOpts.enableSSL) vhosts; + message = '' + The option `services.httpd.virtualHosts.<name>.enableSSL` no longer has any effect; please remove it. + Select one of `services.httpd.virtualHosts.<name>.addSSL`, `services.httpd.virtualHosts.<name>.forceSSL`, + or `services.httpd.virtualHosts.<name>.onlySSL`. + ''; + } + { + assertion = all (hostOpts: with hostOpts; !(addSSL && onlySSL) && !(forceSSL && onlySSL) && !(addSSL && forceSSL)) vhosts; + message = '' + Options `services.httpd.virtualHosts.<name>.addSSL`, + `services.httpd.virtualHosts.<name>.onlySSL` and `services.httpd.virtualHosts.<name>.forceSSL` + are mutually exclusive. + ''; + } + { + assertion = all (hostOpts: !(hostOpts.enableACME && hostOpts.useACMEHost != null)) vhosts; + message = '' + Options `services.httpd.virtualHosts.<name>.enableACME` and + `services.httpd.virtualHosts.<name>.useACMEHost` are mutually exclusive. + ''; + } + ]; - users.users = optionalAttrs (mainCfg.user == "wwwrun") (singleton - { name = "wwwrun"; + users.users = optionalAttrs (mainCfg.user == "wwwrun") { + wwwrun = { group = mainCfg.group; description = "Apache httpd user"; uid = config.ids.uids.wwwrun; - }); + }; + }; - users.groups = optionalAttrs (mainCfg.group == "wwwrun") (singleton - { name = "wwwrun"; - gid = config.ids.gids.wwwrun; - }); + users.groups = optionalAttrs (mainCfg.group == "wwwrun") { + wwwrun.gid = config.ids.gids.wwwrun; + }; - environment.systemPackages = [httpd] ++ concatMap (svc: svc.extraPath) allSubservices; + security.acme.certs = mapAttrs (name: hostOpts: { + user = mainCfg.user; + group = mkDefault mainCfg.group; + email = if hostOpts.adminAddr != null then hostOpts.adminAddr else mainCfg.adminAddr; + webroot = hostOpts.acmeRoot; + extraDomains = genAttrs hostOpts.serverAliases (alias: null); + postRun = "systemctl reload httpd.service"; + }) (filterAttrs (name: hostOpts: hostOpts.enableACME) mainCfg.virtualHosts); + + environment.systemPackages = [httpd]; services.httpd.phpOptions = '' @@ -666,30 +642,48 @@ in date.timezone = "${config.time.timeZone}" ''; + services.httpd.extraModules = mkBefore [ + # HTTP authentication mechanisms: basic and digest. + "auth_basic" "auth_digest" + + # Authentication: is the user who he claims to be? + "authn_file" "authn_dbm" "authn_anon" + + # Authorization: is the user allowed access? + "authz_user" "authz_groupfile" "authz_host" + + # Other modules. + "ext_filter" "include" "env" "mime_magic" + "cern_meta" "expires" "headers" "usertrack" "setenvif" + "dav" "status" "asis" "info" "dav_fs" + "vhost_alias" "imagemap" "actions" "speling" + "proxy" "proxy_http" + "cache" "cache_disk" + + # For compatibility with old configurations, the new module mod_access_compat is provided. + "access_compat" + ]; + systemd.services.httpd = + let + vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts; + in { description = "Apache HTTPD"; wantedBy = [ "multi-user.target" ]; - after = [ "network.target" "fs.target" ]; + wants = concatLists (map (hostOpts: [ "acme-${hostOpts.hostName}.service" "acme-selfsigned-${hostOpts.hostName}.service" ]) vhostsACME); + after = [ "network.target" "fs.target" ] ++ map (hostOpts: "acme-selfsigned-${hostOpts.hostName}.service") vhostsACME; path = [ httpd pkgs.coreutils pkgs.gnugrep ] - ++ optional enablePHP pkgs.system-sendmail # Needed for PHP's mail() function. - ++ concatMap (svc: svc.extraServerPath) allSubservices; + ++ optional mainCfg.enablePHP pkgs.system-sendmail; # Needed for PHP's mail() function. environment = - optionalAttrs enablePHP { PHPRC = phpIni; } - // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; } - // (listToAttrs (concatMap (svc: svc.globalEnvVars) allSubservices)); + optionalAttrs mainCfg.enablePHP { PHPRC = phpIni; } + // optionalAttrs mainCfg.enableMellon { LD_LIBRARY_PATH = "${pkgs.xmlsec}/lib"; }; preStart = '' - mkdir -m 0750 -p ${mainCfg.stateDir} - [ $(id -u) != 0 ] || chown root.${mainCfg.group} ${mainCfg.stateDir} - - mkdir -m 0750 -p "${mainCfg.stateDir}/runtime" - [ $(id -u) != 0 ] || chown root.${mainCfg.group} "${mainCfg.stateDir}/runtime" - mkdir -m 0700 -p ${mainCfg.logDir} # Get rid of old semaphores. These tend to accumulate across @@ -698,21 +692,18 @@ in for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${mainCfg.user} ' | cut -f2 -d ' '); do ${pkgs.utillinux}/bin/ipcrm -s $i done - - # Run the startup hooks for the subservices. - for i in ${toString (map (svn: svn.startupScript) allSubservices)}; do - echo Running Apache startup hook $i... - $i - done ''; serviceConfig.ExecStart = "@${httpd}/bin/httpd httpd -f ${httpdConf}"; serviceConfig.ExecStop = "${httpd}/bin/httpd -f ${httpdConf} -k graceful-stop"; serviceConfig.ExecReload = "${httpd}/bin/httpd -f ${httpdConf} -k graceful"; + serviceConfig.Group = mainCfg.group; serviceConfig.Type = "forking"; - serviceConfig.PIDFile = "${mainCfg.stateDir}/httpd.pid"; + serviceConfig.PIDFile = "${runtimeDir}/httpd.pid"; serviceConfig.Restart = "always"; serviceConfig.RestartSec = "5s"; + serviceConfig.RuntimeDirectory = "httpd httpd/runtime"; + serviceConfig.RuntimeDirectoryMode = "0750"; }; }; diff --git a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix index 9d747549c27..f2e92cda05f 100644 --- a/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix +++ b/nixos/modules/services/web-servers/apache-httpd/per-server-options.nix @@ -1,180 +1,235 @@ -# 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.) - -{ forMainServer, lib }: - -with lib; - +{ config, lib, name, ... }: +let + inherit (lib) mkOption types; +in { + options = { + + hostName = mkOption { + type = types.str; + default = name; + description = "Canonical hostname for the server."; + }; + + serverAliases = mkOption { + type = types.listOf types.str; + default = []; + example = ["www.example.org" "www.example.org:8080" "example.org"]; + description = '' + Additional names of virtual hosts served by this virtual host configuration. + ''; + }; + + listen = mkOption { + type = with types; listOf (submodule ({ + options = { + port = mkOption { + type = types.port; + description = "Port to listen on"; + }; + ip = mkOption { + type = types.str; + default = "*"; + description = "IP to listen on. 0.0.0.0 for IPv4 only, * for all."; + }; + ssl = mkOption { + type = types.bool; + default = false; + description = "Whether to enable SSL (https) support."; + }; + }; + })); + default = []; + example = [ + { ip = "195.154.1.1"; port = 443; ssl = true;} + { ip = "192.154.1.1"; port = 80; } + { ip = "*"; port = 8080; } + ]; + description = '' + Listen addresses and ports for this virtual host. + <note><para> + This option overrides <literal>addSSL</literal>, <literal>forceSSL</literal> and <literal>onlySSL</literal>. + </para></note> + ''; + }; + + enableSSL = mkOption { + type = types.bool; + visible = false; + default = false; + }; + + addSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable HTTPS in addition to plain HTTP. This will set defaults for + <literal>listen</literal> 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 <literal>listen</literal> to listen on all interfaces on port 443. + ''; + }; + + forceSSL = mkOption { + type = types.bool; + default = false; + description = '' + Whether to add a separate nginx server block that permanently redirects (301) + all plain HTTP traffic to HTTPS. This will set defaults for + <literal>listen</literal> to listen on all interfaces on the respective default + ports (80, 443), where the non-SSL listens are used for the redirect vhosts. + ''; + }; + + enableACME = mkOption { + type = types.bool; + default = false; + description = '' + Whether to ask Let's Encrypt to sign a certificate for this vhost. + Alternately, you can use an existing certificate through <option>useACMEHost</option>. + ''; + }; + + useACMEHost = mkOption { + type = types.nullOr types.str; + default = null; + description = '' + A host of an existing Let's Encrypt certificate to use. + This is useful if you have many subdomains and want to avoid hitting the + <link xlink:href="https://letsencrypt.org/docs/rate-limits/">rate limit</link>. + Alternately, you can generate a certificate through <option>enableACME</option>. + <emphasis>Note that this option does not create any certificates, nor it does add subdomains to existing ones – you will need to create them manually using <xref linkend="opt-security.acme.certs"/>.</emphasis> + ''; + }; + + acmeRoot = mkOption { + type = types.str; + default = "/var/lib/acme/acme-challenges"; + description = "Directory for the acme challenge which is PUBLIC, don't put certs or keys in here"; + }; + + sslServerCert = mkOption { + type = types.path; + example = "/var/host.cert"; + description = "Path to server SSL certificate."; + }; + + sslServerKey = mkOption { + type = types.path; + example = "/var/host.key"; + description = "Path to server SSL certificate key."; + }; + + sslServerChain = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/ca.pem"; + description = "Path to server SSL chain file."; + }; + + adminAddr = mkOption { + type = types.nullOr types.str; + default = null; + example = "admin@example.org"; + description = "E-mail address of the server administrator."; + }; + + documentRoot = mkOption { + type = types.nullOr types.path; + default = null; + example = "/data/webserver/docs"; + description = '' + The path of Apache's document root directory. If left undefined, + an empty directory in the Nix store will be used as root. + ''; + }; + + servedDirs = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/nix"; + dir = "/home/eelco/Dev/nix-homepage"; + } + ]; + description = '' + This option provides a simple way to serve static directories. + ''; + }; + + servedFiles = mkOption { + type = types.listOf types.attrs; + default = []; + example = [ + { urlPath = "/foo/bar.png"; + file = "/home/eelco/some-file.png"; + } + ]; + description = '' + This option provides a simple way to serve individual, static files. + ''; + }; + + extraConfig = mkOption { + type = types.lines; + default = ""; + example = '' + <Directory /home> + Options FollowSymlinks + AllowOverride All + </Directory> + ''; + description = '' + These lines go to httpd.conf verbatim. They will go after + directories and directory aliases defined by default. + ''; + }; + + enableUserDir = mkOption { + type = types.bool; + default = false; + description = '' + Whether to enable serving <filename>~/public_html</filename> as + <literal>/~<replaceable>username</replaceable></literal>. + ''; + }; + + 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. + ''; + }; + + logFormat = mkOption { + type = types.str; + default = "common"; + example = "combined"; + description = '' + Log format for Apache's log files. Possible values are: combined, common, referer, agent. + ''; + }; + + robotsEntries = mkOption { + type = types.lines; + default = ""; + example = "Disallow: /foo/"; + description = '' + Specification of pages to be ignored by web crawlers. See <link + xlink:href='http://www.robotstxt.org/'/> for details. + ''; + }; - hostName = mkOption { - type = types.str; - default = "localhost"; - description = "Canonical hostname for the server."; - }; - - serverAliases = mkOption { - type = types.listOf types.str; - default = []; - example = ["www.example.org" "www.example.org:8080" "example.org"]; - description = '' - Additional names of virtual hosts served by this virtual host configuration. - ''; - }; - - listen = mkOption { - type = types.listOf (types.submodule ( - { - options = { - port = mkOption { - type = types.int; - description = "port to listen on"; - }; - ip = mkOption { - type = types.str; - default = "*"; - description = "Ip to listen on. 0.0.0.0 for ipv4 only, * for all."; - }; - }; - } )); - description = '' - List of { /* ip: "*"; */ port = 80;} to listen on - ''; - - default = []; - }; - - enableSSL = mkOption { - type = types.bool; - default = false; - description = "Whether to enable SSL (https) support."; - }; - - # Note: sslServerCert and sslServerKey can be left empty, but this - # only makes sense for virtual hosts (they will inherit from the - # main server). - - sslServerCert = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/host.cert"; - description = "Path to server SSL certificate."; - }; - - sslServerKey = mkOption { - type = types.path; - example = "/var/host.key"; - description = "Path to server SSL certificate key."; - }; - - sslServerChain = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/ca.pem"; - description = "Path to server SSL chain file."; - }; - - adminAddr = mkOption ({ - type = types.nullOr types.str; - example = "admin@example.org"; - description = "E-mail address of the server administrator."; - } // (if forMainServer then {} else {default = null;})); - - documentRoot = mkOption { - type = types.nullOr types.path; - default = null; - example = "/data/webserver/docs"; - description = '' - The path of Apache's document root directory. If left undefined, - an empty directory in the Nix store will be used as root. - ''; }; - - servedDirs = mkOption { - type = types.listOf types.attrs; - default = []; - example = [ - { urlPath = "/nix"; - dir = "/home/eelco/Dev/nix-homepage"; - } - ]; - description = '' - This option provides a simple way to serve static directories. - ''; - }; - - servedFiles = mkOption { - type = types.listOf types.attrs; - default = []; - example = [ - { urlPath = "/foo/bar.png"; - file = "/home/eelco/some-file.png"; - } - ]; - description = '' - This option provides a simple way to serve individual, static files. - ''; - }; - - extraConfig = mkOption { - type = types.lines; - default = ""; - example = '' - <Directory /home> - Options FollowSymlinks - AllowOverride All - </Directory> - ''; - description = '' - These lines go to httpd.conf verbatim. They will go after - directories and directory aliases defined by default. - ''; - }; - - extraSubservices = mkOption { - type = types.listOf types.unspecified; - default = []; - description = "Extra subservices to enable in the webserver."; - }; - - enableUserDir = mkOption { - type = types.bool; - default = false; - description = '' - Whether to enable serving <filename>~/public_html</filename> as - <literal>/~<replaceable>username</replaceable></literal>. - ''; - }; - - 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. - ''; - }; - - logFormat = mkOption { - type = types.str; - default = "common"; - example = "combined"; - description = '' - Log format for Apache's log files. Possible values are: combined, common, referer, agent. - ''; - }; - - robotsEntries = mkOption { - type = types.lines; - default = ""; - example = "Disallow: /foo/"; - description = '' - Specification of pages to be ignored by web crawlers. See <link - xlink:href='http://www.robotstxt.org/'/> for details. - ''; - }; - } diff --git a/nixos/modules/services/web-servers/hitch/default.nix b/nixos/modules/services/web-servers/hitch/default.nix index a6c4cbea122..1812f225b74 100644 --- a/nixos/modules/services/web-servers/hitch/default.nix +++ b/nixos/modules/services/web-servers/hitch/default.nix @@ -102,7 +102,10 @@ with lib; environment.systemPackages = [ pkgs.hitch ]; - users.users.hitch.group = "hitch"; + users.users.hitch = { + group = "hitch"; + isSystemUser = true; + }; users.groups.hitch = {}; }; } diff --git a/nixos/modules/services/web-servers/nginx/default.nix b/nixos/modules/services/web-servers/nginx/default.nix index b94b338fd4a..c8602e5975b 100644 --- a/nixos/modules/services/web-servers/nginx/default.nix +++ b/nixos/modules/services/web-servers/nginx/default.nix @@ -47,7 +47,7 @@ let '')); configFile = pkgs.writers.writeNginxConfig "nginx.conf" '' - user ${cfg.user} ${cfg.group}; + pid /run/nginx/nginx.pid; error_log ${cfg.logError}; daemon off; @@ -61,7 +61,10 @@ let ${optionalString (cfg.httpConfig == "" && cfg.config == "") '' http { - include ${cfg.package}/conf/mime.types; + # The mime type definitions included with nginx are very incomplete, so + # we use a list of mime types from the mailcap package, which is also + # used by most other Linux distributions by default. + include ${pkgs.mailcap}/etc/nginx/mime.types; include ${cfg.package}/conf/fastcgi.conf; include ${cfg.package}/conf/uwsgi_params; @@ -94,7 +97,6 @@ let ${optionalString (cfg.recommendedGzipSettings) '' gzip on; - gzip_disable "msie6"; gzip_proxied any; gzip_comp_level 5; gzip_types @@ -120,6 +122,14 @@ let include ${recommendedProxyConfig}; ''} + ${optionalString (cfg.mapHashBucketSize != null) '' + map_hash_bucket_size ${toString cfg.mapHashBucketSize}; + ''} + + ${optionalString (cfg.mapHashMaxSize != null) '' + map_hash_max_size ${toString cfg.mapHashMaxSize}; + ''} + # $connection_upgrade is used for websocket proxying map $http_upgrade $connection_upgrade { default upgrade; @@ -168,6 +178,8 @@ let then "/etc/nginx/nginx.conf" else configFile; + execCommand = "${cfg.package}/bin/nginx -c '${configPath}' -p '${cfg.stateDir}'"; + vhosts = concatStringsSep "\n" (mapAttrsToList (vhostName: vhost: let onlySSL = vhost.onlySSL || vhost.enableSSL; @@ -356,12 +368,7 @@ in preStart = mkOption { type = types.lines; - default = '' - test -d ${cfg.stateDir}/logs || mkdir -m 750 -p ${cfg.stateDir}/logs - test `stat -c %a ${cfg.stateDir}` = "750" || chmod 750 ${cfg.stateDir} - test `stat -c %a ${cfg.stateDir}/logs` = "750" || chmod 750 ${cfg.stateDir}/logs - chown -R ${cfg.user}:${cfg.group} ${cfg.stateDir} - ''; + default = ""; description = " Shell commands executed before the service's nginx is started. "; @@ -508,6 +515,23 @@ in ''; }; + mapHashBucketSize = mkOption { + type = types.nullOr (types.enum [ 32 64 128 ]); + default = null; + description = '' + Sets the bucket size for the map variables hash tables. Default + value depends on the processor’s cache line size. + ''; + }; + + mapHashMaxSize = mkOption { + type = types.nullOr types.ints.positive; + default = null; + description = '' + Sets the maximum size of the map variables hash tables. + ''; + }; + resolver = mkOption { type = types.submodule { options = { @@ -646,23 +670,36 @@ in } ]; + systemd.tmpfiles.rules = [ + "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" + "d '${cfg.stateDir}/logs' 0750 ${cfg.user} ${cfg.group} - -" + "Z '${cfg.stateDir}' - ${cfg.user} ${cfg.group} - -" + ]; + systemd.services.nginx = { description = "Nginx Web Server"; wantedBy = [ "multi-user.target" ]; wants = concatLists (map (vhostConfig: ["acme-${vhostConfig.serverName}.service" "acme-selfsigned-${vhostConfig.serverName}.service"]) acmeEnabledVhosts); after = [ "network.target" ] ++ map (vhostConfig: "acme-selfsigned-${vhostConfig.serverName}.service") acmeEnabledVhosts; stopIfChanged = false; - preStart = - '' + preStart = '' ${cfg.preStart} - ${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir} -t - ''; + ${execCommand} -t + ''; serviceConfig = { - ExecStart = "${cfg.package}/bin/nginx -c ${configPath} -p ${cfg.stateDir}"; + ExecStart = execCommand; ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; Restart = "always"; RestartSec = "10s"; StartLimitInterval = "1min"; + # User and group + User = cfg.user; + Group = cfg.group; + # Runtime directory and mode + RuntimeDirectory = "nginx"; + RuntimeDirectoryMode = "0750"; + # Capabilities + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SYS_RESOURCE" ]; }; }; @@ -671,11 +708,18 @@ in }; systemd.services.nginx-config-reload = mkIf cfg.enableReload { - wantedBy = [ "nginx.service" ]; + wants = [ "nginx.service" ]; + wantedBy = [ "multi-user.target" ]; restartTriggers = [ configFile ]; + # commented, because can cause extra delays during activate for this config: + # services.nginx.virtualHosts."_".locations."/".proxyPass = "http://blabla:3000"; + # stopIfChanged = false; + serviceConfig.Type = "oneshot"; + serviceConfig.TimeoutSec = 60; script = '' if ${pkgs.systemd}/bin/systemctl -q is-active nginx.service ; then - ${pkgs.systemd}/bin/systemctl reload nginx.service + ${execCommand} -t && \ + ${pkgs.systemd}/bin/systemctl reload nginx.service fi ''; serviceConfig.RemainAfterExit = true; @@ -696,15 +740,16 @@ in listToAttrs acmePairs ); - users.users = optionalAttrs (cfg.user == "nginx") (singleton - { name = "nginx"; + users.users = optionalAttrs (cfg.user == "nginx") { + nginx = { group = cfg.group; uid = config.ids.uids.nginx; - }); + }; + }; + + users.groups = optionalAttrs (cfg.group == "nginx") { + nginx.gid = config.ids.gids.nginx; + }; - users.groups = optionalAttrs (cfg.group == "nginx") (singleton - { name = "nginx"; - gid = config.ids.gids.nginx; - }); }; } diff --git a/nixos/modules/services/web-servers/nginx/location-options.nix b/nixos/modules/services/web-servers/nginx/location-options.nix index aeb9b1dd79e..3d9e391ecf2 100644 --- a/nixos/modules/services/web-servers/nginx/location-options.nix +++ b/nixos/modules/services/web-servers/nginx/location-options.nix @@ -1,4 +1,4 @@ -# This file defines the options that can be used both for the Apache +# This file defines the options that can be used both for the Nginx # 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.) @@ -67,7 +67,7 @@ with lib; return = mkOption { type = types.nullOr types.str; default = null; - example = "301 http://example.com$request_uri;"; + example = "301 http://example.com$request_uri"; description = '' Adds a return directive, for e.g. redirections. ''; @@ -92,4 +92,3 @@ with lib; }; }; } - diff --git a/nixos/modules/services/web-servers/nginx/vhost-options.nix b/nixos/modules/services/web-servers/nginx/vhost-options.nix index 15b933c984a..455854e2a96 100644 --- a/nixos/modules/services/web-servers/nginx/vhost-options.nix +++ b/nixos/modules/services/web-servers/nginx/vhost-options.nix @@ -1,4 +1,4 @@ -# This file defines the options that can be used both for the Apache +# This file defines the options that can be used both for the Nginx # 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.) @@ -207,6 +207,7 @@ with lib; default = null; description = '' Basic Auth password file for a vhost. + Can be created via: <command>htpasswd -c <filename> <username></command> ''; }; diff --git a/nixos/modules/services/web-servers/phpfpm/default.nix b/nixos/modules/services/web-servers/phpfpm/default.nix index 4ab7e3f0c0a..2c73da10394 100644 --- a/nixos/modules/services/web-servers/phpfpm/default.nix +++ b/nixos/modules/services/web-servers/phpfpm/default.nix @@ -31,7 +31,7 @@ let ''; passAsFile = [ "nixDefaults" "phpOptions" ]; } '' - cat $phpPackage/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out + cat ${poolOpts.phpPackage}/etc/php.ini $nixDefaultsPath $phpOptionsPath > $out ''; poolOpts = { name, ... }: @@ -69,8 +69,6 @@ let phpOptions = mkOption { type = types.lines; - default = cfg.phpOptions; - defaultText = "config.services.phpfpm.phpOptions"; description = '' "Options appended to the PHP configuration file <filename>php.ini</filename> used for this PHP-FPM pool." ''; @@ -137,6 +135,7 @@ let config = { socket = if poolOpts.listen == "" then "${runtimeDir}/${name}.sock" else poolOpts.listen; group = mkDefault poolOpts.user; + phpOptions = mkBefore cfg.phpOptions; settings = mapAttrs (name: mkDefault){ listen = poolOpts.socket; @@ -147,6 +146,10 @@ let }; in { + imports = [ + (mkRemovedOptionModule [ "services" "phpfpm" "poolConfigs" ] "Use services.phpfpm.pools instead.") + (mkRemovedOptionModule [ "services" "phpfpm" "phpIni" ] "") + ]; options = { services.phpfpm = { @@ -263,6 +266,7 @@ in { in { Slice = "phpfpm.slice"; PrivateDevices = true; + PrivateTmp = true; ProtectSystem = "full"; ProtectHome = true; # XXX: We need AF_NETLINK to make the sendmail SUID binary from postfix work diff --git a/nixos/modules/services/web-servers/tomcat.nix b/nixos/modules/services/web-servers/tomcat.nix index 68261c50324..6d12925829f 100644 --- a/nixos/modules/services/web-servers/tomcat.nix +++ b/nixos/modules/services/web-servers/tomcat.nix @@ -194,14 +194,10 @@ in config = mkIf config.services.tomcat.enable { - users.groups = singleton - { name = "tomcat"; - gid = config.ids.gids.tomcat; - }; + users.groups.tomcat.gid = config.ids.gids.tomcat; - users.users = singleton - { name = "tomcat"; - uid = config.ids.uids.tomcat; + users.users.tomcat = + { uid = config.ids.uids.tomcat; description = "Tomcat user"; home = "/homeless-shelter"; extraGroups = cfg.extraGroups; diff --git a/nixos/modules/services/web-servers/traefik.nix b/nixos/modules/services/web-servers/traefik.nix index 8de7df0d446..5b0fc467ea4 100644 --- a/nixos/modules/services/web-servers/traefik.nix +++ b/nixos/modules/services/web-servers/traefik.nix @@ -117,6 +117,7 @@ in { group = "traefik"; home = cfg.dataDir; createHome = true; + isSystemUser = true; }; users.groups.traefik = {}; diff --git a/nixos/modules/services/web-servers/ttyd.nix b/nixos/modules/services/web-servers/ttyd.nix new file mode 100644 index 00000000000..01a01d97a23 --- /dev/null +++ b/nixos/modules/services/web-servers/ttyd.nix @@ -0,0 +1,196 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + + cfg = config.services.ttyd; + + # Command line arguments for the ttyd daemon + args = [ "--port" (toString cfg.port) ] + ++ optionals (cfg.socket != null) [ "--interface" cfg.socket ] + ++ optionals (cfg.interface != null) [ "--interface" cfg.interface ] + ++ [ "--signal" (toString cfg.signal) ] + ++ (concatLists (mapAttrsToList (_k: _v: [ "--client-option" "${_k}=${_v}" ]) cfg.clientOptions)) + ++ [ "--terminal-type" cfg.terminalType ] + ++ optionals cfg.checkOrigin [ "--check-origin" ] + ++ [ "--max-clients" (toString cfg.maxClients) ] + ++ optionals (cfg.indexFile != null) [ "--index" cfg.indexFile ] + ++ optionals cfg.enableIPv6 [ "--ipv6" ] + ++ optionals cfg.enableSSL [ "--ssl-cert" cfg.certFile + "--ssl-key" cfg.keyFile + "--ssl-ca" cfg.caFile ] + ++ [ "--debug" (toString cfg.logLevel) ]; + +in + +{ + + ###### interface + + options = { + services.ttyd = { + enable = mkEnableOption "ttyd daemon"; + + port = mkOption { + type = types.int; + default = 7681; + description = "Port to listen on (use 0 for random port)"; + }; + + socket = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/run/ttyd.sock"; + description = "UNIX domain socket path to bind."; + }; + + interface = mkOption { + type = types.nullOr types.str; + default = null; + example = "eth0"; + description = "Network interface to bind."; + }; + + username = mkOption { + type = types.nullOr types.str; + default = null; + description = "Username for basic authentication."; + }; + + passwordFile = mkOption { + type = types.nullOr types.path; + default = null; + apply = value: if value == null then null else toString value; + description = '' + File containing the password to use for basic authentication. + For insecurely putting the password in the globally readable store use + <literal>pkgs.writeText "ttydpw" "MyPassword"</literal>. + ''; + }; + + signal = mkOption { + type = types.ints.u8; + default = 1; + description = "Signal to send to the command on session close."; + }; + + clientOptions = mkOption { + type = types.attrsOf types.str; + default = {}; + example = literalExample ''{ + fontSize = "16"; + fontFamily = "Fira Code"; + + }''; + description = '' + Attribute set of client options for xtermjs. + <link xlink:href="https://xtermjs.org/docs/api/terminal/interfaces/iterminaloptions/"/> + ''; + }; + + terminalType = mkOption { + type = types.str; + default = "xterm-256color"; + description = "Terminal type to report."; + }; + + checkOrigin = mkOption { + type = types.bool; + default = false; + description = "Whether to allow a websocket connection from a different origin."; + }; + + maxClients = mkOption { + type = types.int; + default = 0; + description = "Maximum clients to support (0, no limit)"; + }; + + indexFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "Custom index.html path"; + }; + + enableIPv6 = mkOption { + type = types.bool; + default = false; + description = "Whether or not to enable IPv6 support."; + }; + + enableSSL = mkOption { + type = types.bool; + default = false; + description = "Whether or not to enable SSL (https) support."; + }; + + certFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "SSL certificate file path."; + }; + + keyFile = mkOption { + type = types.nullOr types.path; + default = null; + apply = value: if value == null then null else toString value; + description = '' + SSL key file path. + For insecurely putting the keyFile in the globally readable store use + <literal>pkgs.writeText "ttydKeyFile" "SSLKEY"</literal>. + ''; + }; + + caFile = mkOption { + type = types.nullOr types.path; + default = null; + description = "SSL CA file path for client certificate verification."; + }; + + logLevel = mkOption { + type = types.int; + default = 7; + description = "Set log level."; + }; + }; + }; + + ###### implementation + + config = mkIf cfg.enable { + + assertions = + [ { assertion = cfg.enableSSL + -> cfg.certFile != null && cfg.keyFile != null && cfg.caFile != null; + message = "SSL is enabled for ttyd, but no certFile, keyFile or caFile has been specefied."; } + { assertion = ! (cfg.interface != null && cfg.socket != null); + message = "Cannot set both interface and socket for ttyd."; } + { assertion = (cfg.username != null) == (cfg.passwordFile != null); + message = "Need to set both username and passwordFile for ttyd"; } + ]; + + systemd.services.ttyd = { + description = "ttyd Web Server Daemon"; + + wantedBy = [ "multi-user.target" ]; + + serviceConfig = { + # Runs login which needs to be run as root + # login: Cannot possibly work without effective root + User = "root"; + }; + + script = if cfg.passwordFile != null then '' + PASSWORD=$(cat ${escapeShellArg cfg.passwordFile}) + ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ + --credential ${escapeShellArg cfg.username}:"$PASSWORD" \ + ${pkgs.shadow}/bin/login + '' + else '' + ${pkgs.ttyd}/bin/ttyd ${lib.escapeShellArgs args} \ + ${pkgs.shadow}/bin/login + ''; + }; + }; +} diff --git a/nixos/modules/services/web-servers/unit/default.nix b/nixos/modules/services/web-servers/unit/default.nix index a4a9d370d64..2303dfa9540 100644 --- a/nixos/modules/services/web-servers/unit/default.nix +++ b/nixos/modules/services/web-servers/unit/default.nix @@ -85,7 +85,7 @@ in { systemd.tmpfiles.rules = [ "d '${cfg.stateDir}' 0750 ${cfg.user} ${cfg.group} - -" "d '${cfg.logDir}' 0750 ${cfg.user} ${cfg.group} - -" - ]; + ]; systemd.services.unit = { description = "Unit App Server"; @@ -93,33 +93,50 @@ in { wantedBy = [ "multi-user.target" ]; path = with pkgs; [ curl ]; preStart = '' - test -f '/run/unit/control.unit.sock' || rm -f '/run/unit/control.unit.sock' + test -f '${cfg.stateDir}/conf.json' || rm -f '${cfg.stateDir}/conf.json' ''; postStart = '' curl -X PUT --data-binary '@${configFile}' --unix-socket '/run/unit/control.unit.sock' 'http://localhost/config' ''; serviceConfig = { - User = cfg.user; - Group = cfg.group; - AmbientCapabilities = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; - CapabilityBoundingSet = "CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID"; ExecStart = '' ${cfg.package}/bin/unitd --control 'unix:/run/unit/control.unit.sock' --pid '/run/unit/unit.pid' \ --log '${cfg.logDir}/unit.log' --state '${cfg.stateDir}' --no-daemon \ --user ${cfg.user} --group ${cfg.group} ''; + # User and group + User = cfg.user; + Group = cfg.group; + # Capabilities + AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" "CAP_SETGID" "CAP_SETUID" ]; + # Security + NoNewPrivileges = true; + # Sanboxing + ProtectSystem = "full"; + ProtectHome = true; RuntimeDirectory = "unit"; RuntimeDirectoryMode = "0750"; + PrivateTmp = true; + PrivateDevices = true; + ProtectHostname = true; + ProtectKernelTunables = true; + ProtectKernelModules = true; + ProtectControlGroups = true; + LockPersonality = true; + MemoryDenyWriteExecute = true; + RestrictRealtime = true; + PrivateMounts = true; }; }; - users.users = optionalAttrs (cfg.user == "unit") (singleton { - name = "unit"; - group = cfg.group; - }); + users.users = optionalAttrs (cfg.user == "unit") { + unit.group = cfg.group; + isSystemUser = true; + }; + + users.groups = optionalAttrs (cfg.group == "unit") { + unit = { }; + }; - users.groups = optionalAttrs (cfg.group == "unit") (singleton { - name = "unit"; - }); }; } diff --git a/nixos/modules/services/web-servers/uwsgi.nix b/nixos/modules/services/web-servers/uwsgi.nix index 66537b29cd2..3481b5e6040 100644 --- a/nixos/modules/services/web-servers/uwsgi.nix +++ b/nixos/modules/services/web-servers/uwsgi.nix @@ -147,16 +147,16 @@ in { }; }; - users.users = optionalAttrs (cfg.user == "uwsgi") (singleton - { name = "uwsgi"; + users.users = optionalAttrs (cfg.user == "uwsgi") { + uwsgi = { group = cfg.group; uid = config.ids.uids.uwsgi; - }); + }; + }; - users.groups = optionalAttrs (cfg.group == "uwsgi") (singleton - { name = "uwsgi"; - gid = config.ids.gids.uwsgi; - }); + users.groups = optionalAttrs (cfg.group == "uwsgi") { + uwsgi.gid = config.ids.gids.uwsgi; + }; services.uwsgi.package = pkgs.uwsgi.override { inherit (cfg) plugins; diff --git a/nixos/modules/services/web-servers/varnish/default.nix b/nixos/modules/services/web-servers/varnish/default.nix index 63f967185c2..01fe3d12917 100644 --- a/nixos/modules/services/web-servers/varnish/default.nix +++ b/nixos/modules/services/web-servers/varnish/default.nix @@ -15,8 +15,8 @@ in package = mkOption { type = types.package; - default = pkgs.varnish5; - defaultText = "pkgs.varnish5"; + default = pkgs.varnish; + defaultText = "pkgs.varnish"; description = '' The package to use ''; @@ -48,7 +48,7 @@ in extraModules = mkOption { type = types.listOf types.package; default = []; - example = literalExample "[ pkgs.varnish5Packages.geoip ]"; + example = literalExample "[ pkgs.varnishPackages.geoip ]"; description = " Varnish modules (except 'std'). "; |