diff options
44 files changed, 558 insertions, 1066 deletions
diff --git a/nixos/doc/manual/configuration/profiles/qemu-guest.xml b/nixos/doc/manual/configuration/profiles/qemu-guest.xml index 5d055c45d2d8..3ed97b94b510 100644 --- a/nixos/doc/manual/configuration/profiles/qemu-guest.xml +++ b/nixos/doc/manual/configuration/profiles/qemu-guest.xml @@ -11,8 +11,7 @@ </para> <para> - It makes virtio modules available on the initrd, sets the system time from - the hardware clock to work around a bug in qemu-kvm, and - <link linkend="opt-security.rngd.enable">enables rngd</link>. + It makes virtio modules available on the initrd and sets the system time from + the hardware clock to work around a bug in qemu-kvm. </para> </section> diff --git a/nixos/doc/manual/release-notes/rl-2103.xml b/nixos/doc/manual/release-notes/rl-2103.xml index 0458b3564a9f..24a0281310c6 100644 --- a/nixos/doc/manual/release-notes/rl-2103.xml +++ b/nixos/doc/manual/release-notes/rl-2103.xml @@ -612,6 +612,15 @@ self: super: </listitem> <listitem> <para> + In the ACME module, the data used to build the hash for the account + directory has changed to accomodate new features to reduce account + rate limit issues. This will trigger new account creation on the first + rebuild following this update. No issues are expected to arise from this, + thanks to the new account creation handling. + </para> + </listitem> + <listitem> + <para> <xref linkend="opt-users.users._name_.createHome" /> now always ensures home directory permissions to be <literal>0700</literal>. Permissions had previously been ignored for already existing home directories, possibly leaving them readable by others. The option's description was incorrect regarding ownership management and has been simplified greatly. diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix index feb9c68301d5..a0f5ce72f339 100644 --- a/nixos/modules/misc/ids.nix +++ b/nixos/modules/misc/ids.nix @@ -71,7 +71,7 @@ in #utmp = 29; # unused # ddclient = 30; # converted to DynamicUser = true davfs2 = 31; - #disnix = 33; # unused + #disnix = 33; # module removed osgi = 34; tor = 35; cups = 36; @@ -387,7 +387,7 @@ in utmp = 29; # ddclient = 30; # converted to DynamicUser = true davfs2 = 31; - disnix = 33; + #disnix = 33; # module removed osgi = 34; tor = 35; #cups = 36; # unused diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 3f0453a372a0..c7a8f6b2f7c3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -450,8 +450,6 @@ ./services/misc/devmon.nix ./services/misc/dictd.nix ./services/misc/dwm-status.nix - ./services/misc/dysnomia.nix - ./services/misc/disnix.nix ./services/misc/docker-registry.nix ./services/misc/domoticz.nix ./services/misc/errbot.nix diff --git a/nixos/modules/profiles/qemu-guest.nix b/nixos/modules/profiles/qemu-guest.nix index 0ea70107f717..d4335edfcf2d 100644 --- a/nixos/modules/profiles/qemu-guest.nix +++ b/nixos/modules/profiles/qemu-guest.nix @@ -1,7 +1,7 @@ # Common configuration for virtual machines running under QEMU (using # virtio). -{ lib, ... }: +{ ... }: { boot.initrd.availableKernelModules = [ "virtio_net" "virtio_pci" "virtio_mmio" "virtio_blk" "virtio_scsi" "9p" "9pnet_virtio" ]; @@ -14,6 +14,4 @@ # to the *boot time* of the host). hwclock -s ''; - - security.rngd.enable = lib.mkDefault false; } diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix index 8e646ae1567e..6b62e5043caf 100644 --- a/nixos/modules/security/acme.nix +++ b/nixos/modules/security/acme.nix @@ -7,6 +7,11 @@ let numCerts = length (builtins.attrNames cfg.certs); _24hSecs = 60 * 60 * 24; + # Used to make unique paths for each cert/account config set + mkHash = with builtins; val: substring 0 20 (hashString "sha256" val); + mkAccountHash = acmeServer: data: mkHash "${toString acmeServer} ${data.keyType} ${data.email}"; + accountDirRoot = "/var/lib/acme/.lego/accounts/"; + # There are many services required to make cert renewals work. # They all follow a common structure: # - They inherit this commonServiceConfig @@ -19,7 +24,7 @@ let Type = "oneshot"; User = "acme"; Group = mkDefault "acme"; - UMask = 0027; + UMask = 0023; StateDirectoryMode = 750; ProtectSystem = "full"; PrivateTmp = true; @@ -54,23 +59,35 @@ let ''; }; - # Previously, all certs were owned by whatever user was configured in - # config.security.acme.certs.<cert>.user. Now everything is owned by and - # run by the acme user. - userMigrationService = { - description = "Fix owner and group of all ACME certificates"; - - script = with builtins; concatStringsSep "\n" (mapAttrsToList (cert: data: '' - for fixpath in /var/lib/acme/${escapeShellArg cert} /var/lib/acme/.lego/${escapeShellArg cert}; do + # Ensures that directories which are shared across all certs + # exist and have the correct user and group, since group + # is configurable on a per-cert basis. + userMigrationService = let + script = with builtins; '' + chown -R acme .lego/accounts + '' + (concatStringsSep "\n" (mapAttrsToList (cert: data: '' + for fixpath in ${escapeShellArg cert} .lego/${escapeShellArg cert}; do if [ -d "$fixpath" ]; then chmod -R u=rwX,g=rX,o= "$fixpath" chown -R acme:${data.group} "$fixpath" fi done - '') certConfigs); + '') certConfigs)); + in { + description = "Fix owner and group of all ACME certificates"; - # We don't want this to run every time a renewal happens - serviceConfig.RemainAfterExit = true; + serviceConfig = commonServiceConfig // { + # We don't want this to run every time a renewal happens + RemainAfterExit = true; + + # These StateDirectory entries negate the need for tmpfiles + StateDirectory = [ "acme" "acme/.lego" "acme/.lego/accounts" ]; + StateDirectoryMode = 755; + WorkingDirectory = "/var/lib/acme"; + + # Run the start script as root + ExecStart = "+" + (pkgs.writeShellScript "acme-fixperms" script); + }; }; certToConfig = cert: data: let @@ -101,11 +118,10 @@ let ${toString acmeServer} ${toString data.dnsProvider} ${toString data.ocspMustStaple} ${data.keyType} ''; - mkHash = with builtins; val: substring 0 20 (hashString "sha256" val); certDir = mkHash hashData; domainHash = mkHash "${concatStringsSep " " extraDomains} ${data.domain}"; - othersHash = mkHash "${toString acmeServer} ${data.keyType} ${data.email}"; - accountDir = "/var/lib/acme/.lego/accounts/" + othersHash; + accountHash = (mkAccountHash acmeServer data); + accountDir = accountDirRoot + accountHash; protocolOpts = if useDns then ( [ "--dns" data.dnsProvider ] @@ -142,9 +158,8 @@ let ); in { - inherit accountDir selfsignedDeps; + inherit accountHash cert selfsignedDeps; - webroot = data.webroot; group = data.group; renewTimer = { @@ -184,7 +199,10 @@ let StateDirectory = "acme/${cert}"; - BindPaths = "/var/lib/acme/.minica:/tmp/ca /var/lib/acme/${cert}:/tmp/${keyName}"; + BindPaths = [ + "/var/lib/acme/.minica:/tmp/ca" + "/var/lib/acme/${cert}:/tmp/${keyName}" + ]; }; # Working directory will be /tmp @@ -222,16 +240,22 @@ let serviceConfig = commonServiceConfig // { Group = data.group; - # AccountDir dir will be created by tmpfiles to ensure correct permissions - # And to avoid deletion during systemctl clean - # acme/.lego/${cert} is listed so that it is deleted during systemctl clean - StateDirectory = "acme/${cert} acme/.lego/${cert} acme/.lego/${cert}/${certDir}"; + # Keep in mind that these directories will be deleted if the user runs + # systemctl clean --what=state + # acme/.lego/${cert} is listed for this reason. + StateDirectory = [ + "acme/${cert}" + "acme/.lego/${cert}" + "acme/.lego/${cert}/${certDir}" + "acme/.lego/accounts/${accountHash}" + ]; # Needs to be space separated, but can't use a multiline string because that'll include newlines - BindPaths = - "${accountDir}:/tmp/accounts " + - "/var/lib/acme/${cert}:/tmp/out " + - "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates "; + BindPaths = [ + "${accountDir}:/tmp/accounts" + "/var/lib/acme/${cert}:/tmp/out" + "/var/lib/acme/.lego/${cert}/${certDir}:/tmp/certificates" + ]; # Only try loading the credentialsFile if the dns challenge is enabled EnvironmentFile = mkIf useDns data.credentialsFile; @@ -248,13 +272,18 @@ let # Working directory will be /tmp script = '' - set -euo pipefail + set -euxo pipefail + + ${optionalString (data.webroot != null) '' + # Ensure the webroot exists + mkdir -p '${data.webroot}/.well-known/acme-challenge' + chown 'acme:${data.group}' ${data.webroot}/{.well-known,.well-known/acme-challenge} + ''} echo '${domainHash}' > domainhash.txt # Check if we can renew - # Certificates and account credentials must exist - if [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a "$(ls -1 accounts)" ]; then + if [ -e 'certificates/${keyName}.key' -a -e 'certificates/${keyName}.crt' -a -n "$(ls -1 accounts)" ]; then # When domains are updated, there's no need to do a full # Lego run, but it's likely renew won't work if days is too low. @@ -664,21 +693,33 @@ in { systemd.timers = mapAttrs' (cert: conf: nameValuePair "acme-${cert}" conf.renewTimer) certConfigs; - # .lego and .lego/accounts specified to fix any incorrect permissions - systemd.tmpfiles.rules = [ - "d /var/lib/acme/.lego - acme acme" - "d /var/lib/acme/.lego/accounts - acme acme" - ] ++ (unique (concatMap (conf: [ - "d ${conf.accountDir} - acme acme" - ] ++ (optional (conf.webroot != null) "d ${conf.webroot}/.well-known/acme-challenge - acme ${conf.group}") - ) (attrValues certConfigs))); - - # Create some targets which can be depended on to be "active" after cert renewals - systemd.targets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" { - wantedBy = [ "default.target" ]; - requires = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps; - after = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps; - }) certConfigs; + systemd.targets = let + # Create some targets which can be depended on to be "active" after cert renewals + finishedTargets = mapAttrs' (cert: conf: nameValuePair "acme-finished-${cert}" { + wantedBy = [ "default.target" ]; + requires = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps; + after = [ "acme-${cert}.service" ] ++ conf.selfsignedDeps; + }) certConfigs; + + # Create targets to limit the number of simultaneous account creations + # How it works: + # - Pick a "leader" cert service, which will be in charge of creating the account, + # and run first (requires + after) + # - Make all other cert services sharing the same account wait for the leader to + # finish before starting (requiredBy + before). + # Using a target here is fine - account creation is a one time event. Even if + # systemd clean --what=state is used to delete the account, so long as the user + # then runs one of the cert services, there won't be any issues. + accountTargets = mapAttrs' (hash: confs: let + leader = "acme-${(builtins.head confs).cert}.service"; + dependantServices = map (conf: "acme-${conf.cert}.service") (builtins.tail confs); + in nameValuePair "acme-account-${hash}" { + requiredBy = dependantServices; + before = dependantServices; + requires = [ leader ]; + after = [ leader ]; + }) (groupBy (conf: conf.accountHash) (attrValues certConfigs)); + in finishedTargets // accountTargets; }) ]; diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml index f24811291728..b34cbdafb2d3 100644 --- a/nixos/modules/security/acme.xml +++ b/nixos/modules/security/acme.xml @@ -162,6 +162,9 @@ services.httpd = { <xref linkend="opt-security.acme.certs"/>."foo.example.com" = { <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/lib/acme/.challenges"; <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com"; + # Ensure that the web server you use can read the generated certs + # Take a look at the <link linkend="opt-services.nginx.group">group</link> option for the web server you choose. + <link linkend="opt-security.acme.certs._name_.group">group</link> = "nginx"; # Since we have a wildcard vhost to handle port 80, # we can generate certs for anything! # Just make sure your DNS resolves them. @@ -257,10 +260,11 @@ chmod 400 /var/lib/secrets/certs.secret <para> Should you need to regenerate a particular certificate in a hurry, such as when a vulnerability is found in Let's Encrypt, there is now a convenient - mechanism for doing so. Running <literal>systemctl clean acme-example.com.service</literal> - will remove all certificate files for the given domain, allowing you to then - <literal>systemctl start acme-example.com.service</literal> to generate fresh - ones. + mechanism for doing so. Running + <literal>systemctl clean --what=state acme-example.com.service</literal> + will remove all certificate files and the account data for the given domain, + allowing you to then <literal>systemctl start acme-example.com.service</literal> + to generate fresh ones. </para> </section> <section xml:id="module-security-acme-fix-jws"> diff --git a/nixos/modules/services/misc/disnix.nix b/nixos/modules/services/misc/disnix.nix deleted file mode 100644 index 41483d80a2dd..000000000000 --- a/nixos/modules/services/misc/disnix.nix +++ /dev/null @@ -1,98 +0,0 @@ -# Disnix server -{ config, lib, pkgs, ... }: - -with lib; - -let - - cfg = config.services.disnix; - -in - -{ - - ###### interface - - options = { - - services.disnix = { - - enable = mkEnableOption "Disnix"; - - enableMultiUser = mkOption { - type = types.bool; - default = true; - description = "Whether to support multi-user mode by enabling the Disnix D-Bus service"; - }; - - useWebServiceInterface = mkEnableOption "the DisnixWebService interface running on Apache Tomcat"; - - package = mkOption { - type = types.path; - description = "The Disnix package"; - default = pkgs.disnix; - defaultText = "pkgs.disnix"; - }; - - enableProfilePath = mkEnableOption "exposing the Disnix profiles in the system's PATH"; - - profiles = mkOption { - type = types.listOf types.string; - default = [ "default" ]; - example = [ "default" ]; - description = "Names of the Disnix profiles to expose in the system's PATH"; - }; - }; - - }; - - ###### implementation - - config = mkIf cfg.enable { - dysnomia.enable = true; - - environment.systemPackages = [ pkgs.disnix ] ++ optional cfg.useWebServiceInterface pkgs.DisnixWebService; - environment.variables.PATH = lib.optionals cfg.enableProfilePath (map (profileName: "/nix/var/nix/profiles/disnix/${profileName}/bin" ) cfg.profiles); - - services.dbus.enable = true; - services.dbus.packages = [ pkgs.disnix ]; - - services.tomcat.enable = cfg.useWebServiceInterface; - services.tomcat.extraGroups = [ "disnix" ]; - services.tomcat.javaOpts = "${optionalString cfg.useWebServiceInterface "-Djava.library.path=${pkgs.libmatthew_java}/lib/jni"} "; - services.tomcat.sharedLibs = optional cfg.useWebServiceInterface "${pkgs.DisnixWebService}/share/java/DisnixConnection.jar" - ++ optional cfg.useWebServiceInterface "${pkgs.dbus_java}/share/java/dbus.jar"; - services.tomcat.webapps = optional cfg.useWebServiceInterface pkgs.DisnixWebService; - - users.groups.disnix.gid = config.ids.gids.disnix; - - systemd.services = { - disnix = mkIf cfg.enableMultiUser { - description = "Disnix server"; - wants = [ "dysnomia.target" ]; - wantedBy = [ "multi-user.target" ]; - after = [ "dbus.service" ] - ++ optional config.services.httpd.enable "httpd.service" - ++ optional config.services.mysql.enable "mysql.service" - ++ optional config.services.postgresql.enable "postgresql.service" - ++ optional config.services.tomcat.enable "tomcat.service" - ++ optional config.services.svnserve.enable "svnserve.service" - ++ optional config.services.mongodb.enable "mongodb.service" - ++ optional config.services.influxdb.enable "influxdb.service"; - - restartIfChanged = false; - - path = [ config.nix.package cfg.package config.dysnomia.package "/run/current-system/sw" ]; - - environment = { - HOME = "/root"; - } - // (if config.environment.variables ? DYSNOMIA_CONTAINERS_PATH then { inherit (config.environment.variables) DYSNOMIA_CONTAINERS_PATH; } else {}) - // (if config.environment.variables ? DYSNOMIA_MODULES_PATH then { inherit (config.environment.variables) DYSNOMIA_MODULES_PATH; } else {}); - - serviceConfig.ExecStart = "${cfg.package}/bin/disnix-service"; - }; - - }; - }; -} diff --git a/nixos/modules/services/misc/dysnomia.nix b/nixos/modules/services/misc/dysnomia.nix deleted file mode 100644 index eb94791fbbff..000000000000 --- a/nixos/modules/services/misc/dysnomia.nix +++ /dev/null @@ -1,257 +0,0 @@ -{pkgs, lib, config, ...}: - -with lib; - -let - cfg = config.dysnomia; - - printProperties = properties: - concatMapStrings (propertyName: - let - property = properties.${propertyName}; - in - if isList property then "${propertyName}=(${lib.concatMapStrings (elem: "\"${toString elem}\" ") (properties.${propertyName})})\n" - else "${propertyName}=\"${toString property}\"\n" - ) (builtins.attrNames properties); - - properties = pkgs.stdenv.mkDerivation { - name = "dysnomia-properties"; - buildCommand = '' - cat > $out << "EOF" - ${printProperties cfg.properties} - EOF - ''; - }; - - containersDir = pkgs.stdenv.mkDerivation { - name = "dysnomia-containers"; - buildCommand = '' - mkdir -p $out - cd $out - - ${concatMapStrings (containerName: - let - containerProperties = cfg.containers.${containerName}; - in - '' - cat > ${containerName} <<EOF - ${printProperties containerProperties} - type=${containerName} - EOF - '' - ) (builtins.attrNames cfg.containers)} - ''; - }; - - linkMutableComponents = {containerName}: - '' - mkdir ${containerName} - - ${concatMapStrings (componentName: - let - component = cfg.components.${containerName}.${componentName}; - in - "ln -s ${component} ${containerName}/${componentName}\n" - ) (builtins.attrNames (cfg.components.${containerName} or {}))} - ''; - - componentsDir = pkgs.stdenv.mkDerivation { - name = "dysnomia-components"; - buildCommand = '' - mkdir -p $out - cd $out - - ${concatMapStrings (containerName: - linkMutableComponents { inherit containerName; } - ) (builtins.attrNames cfg.components)} - ''; - }; - - dysnomiaFlags = { - enableApacheWebApplication = config.services.httpd.enable; - enableAxis2WebService = config.services.tomcat.axis2.enable; - enableDockerContainer = config.virtualisation.docker.enable; - enableEjabberdDump = config.services.ejabberd.enable; - enableMySQLDatabase = config.services.mysql.enable; - enablePostgreSQLDatabase = config.services.postgresql.enable; - enableTomcatWebApplication = config.services.tomcat.enable; - enableMongoDatabase = config.services.mongodb.enable; - enableSubversionRepository = config.services.svnserve.enable; - enableInfluxDatabase = config.services.influxdb.enable; - }; -in -{ - options = { - dysnomia = { - - enable = mkOption { - type = types.bool; - default = false; - description = "Whether to enable Dysnomia"; - }; - - enableAuthentication = mkOption { - type = types.bool; - default = false; - description = "Whether to publish privacy-sensitive authentication credentials"; - }; - - package = mkOption { - type = types.path; - description = "The Dysnomia package"; - }; - - properties = mkOption { - description = "An attribute set in which each attribute represents a machine property. Optionally, these values can be shell substitutions."; - default = {}; - }; - - containers = mkOption { - description = "An attribute set in which each key represents a container and each value an attribute set providing its configuration properties"; - default = {}; - }; - - components = mkOption { - description = "An atttribute set in which each key represents a container and each value an attribute set in which each key represents a component and each value a derivation constructing its initial state"; - default = {}; - }; - - extraContainerProperties = mkOption { - description = "An attribute set providing additional container settings in addition to the default properties"; - default = {}; - }; - - extraContainerPaths = mkOption { - description = "A list of paths containing additional container configurations that are ad |