diff options
Diffstat (limited to 'nixos/modules/services/web-servers/apache-httpd/default.nix')
-rw-r--r-- | nixos/modules/services/web-servers/apache-httpd/default.nix | 121 |
1 files changed, 84 insertions, 37 deletions
diff --git a/nixos/modules/services/web-servers/apache-httpd/default.nix b/nixos/modules/services/web-servers/apache-httpd/default.nix index fc4c2945394..df7035c03cc 100644 --- a/nixos/modules/services/web-servers/apache-httpd/default.nix +++ b/nixos/modules/services/web-servers/apache-httpd/default.nix @@ -6,6 +6,8 @@ let cfg = config.services.httpd; + certs = config.security.acme.certs; + runtimeDir = "/run/httpd"; pkg = cfg.package.out; @@ -13,19 +15,26 @@ let apachectl = pkgs.runCommand "apachectl" { meta.priority = -1; } '' mkdir -p $out/bin cp ${pkg}/bin/apachectl $out/bin/apachectl - sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f ${httpdConf}|' + sed -i $out/bin/apachectl -e 's|$HTTPD -t|$HTTPD -t -f /etc/httpd/httpd.conf|' ''; - httpdConf = cfg.configFile; - php = cfg.phpPackage.override { apacheHttpd = pkg; }; - phpMajorVersion = lib.versions.major (lib.getVersion php); + phpModuleName = let + majorVersion = lib.versions.major (lib.getVersion php); + in (if majorVersion == "8" then "php" else "php${majorVersion}"); mod_perl = pkgs.apacheHttpdPackages.mod_perl.override { apacheHttpd = pkg; }; vhosts = attrValues cfg.virtualHosts; + # certName is used later on to determine systemd service names. + acmeEnabledVhosts = map (hostOpts: hostOpts // { + certName = if hostOpts.useACMEHost != null then hostOpts.useACMEHost else hostOpts.hostName; + }) (filter (hostOpts: hostOpts.enableACME || hostOpts.useACMEHost != null) vhosts); + + dependentCertNames = unique (map (hostOpts: hostOpts.certName) acmeEnabledVhosts); + mkListenInfo = hostOpts: if hostOpts.listen != [] then hostOpts.listen else ( @@ -54,7 +63,7 @@ let ++ optional enableSSL "ssl" ++ optional enableUserDir "userdir" ++ optional cfg.enableMellon { name = "auth_mellon"; path = "${pkgs.apacheHttpdPackages.mod_auth_mellon}/modules/mod_auth_mellon.so"; } - ++ optional cfg.enablePHP { name = "php${phpMajorVersion}"; path = "${php}/modules/libphp${phpMajorVersion}.so"; } + ++ optional cfg.enablePHP { name = phpModuleName; path = "${php}/modules/lib${phpModuleName}.so"; } ++ optional cfg.enablePerl { name = "perl"; path = "${mod_perl}/modules/mod_perl.so"; } ++ cfg.extraModules; @@ -117,6 +126,17 @@ let </IfModule> ''; + luaSetPaths = let + # support both lua and lua.withPackages derivations + luaversion = cfg.package.lua5.lua.luaversion or cfg.package.lua5.luaversion; + in + '' + <IfModule mod_lua.c> + LuaPackageCPath ${cfg.package.lua5}/lib/lua/${luaversion}/?.so + LuaPackagePath ${cfg.package.lua5}/share/lua/${luaversion}/?.lua + </IfModule> + ''; + mkVHostConf = hostOpts: let adminAddr = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; @@ -125,13 +145,13 @@ let 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 + if hostOpts.enableACME then certs.${hostOpts.hostName}.directory + else if hostOpts.useACMEHost != null then certs.${hostOpts.useACMEHost}.directory else abort "This case should never happen."; - sslServerCert = if useACME then "${sslCertDir}/full.pem" else hostOpts.sslServerCert; + sslServerCert = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerCert; sslServerKey = if useACME then "${sslCertDir}/key.pem" else hostOpts.sslServerKey; - sslServerChain = if useACME then "${sslCertDir}/fullchain.pem" else hostOpts.sslServerChain; + sslServerChain = if useACME then "${sslCertDir}/chain.pem" else hostOpts.sslServerChain; acmeChallenge = optionalString useACME '' Alias /.well-known/acme-challenge/ "${hostOpts.acmeRoot}/.well-known/acme-challenge/" @@ -182,7 +202,7 @@ let let documentRoot = if hostOpts.documentRoot != null then hostOpts.documentRoot - else pkgs.runCommand "empty" { preferLocalBuild = true; } "mkdir -p $out" + else pkgs.emptyDirectory ; mkLocations = locations: concatStringsSep "\n" (map (config: '' @@ -317,6 +337,8 @@ let ${sslConf} + ${optionalString cfg.package.luaSupport luaSetPaths} + # Fascist default - deny access to everything. <Directory /> Options FollowSymLinks @@ -347,7 +369,6 @@ let cat ${php.phpIni} > $out echo "$options" >> $out ''; - in @@ -647,15 +668,20 @@ in wwwrun.gid = config.ids.gids.wwwrun; }; - security.acme.certs = mapAttrs (name: hostOpts: { - user = cfg.user; - group = mkDefault cfg.group; - email = if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr; - webroot = hostOpts.acmeRoot; - extraDomains = genAttrs hostOpts.serverAliases (alias: null); - postRun = "systemctl reload httpd.service"; - }) (filterAttrs (name: hostOpts: hostOpts.enableACME) cfg.virtualHosts); - + security.acme.certs = let + acmePairs = map (hostOpts: nameValuePair hostOpts.hostName { + group = mkDefault cfg.group; + webroot = hostOpts.acmeRoot; + extraDomainNames = hostOpts.serverAliases; + # Use the vhost-specific email address if provided, otherwise let + # security.acme.email or security.acme.certs.<cert>.email be used. + email = mkOverride 2000 (if hostOpts.adminAddr != null then hostOpts.adminAddr else cfg.adminAddr); + # Filter for enableACME-only vhosts. Don't want to create dud certs + }) (filter (hostOpts: hostOpts.useACMEHost == null) acmeEnabledVhosts); + in listToAttrs acmePairs; + + # httpd requires a stable path to the configuration file for reloads + environment.etc."httpd/httpd.conf".source = cfg.configFile; environment.systemPackages = [ apachectl pkg @@ -682,9 +708,6 @@ in services.httpd.phpOptions = '' - ; Needed for PHP's mail() function. - sendmail_path = ${pkgs.system-sendmail}/bin/sendmail -t -i - ; Don't advertise PHP expose_php = off '' + optionalString (config.time.timeZone != null) '' @@ -724,16 +747,13 @@ in "Z '${cfg.logDir}' - ${svc.User} ${svc.Group}" ]; - systemd.services.httpd = - let - vhostsACME = filter (hostOpts: hostOpts.enableACME) vhosts; - in - { description = "Apache HTTPD"; - + systemd.services.httpd = { + description = "Apache HTTPD"; wantedBy = [ "multi-user.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; - before = map (hostOpts: "acme-${hostOpts.hostName}.service") vhostsACME; + wants = concatLists (map (certName: [ "acme-finished-${certName}.target" ]) dependentCertNames); + after = [ "network.target" ] ++ map (certName: "acme-selfsigned-${certName}.service") dependentCertNames; + before = map (certName: "acme-${certName}.service") dependentCertNames; + restartTriggers = [ cfg.configFile ]; path = [ pkg pkgs.coreutils pkgs.gnugrep ]; @@ -746,15 +766,15 @@ in # Get rid of old semaphores. These tend to accumulate across # server restarts, eventually preventing it from restarting # successfully. - for i in $(${pkgs.utillinux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do - ${pkgs.utillinux}/bin/ipcrm -s $i + for i in $(${pkgs.util-linux}/bin/ipcs -s | grep ' ${cfg.user} ' | cut -f2 -d ' '); do + ${pkgs.util-linux}/bin/ipcrm -s $i done ''; serviceConfig = { - ExecStart = "@${pkg}/bin/httpd httpd -f ${httpdConf}"; - ExecStop = "${pkg}/bin/httpd -f ${httpdConf} -k graceful-stop"; - ExecReload = "${pkg}/bin/httpd -f ${httpdConf} -k graceful"; + ExecStart = "@${pkg}/bin/httpd httpd -f /etc/httpd/httpd.conf"; + ExecStop = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful-stop"; + ExecReload = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -k graceful"; User = cfg.user; Group = cfg.group; Type = "forking"; @@ -767,5 +787,32 @@ in }; }; + # postRun hooks on cert renew can't be used to restart Apache since renewal + # runs as the unprivileged acme user. sslTargets are added to wantedBy + before + # which allows the acme-finished-$cert.target to signify the successful updating + # of certs end-to-end. + systemd.services.httpd-config-reload = let + sslServices = map (certName: "acme-${certName}.service") dependentCertNames; + sslTargets = map (certName: "acme-finished-${certName}.target") dependentCertNames; + in mkIf (sslServices != []) { + wantedBy = sslServices ++ [ "multi-user.target" ]; + # Before the finished targets, after the renew services. + # This service might be needed for HTTP-01 challenges, but we only want to confirm + # certs are updated _after_ config has been reloaded. + before = sslTargets; + after = sslServices; + restartTriggers = [ cfg.configFile ]; + # Block reloading if not all certs exist yet. + # Happens when config changes add new vhosts/certs. + unitConfig.ConditionPathExists = map (certName: certs.${certName}.directory + "/fullchain.pem") dependentCertNames; + serviceConfig = { + Type = "oneshot"; + TimeoutSec = 60; + ExecCondition = "/run/current-system/systemd/bin/systemctl -q is-active httpd.service"; + ExecStartPre = "${pkg}/bin/httpd -f /etc/httpd/httpd.conf -t"; + ExecStart = "/run/current-system/systemd/bin/systemctl reload httpd.service"; + }; + }; + }; } |