summary refs log tree commit diff
path: root/nixos/modules/security
diff options
context:
space:
mode:
Diffstat (limited to 'nixos/modules/security')
-rw-r--r--nixos/modules/security/acme.nix245
-rw-r--r--nixos/modules/security/acme.xml2
-rw-r--r--nixos/modules/security/apparmor-suid.nix3
-rw-r--r--nixos/modules/security/chromium-suid-sandbox.nix4
-rw-r--r--nixos/modules/security/duosec.nix110
-rw-r--r--nixos/modules/security/google_oslogin.nix6
-rw-r--r--nixos/modules/security/pam.nix18
-rw-r--r--nixos/modules/security/pam_mount.nix5
-rw-r--r--nixos/modules/security/polkit.nix5
-rw-r--r--nixos/modules/security/rngd.nix12
-rw-r--r--nixos/modules/security/rtkit.nix5
-rw-r--r--nixos/modules/security/sudo.nix39
-rw-r--r--nixos/modules/security/tpm2.nix185
-rw-r--r--nixos/modules/security/wrappers/default.nix4
14 files changed, 502 insertions, 141 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index 9563029f030..39976380e3b 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -1,7 +1,5 @@
 { config, lib, pkgs, ... }:
-
 with lib;
-
 let
 
   cfg = config.security.acme;
@@ -9,7 +7,8 @@ let
   certOpts = { name, ... }: {
     options = {
       webroot = mkOption {
-        type = types.str;
+        type = types.nullOr types.str;
+        default = null;
         example = "/var/lib/acme/acme-challenges";
         description = ''
           Where the webroot of the HTTP vhost is located.
@@ -38,7 +37,7 @@ let
 
       email = mkOption {
         type = types.nullOr types.str;
-        default = null;
+        default = cfg.email;
         description = "Contact email address for the CA to be able to reach you.";
       };
 
@@ -76,20 +75,6 @@ let
         '';
       };
 
-      plugins = mkOption {
-        type = types.listOf (types.enum [
-          "cert.der" "cert.pem" "chain.pem" "external.sh"
-          "fullchain.pem" "full.pem" "key.der" "key.pem" "account_key.json" "account_reg.json"
-        ]);
-        default = [ "fullchain.pem" "full.pem" "key.pem" "account_key.json" "account_reg.json" ];
-        description = ''
-          Plugins to enable. With default settings simp_le will
-          store public certificate bundle in <filename>fullchain.pem</filename>,
-          private key in <filename>key.pem</filename> and those two previous
-          files combined in <filename>full.pem</filename> in its state directory.
-        '';
-      };
-
       directory = mkOption {
         type = types.str;
         readOnly = true;
@@ -111,6 +96,67 @@ let
           own server roots if needed.
         '';
       };
+
+      keyType = mkOption {
+        type = types.str;
+        default = "ec384";
+        description = ''
+          Key type to use for private keys.
+          For an up to date list of supported values check the --key-type option
+          at https://go-acme.github.io/lego/usage/cli/#usage.
+        '';
+      };
+
+      dnsProvider = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        example = "route53";
+        description = ''
+          DNS Challenge provider. For a list of supported providers, see the "code"
+          field of the DNS providers listed at https://go-acme.github.io/lego/dns/.
+        '';
+      };
+
+      credentialsFile = mkOption {
+        type = types.path;
+        description = ''
+          Path to an EnvironmentFile for the cert's service containing any required and
+          optional environment variables for your selected dnsProvider.
+          To find out what values you need to set, consult the documentation at
+          https://go-acme.github.io/lego/dns/ for the corresponding dnsProvider.
+        '';
+        example = "/var/src/secrets/example.org-route53-api-token";
+      };
+
+      dnsPropagationCheck = mkOption {
+        type = types.bool;
+        default = true;
+        description = ''
+          Toggles lego DNS propagation check, which is used alongside DNS-01
+          challenge to ensure the DNS entries required are available.
+        '';
+      };
+
+      ocspMustStaple = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Turns on the OCSP Must-Staple TLS extension.
+          Make sure you know what you're doing! See:
+          <itemizedlist>
+            <listitem><para><link xlink:href="https://blog.apnic.net/2019/01/15/is-the-web-ready-for-ocsp-must-staple/" /></para></listitem>
+            <listitem><para><link xlink:href="https://blog.hboeck.de/archives/886-The-Problem-with-OCSP-Stapling-and-Must-Staple-and-why-Certificate-Revocation-is-still-broken.html" /></para></listitem>
+          </itemizedlist>
+        '';
+      };
+
+      extraLegoRenewFlags = mkOption {
+        type = types.listOf types.str;
+        default = [];
+        description = ''
+          Additional flags to pass to lego renew.
+        '';
+      };
     };
   };
 
@@ -127,19 +173,29 @@ in
       "https://acme-staging-v02.api.letsencrypt.org/directory".
     ''
     )
+    (mkRemovedOptionModule [ "security" "acme" "directory"] "ACME Directory is now hardcoded to /var/lib/acme and its permisisons are managed by systemd. See https://github.com/NixOS/nixpkgs/issues/53852 for more info.")
+    (mkRemovedOptionModule [ "security" "acme" "preDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+    (mkRemovedOptionModule [ "security" "acme" "activationDelay"] "This option has been removed. If you want to make sure that something executes before certificates are provisioned, add a RequiredBy=acme-\${cert}.service to the service you want to execute before the cert renewal")
+    (mkChangedOptionModule [ "security" "acme" "validMin"] [ "security" "acme" "validMinDays"] (config: config.security.acme.validMin / (24 * 3600)))
   ];
   options = {
     security.acme = {
 
-      validMin = mkOption {
+      validMinDays = mkOption {
         type = types.int;
-        default = 30 * 24 * 3600;
-        description = "Minimum remaining validity before renewal in seconds.";
+        default = 30;
+        description = "Minimum remaining validity before renewal in days.";
+      };
+
+      email = mkOption {
+        type = types.nullOr types.str;
+        default = null;
+        description = "Contact email address for the CA to be able to reach you.";
       };
 
       renewInterval = mkOption {
         type = types.str;
-        default = "weekly";
+        default = "daily";
         description = ''
           Systemd calendar expression when to check for renewal. See
           <citerefentry><refentrytitle>systemd.time</refentrytitle>
@@ -170,6 +226,15 @@ in
         '';
       };
 
+      acceptTerms = mkOption {
+        type = types.bool;
+        default = false;
+        description = ''
+          Accept the CA's terms of service. The default provier is Let's Encrypt,
+          you can find their ToS at https://letsencrypt.org/repository/
+        '';
+      };
+
       certs = mkOption {
         default = { };
         type = with types; attrsOf (submodule certOpts);
@@ -201,48 +266,100 @@ in
   config = mkMerge [
     (mkIf (cfg.certs != { }) {
 
+      assertions = let
+        certs = (mapAttrsToList (k: v: v) cfg.certs);
+      in [
+        {
+          assertion = all (certOpts: certOpts.dnsProvider == null || certOpts.webroot == null) certs;
+          message = ''
+            Options `security.acme.certs.<name>.dnsProvider` and
+            `security.acme.certs.<name>.webroot` are mutually exclusive.
+          '';
+        }
+        {
+          assertion = cfg.email != null || all (certOpts: certOpts.email != null) certs;
+          message = ''
+            You must define `security.acme.certs.<name>.email` or
+            `security.acme.email` to register with the CA.
+          '';
+        }
+        {
+          assertion = cfg.acceptTerms;
+          message = ''
+            You must accept the CA's terms of service before using
+            the ACME module by setting `security.acme.acceptTerms`
+            to `true`. For Let's Encrypt's ToS see https://letsencrypt.org/repository/
+          '';
+        }
+      ];
+
       systemd.services = let
           services = concatLists servicesLists;
           servicesLists = mapAttrsToList certToServices cfg.certs;
           certToServices = cert: data:
               let
+                # StateDirectory must be relative, and will be created under /var/lib by systemd
                 lpath = "acme/${cert}";
-                rights = if data.allowKeysForGroup then "750" else "700";
-                cmdline = [ "-v" "-d" data.domain "--default_root" data.webroot "--valid_min" cfg.validMin ]
-                          ++ optionals (data.email != null) [ "--email" data.email ]
-                          ++ concatMap (p: [ "-f" p ]) data.plugins
-                          ++ concatLists (mapAttrsToList (name: root: [ "-d" (if root == null then name else "${name}:${root}")]) data.extraDomains)
+                apath = "/var/lib/${lpath}";
+                spath = "/var/lib/acme/.lego/${cert}";
+                fileMode = if data.allowKeysForGroup then "640" else "600";
+                globalOpts = [ "-d" data.domain "--email" data.email "--path" "." "--key-type" data.keyType ]
+                          ++ optionals (cfg.acceptTerms) [ "--accept-tos" ]
+                          ++ optionals (data.dnsProvider != null && !data.dnsPropagationCheck) [ "--dns.disable-cp" ]
+                          ++ concatLists (mapAttrsToList (name: root: [ "-d" name ]) data.extraDomains)
+                          ++ (if data.dnsProvider != null then [ "--dns" data.dnsProvider ] else [ "--http" "--http.webroot" data.webroot ])
                           ++ optionals (cfg.server != null || data.server != null) ["--server" (if data.server == null then cfg.server else data.server)];
+                certOpts = optionals data.ocspMustStaple [ "--must-staple" ];
+                runOpts = escapeShellArgs (globalOpts ++ [ "run" ] ++ certOpts);
+                renewOpts = escapeShellArgs (globalOpts ++
+                  [ "renew" "--days" (toString cfg.validMinDays) ] ++
+                  certOpts ++ data.extraLegoRenewFlags);
                 acmeService = {
                   description = "Renew ACME Certificate for ${cert}";
                   after = [ "network.target" "network-online.target" ];
                   wants = [ "network-online.target" ];
-                  # simp_le uses requests, which uses certifi under the hood,
-                  # which doesn't respect the system trust store.
-                  # At least in the acme test, we provision a fake CA, impersonating the LE endpoint.
-                  # REQUESTS_CA_BUNDLE is a way to teach python requests to use something else
-                  environment.REQUESTS_CA_BUNDLE = "/etc/ssl/certs/ca-certificates.crt";
+                  wantedBy = mkIf (!config.boot.isContainer) [ "multi-user.target" ];
                   serviceConfig = {
                     Type = "oneshot";
-                    # With RemainAfterExit the service is considered active even
-                    # after the main process having exited, which means when it
-                    # gets changed, the activation phase restarts it, meaning
-                    # the permissions of the StateDirectory get adjusted
-                    # according to the specified group
-                    RemainAfterExit = true;
-                    SuccessExitStatus = [ "0" "1" ];
                     User = data.user;
                     Group = data.group;
                     PrivateTmp = true;
-                    StateDirectory = lpath;
-                    StateDirectoryMode = rights;
-                    WorkingDirectory = "/var/lib/${lpath}";
-                    ExecStart = "${pkgs.simp_le}/bin/simp_le ${escapeShellArgs cmdline}";
-                    ExecStopPost =
+                    StateDirectory = "acme/.lego/${cert} acme/.lego/accounts ${lpath}";
+                    StateDirectoryMode = if data.allowKeysForGroup then "750" else "700";
+                    WorkingDirectory = spath;
+                    # Only try loading the credentialsFile if the dns challenge is enabled
+                    EnvironmentFile = if data.dnsProvider != null then data.credentialsFile else null;
+                    ExecStart = pkgs.writeScript "acme-start" ''
+                      #!${pkgs.runtimeShell} -e
+                      test -L ${spath}/accounts -o -d ${spath}/accounts || ln -s ../accounts ${spath}/accounts
+                      ${pkgs.lego}/bin/lego ${renewOpts} || ${pkgs.lego}/bin/lego ${runOpts}
+                    '';
+                    ExecStartPost =
                       let
-                        script = pkgs.writeScript "acme-post-stop" ''
+                        keyName = builtins.replaceStrings ["*"] ["_"] data.domain;
+                        script = pkgs.writeScript "acme-post-start" ''
                           #!${pkgs.runtimeShell} -e
-                          ${data.postRun}
+                          cd ${apath}
+
+                          # Test that existing cert is older than new cert
+                          KEY=${spath}/certificates/${keyName}.key
+                          KEY_CHANGED=no
+                          if [ -e $KEY -a $KEY -nt key.pem ]; then
+                            KEY_CHANGED=yes
+                            cp -p ${spath}/certificates/${keyName}.key key.pem
+                            cp -p ${spath}/certificates/${keyName}.crt fullchain.pem
+                            cp -p ${spath}/certificates/${keyName}.issuer.crt chain.pem
+                            ln -sf fullchain.pem cert.pem
+                            cat key.pem fullchain.pem > full.pem
+                          fi
+
+                          chmod ${fileMode} *.pem
+                          chown '${data.user}:${data.group}' *.pem
+
+                          if [ "$KEY_CHANGED" = "yes" ]; then
+                            : # noop in case postRun is empty
+                            ${data.postRun}
+                          fi
                         '';
                       in
                         "+${script}";
@@ -273,17 +390,17 @@ in
                         -out $workdir/server.crt
 
                       # Copy key to destination
-                      cp $workdir/server.key /var/lib/${lpath}/key.pem
+                      cp $workdir/server.key ${apath}/key.pem
 
                       # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
-                      cat $workdir/{server.crt,ca.crt} > "/var/lib/${lpath}/fullchain.pem"
+                      cat $workdir/{server.crt,ca.crt} > "${apath}/fullchain.pem"
 
                       # Create full.pem for e.g. lighttpd
-                      cat $workdir/{server.key,server.crt,ca.crt} > "/var/lib/${lpath}/full.pem"
+                      cat $workdir/{server.key,server.crt,ca.crt} > "${apath}/full.pem"
 
                       # Give key acme permissions
-                      chown '${data.user}:${data.group}' "/var/lib/${lpath}/"{key,fullchain,full}.pem
-                      chmod ${rights} "/var/lib/${lpath}/"{key,fullchain,full}.pem
+                      chown '${data.user}:${data.group}' "${apath}/"{key,fullchain,full}.pem
+                      chmod ${fileMode} "${apath}/"{key,fullchain,full}.pem
                     '';
                   serviceConfig = {
                     Type = "oneshot";
@@ -294,7 +411,7 @@ in
                   };
                   unitConfig = {
                     # Do not create self-signed key when key already exists
-                    ConditionPathExists = "!/var/lib/${lpath}/key.pem";
+                    ConditionPathExists = "!${apath}/key.pem";
                   };
                 };
               in (
@@ -306,10 +423,19 @@ in
           servicesAttr;
 
       systemd.tmpfiles.rules =
-        flip mapAttrsToList cfg.certs
-        (cert: data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}");
-
-      systemd.timers = flip mapAttrs' cfg.certs (cert: data: nameValuePair
+        map (data: "d ${data.webroot}/.well-known/acme-challenge - ${data.user} ${data.group}") (filter (data: data.webroot != null) (attrValues cfg.certs));
+
+      systemd.timers = let
+        # Allow systemd to pick a convenient time within the day
+        # to run the check.
+        # This allows the coalescing of multiple timer jobs.
+        # We divide by the number of certificates so that if you
+        # have many certificates, the renewals are distributed over
+        # the course of the day to avoid rate limits.
+        numCerts = length (attrNames cfg.certs);
+        _24hSecs = 60 * 60 * 24;
+        AccuracySec = "${toString (_24hSecs / numCerts)}s";
+      in flip mapAttrs' cfg.certs (cert: data: nameValuePair
         ("acme-${cert}")
         ({
           description = "Renew ACME Certificate for ${cert}";
@@ -318,8 +444,9 @@ in
             OnCalendar = cfg.renewInterval;
             Unit = "acme-${cert}.service";
             Persistent = "yes";
-            AccuracySec = "5m";
-            RandomizedDelaySec = "1h";
+            inherit AccuracySec;
+            # Skew randomly within the day, per https://letsencrypt.org/docs/integration-guide/.
+            RandomizedDelaySec = "24h";
           };
         })
       );
@@ -331,7 +458,7 @@ in
   ];
 
   meta = {
-    maintainers = with lib.maintainers; [ abbradar fpletz globin ];
+    maintainers = with lib.maintainers; [ abbradar fpletz globin m1cr0man ];
     doc = ./acme.xml;
   };
 }
diff --git a/nixos/modules/security/acme.xml b/nixos/modules/security/acme.xml
index 9d0a1995e0f..2b29c117484 100644
--- a/nixos/modules/security/acme.xml
+++ b/nixos/modules/security/acme.xml
@@ -7,7 +7,7 @@
  <para>
   NixOS supports automatic domain validation &amp; certificate retrieval and
   renewal using the ACME protocol. This is currently only implemented by and
-  for Let's Encrypt. The alternative ACME client <literal>simp_le</literal> is
+  for Let's Encrypt. The alternative ACME client <literal>lego</literal> is
   used under the hood.
  </para>
  <section xml:id="module-security-acme-prerequisites">
diff --git a/nixos/modules/security/apparmor-suid.nix b/nixos/modules/security/apparmor-suid.nix
index 498c2f25d1c..3c93f5440ab 100644
--- a/nixos/modules/security/apparmor-suid.nix
+++ b/nixos/modules/security/apparmor-suid.nix
@@ -4,6 +4,9 @@ let
 in
 with lib;
 {
+  imports = [
+    (mkRenamedOptionModule [ "security" "virtualization" "flushL1DataCache" ] [ "security" "virtualisation" "flushL1DataCache" ])
+  ];
 
   options.security.apparmor.confineSUIDApplications = mkOption {
     default = true;
diff --git a/nixos/modules/security/chromium-suid-sandbox.nix b/nixos/modules/security/chromium-suid-sandbox.nix
index 2255477f26e..b83dbc4202a 100644
--- a/nixos/modules/security/chromium-suid-sandbox.nix
+++ b/nixos/modules/security/chromium-suid-sandbox.nix
@@ -7,6 +7,10 @@ let
   sandbox = pkgs.chromium.sandbox;
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "programs" "unity3d" "enable" ] [ "security" "chromiumSuidSandbox" "enable" ])
+  ];
+
   options.security.chromiumSuidSandbox.enable = mkOption {
     type = types.bool;
     default = false;
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index 997328ad9e6..71428b82f5d 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -9,10 +9,9 @@ let
 
   configFilePam = ''
     [duo]
-    ikey=${cfg.ikey}
-    skey=${cfg.skey}
+    ikey=${cfg.integrationKey}
     host=${cfg.host}
-    ${optionalString (cfg.group != "") ("group="+cfg.group)}
+    ${optionalString (cfg.groups != "") ("groups="+cfg.groups)}
     failmode=${cfg.failmode}
     pushinfo=${boolToStr cfg.pushinfo}
     autopush=${boolToStr cfg.autopush}
@@ -24,22 +23,14 @@ let
     motd=${boolToStr cfg.motd}
     accept_env_factor=${boolToStr cfg.acceptEnvFactor}
   '';
-
-  loginCfgFile = optional cfg.ssh.enable
-    { source = pkgs.writeText "login_duo.conf" configFileLogin;
-      mode   = "0600";
-      user   = "sshd";
-      target = "duo/login_duo.conf";
-    };
-
-  pamCfgFile = optional cfg.pam.enable
-    { source = pkgs.writeText "pam_duo.conf" configFilePam;
-      mode   = "0600";
-      user   = "sshd";
-      target = "duo/pam_duo.conf";
-    };
 in
 {
+  imports = [
+    (mkRenamedOptionModule [ "security" "duosec" "group" ] [ "security" "duosec" "groups" ])
+    (mkRenamedOptionModule [ "security" "duosec" "ikey" ] [ "security" "duosec" "integrationKey" ])
+    (mkRemovedOptionModule [ "security" "duosec" "skey" ] "The insecure security.duosec.skey option has been replaced by a new security.duosec.secretKeyFile option. Use this new option to store a secure copy of your key instead.")
+  ];
+
   options = {
     security.duosec = {
       ssh.enable = mkOption {
@@ -54,14 +45,18 @@ in
         description = "If enabled, protect logins with Duo Security using PAM support.";
       };
 
-      ikey = mkOption {
+      integrationKey = mkOption {
         type = types.str;
         description = "Integration key.";
       };
 
-      skey = mkOption {
-        type = types.str;
-        description = "Secret key.";
+      secretKeyFile = mkOption {
+        type = types.path;
+        default = null;
+        description = ''
+          A file containing your secret key. The security of your Duo application is tied to the security of your secret key.
+        '';
+        example = "/run/keys/duo-skey";
       };
 
       host = mkOption {
@@ -69,10 +64,16 @@ in
         description = "Duo API hostname.";
       };
 
-      group = mkOption {
+      groups = mkOption {
         type = types.str;
         default = "";
-        description = "Use Duo authentication for users only in this group.";
+        example = "users,!wheel,!*admin guests";
+        description = ''
+          If specified, Duo authentication is required only for users
+          whose primary group or supplementary group list matches one
+          of the space-separated pattern lists. Refer to
+          <link xlink:href="https://duo.com/docs/duounix"/> for details.
+        '';
       };
 
       failmode = mkOption {
@@ -183,21 +184,52 @@ in
   };
 
   config = mkIf (cfg.ssh.enable || cfg.pam.enable) {
-     environment.systemPackages = [ pkgs.duo-unix ];
-
-     security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
-     environment.etc = loginCfgFile ++ pamCfgFile;
-
-     /* If PAM *and* SSH are enabled, then don't do anything special.
-     If PAM isn't used, set the default SSH-only options. */
-     services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) (
-     if cfg.pam.enable then "UseDNS no" else ''
-       # Duo Security configuration
-       ForceCommand ${config.security.wrapperDir}/login_duo
-       PermitTunnel no
-       ${optionalString (!cfg.allowTcpForwarding) ''
-         AllowTcpForwarding no
-       ''}
-     '');
+    environment.systemPackages = [ pkgs.duo-unix ];
+
+    security.wrappers.login_duo.source = "${pkgs.duo-unix.out}/bin/login_duo";
+
+    system.activationScripts = {
+      login_duo = mkIf cfg.ssh.enable ''
+        if test -f "${cfg.secretKeyFile}"; then
+          mkdir -m 0755 -p /etc/duo
+
+          umask 0077
+          conf="$(mktemp)"
+          {
+            cat ${pkgs.writeText "login_duo.conf" configFileLogin}
+            printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
+          } >"$conf"
+
+          chown sshd "$conf"
+          mv -fT "$conf" /etc/duo/login_duo.conf
+        fi
+      '';
+      pam_duo = mkIf cfg.pam.enable ''
+        if test -f "${cfg.secretKeyFile}"; then
+          mkdir -m 0755 -p /etc/duo
+
+          umask 0077
+          conf="$(mktemp)"
+          {
+            cat ${pkgs.writeText "login_duo.conf" configFilePam}
+            printf 'skey = %s\n' "$(cat ${cfg.secretKeyFile})"
+          } >"$conf"
+
+          mv -fT "$conf" /etc/duo/pam_duo.conf
+        fi
+      '';
+    };
+
+    /* If PAM *and* SSH are enabled, then don't do anything special.
+    If PAM isn't used, set the default SSH-only options. */
+    services.openssh.extraConfig = mkIf (cfg.ssh.enable || cfg.pam.enable) (
+    if cfg.pam.enable then "UseDNS no" else ''
+      # Duo Security configuration
+      ForceCommand ${config.security.wrapperDir}/login_duo
+      PermitTunnel no
+      ${optionalString (!cfg.allowTcpForwarding) ''
+        AllowTcpForwarding no
+      ''}
+    '');
   };
 }
diff --git a/nixos/modules/security/google_oslogin.nix b/nixos/modules/security/google_oslogin.nix
index 246419b681a..6f9962e1d62 100644
--- a/nixos/modules/security/google_oslogin.nix
+++ b/nixos/modules/security/google_oslogin.nix
@@ -59,10 +59,8 @@ in
         exec ${package}/bin/google_authorized_keys "$@"
       '';
     };
-    services.openssh.extraConfig = ''
-      AuthorizedKeysCommand /etc/ssh/authorized_keys_command_google_oslogin %u
-      AuthorizedKeysCommandUser nobody
-    '';
+    services.openssh.authorizedKeysCommand = "/etc/ssh/authorized_keys_command_google_oslogin %u";
+    services.openssh.authorizedKeysCommandUser = "nobody";
   };
 
 }
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 11227354ad3..bfc2a881387 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -475,15 +475,19 @@ let
 
   motd = pkgs.writeText "motd" config.users.motd;
 
-  makePAMService = pamService:
-    { source = pkgs.writeText "${pamService.name}.pam" pamService.text;
-      target = "pam.d/${pamService.name}";
+  makePAMService = name: service:
+    { name = "pam.d/${name}";
+      value.source = pkgs.writeText "${name}.pam" service.text;
     };
 
 in
 
 {
 
+  imports = [
+    (mkRenamedOptionModule [ "security" "pam" "enableU2F" ] [ "security" "pam" "u2f" "enable" ])
+  ];
+
   ###### interface
 
   options = {
@@ -707,7 +711,7 @@ in
 
           Use "challenge-response" for offline validation using YubiKeys with HMAC-SHA-1
           Challenge-Response configurations. See the man-page ykpamcfg(1) for further
-          details on how to configure offline Challenge-Response validation. 
+          details on how to configure offline Challenge-Response validation.
 
           More information can be found <link
           xlink:href="https://developers.yubico.com/yubico-pam/Authentication_Using_Challenge-Response.html">here</link>.
@@ -756,8 +760,7 @@ in
       };
     };
 
-    environment.etc =
-      mapAttrsToList (n: v: makePAMService v) config.security.pam.services;
+    environment.etc = mapAttrs' makePAMService config.security.pam.services;
 
     security.pam.services =
       { other.text =
@@ -773,11 +776,8 @@ in
           '';
 
         # Most of these should be moved to specific modules.
-        cups = {};
-        ftp = {};
         i3lock = {};
         i3lock-color = {};
-        screen = {};
         vlock = {};
         xlock = {};
         xscreensaver = {};
diff --git a/nixos/modules/security/pam_mount.nix b/nixos/modules/security/pam_mount.nix
index 75f58462d13..77e22a96b55 100644
--- a/nixos/modules/security/pam_mount.nix
+++ b/nixos/modules/security/pam_mount.nix
@@ -36,8 +36,7 @@ in
   config = mkIf (cfg.enable || anyPamMount) {
 
     environment.systemPackages = [ pkgs.pam_mount ];
-    environment.etc = [{
-      target = "security/pam_mount.conf.xml";
+    environment.etc."security/pam_mount.conf.xml" = {
       source =
         let
           extraUserVolumes = filterAttrs (n: u: u.cryptHomeLuks != null) config.users.users;
@@ -66,7 +65,7 @@ in
           ${concatStringsSep "\n" cfg.extraVolumes}
           </pam_mount>
           '';
-    }];
+    };
 
   };
 }
diff --git a/nixos/modules/security/polkit.nix b/nixos/modules/security/polkit.nix
index f2b2df4004c..a6724bd7583 100644
--- a/nixos/modules/security/polkit.nix
+++ b/nixos/modules/security/polkit.nix
@@ -42,15 +42,14 @@ in
 
     security.polkit.adminIdentities = mkOption {
       type = types.listOf types.str;
-      default = [ "unix-user:0" "unix-group:wheel" ];
+      default = [ "unix-group:wheel" ];
       example = [ "unix-user:alice" "unix-group:admin" ];
       description =
         ''
           Specifies which users are considered “administrators”, for those
           actions that require the user to authenticate as an
           administrator (i.e. have an <literal>auth_admin</literal>
-          value).  By default, this is the <literal>root</literal>
-          user and all users in the <literal>wheel</literal> group.
+          value).  By default, this is all users in the <literal>wheel</literal> group.
         '';
     };
 
diff --git a/nixos/modules/security/rngd.nix b/nixos/modules/security/rngd.nix
index d9d6d9c9f25..cffa1a5849f 100644
--- a/nixos/modules/security/rngd.nix
+++ b/nixos/modules/security/rngd.nix
@@ -37,14 +37,24 @@ in
 
       after = [ "dev-random.device" ];
 
+      # Clean shutdown without DefaultDependencies
+      conflicts = [ "shutdown.target" ];
+      before = [
+        "sysinit.target"
+        "shutdown.target"
+      ];
+
       description = "Hardware RNG Entropy Gatherer Daemon";
 
+      # rngd may have to start early to avoid entropy starvation during boot with encrypted swap
+      unitConfig.DefaultDependencies = false;
       serviceConfig = {
         ExecStart = "${pkgs.rng-tools}/sbin/rngd -f"
           + optionalString cfg.debug " -d";
+        # PrivateTmp would introduce a circular dependency if /tmp is on tmpfs and swap is encrypted,
+        # thus depending on rngd before swap, while swap depends on rngd to avoid entropy starvation.
         NoNewPrivileges = true;
         PrivateNetwork = true;
-        PrivateTmp = true;
         ProtectSystem = "full";
         ProtectHome = true;
       };
diff --git a/nixos/modules/security/rtkit.nix b/nixos/modules/security/rtkit.nix
index f6dda21c600..a7b27cbcf21 100644
--- a/nixos/modules/security/rtkit.nix
+++ b/nixos/modules/security/rtkit.nix
@@ -34,9 +34,8 @@ with lib;
 
     services.dbus.packages = [ pkgs.rtkit ];
 
-    users.users = singleton
-      { name = "rtkit";
-        uid = config.ids.uids.rtkit;
+    users.users.rtkit =
+      { uid = config.ids.uids.rtkit;
         description = "RealtimeKit daemon";
       };
 
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index 10ee036be84..e3e43177def 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -71,23 +71,25 @@ in
         this is the case when configuration options are merged.
       '';
       default = [];
-      example = [
-        # Allow execution of any command by all users in group sudo,
-        # requiring a password.
-        { groups = [ "sudo" ]; commands = [ "ALL" ]; }
-
-        # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
-        # and the group with GID `1006` without a password.
-        { users = [ "backup" "database" ]; groups = [ 1006 ];
-          commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
-
-        # Allow all users of group `bar` to run two executables as user `foo`
-        # with arguments being pre-set.
-        { groups = [ "bar" ]; runAs = "foo";
-          commands =
-            [ "/home/baz/cmd1.sh hello-sudo"
-              { command = ''/home/baz/cmd2.sh ""''; options = [ "SETENV" ]; } ]; }
-      ];
+      example = literalExample ''
+        [
+          # Allow execution of any command by all users in group sudo,
+          # requiring a password.
+          { groups = [ "sudo" ]; commands = [ "ALL" ]; }
+
+          # Allow execution of "/home/root/secret.sh" by user `backup`, `database`
+          # and the group with GID `1006` without a password.
+          { users = [ "backup" "database" ]; groups = [ 1006 ];
+            commands = [ { command = "/home/root/secret.sh"; options = [ "SETENV" "NOPASSWD" ]; } ]; }
+
+          # Allow all users of group `bar` to run two executables as user `foo`
+          # with arguments being pre-set.
+          { groups = [ "bar" ]; runAs = "foo";
+            commands =
+              [ "/home/baz/cmd1.sh hello-sudo"
+                  { command = '''/home/baz/cmd2.sh ""'''; options = [ "SETENV" ]; } ]; }
+        ]
+      '';
       type = with types; listOf (submodule {
         options = {
           users = mkOption {
@@ -212,7 +214,7 @@ in
 
     security.pam.services.sudo = { sshAgentAuth = true; };
 
-    environment.etc = singleton
+    environment.etc.sudoers =
       { source =
           pkgs.runCommand "sudoers"
           {
@@ -222,7 +224,6 @@ in
           # Make sure that the sudoers file is syntactically valid.
           # (currently disabled - NIXOS-66)
           "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out";
-        target = "sudoers";
         mode = "0440";
       };
 
diff --git a/nixos/modules/security/tpm2.nix b/nixos/modules/security/tpm2.nix
new file mode 100644
index 00000000000..13804fb82cb
--- /dev/null
+++ b/nixos/modules/security/tpm2.nix
@@ -0,0 +1,185 @@
+{ lib, pkgs, config, ... }:
+let
+  cfg = config.security.tpm2;
+
+  # This snippet is taken from tpm2-tss/dist/tpm-udev.rules, but modified to allow custom user/groups
+  # The idea is that the tssUser is allowed to acess the TPM and kernel TPM resource manager, while
+  # the tssGroup is only allowed to access the kernel resource manager
+  # Therefore, if either of the two are null, the respective part isn't generated
+  udevRules = tssUser: tssGroup: ''
+    ${lib.optionalString (tssUser != null) ''KERNEL=="tpm[0-9]*", MODE="0660", OWNER="${tssUser}"''}
+    ${lib.optionalString (tssUser != null || tssGroup != null)
+      ''KERNEL=="tpmrm[0-9]*", MODE="0660"''
+      + lib.optionalString (tssUser != null) '', OWNER="${tssUser}"''
+      + lib.optionalString (tssGroup != null) '', GROUP="${tssGroup}"''
+     }
+  '';
+
+in {
+  options.security.tpm2 = {
+    enable = lib.mkEnableOption "Trusted Platform Module 2 support";
+
+    tssUser = lib.mkOption {
+      description = ''
+        Name of the tpm device-owner and service user, set if applyUdevRules is
+        set.
+      '';
+      type = lib.types.nullOr lib.types.str;
+      default = if cfg.abrmd.enable then "tss" else "root";
+      defaultText = ''"tss" when using the userspace resource manager,'' +
+                    ''"root" otherwise'';
+    };
+
+    tssGroup = lib.mkOption {
+      description = ''
+        Group of the tpm kernel resource manager (tpmrm) device-group, set if
+        applyUdevRules is set.
+      '';
+      type = lib.types.nullOr lib.types.str;
+      default = "tss";
+    };
+
+    applyUdevRules = lib.mkOption {
+      description = ''
+        Whether to make the /dev/tpm[0-9] devices accessible by the tssUser, or
+        the /dev/tpmrm[0-9] by tssGroup respectively
+      '';
+      type = lib.types.bool;
+      default = true;
+    };
+
+    abrmd = {
+      enable = lib.mkEnableOption ''
+        Trusted Platform 2 userspace resource manager daemon
+      '';
+
+      package = lib.mkOption {
+        description = "tpm2-abrmd package to use";
+        type = lib.types.package;
+        default = pkgs.tpm2-abrmd;
+        defaultText = "pkgs.tpm2-abrmd";
+      };
+    };
+
+    pkcs11 = {
+      enable = lib.mkEnableOption ''
+        TPM2 PKCS#11 tool and shared library in system path
+        (<literal>/run/current-system/sw/lib/libtpm2_pkcs11.so</literal>)
+      '';
+
+      package = lib.mkOption {
+        description = "tpm2-pkcs11 package to use";
+        type = lib.types.package;
+        default = pkgs.tpm2-pkcs11;
+        defaultText = "pkgs.tpm2-pkcs11";
+      };
+    };
+
+    tctiEnvironment = {
+      enable = lib.mkOption {
+        description = ''
+          Set common TCTI environment variables to the specified value.
+          The variables are
+          <itemizedlist>
+            <listitem>
+              <para>
+                <literal>TPM2TOOLS_TCTI</literal>
+              </para>
+            </listitem>
+            <listitem>
+              <para>
+                <literal>TPM2_PKCS11_TCTI</literal>
+              </para>
+            </listitem>
+          </itemizedlist>
+        '';
+        type = lib.types.bool;
+        default = false;
+      };
+
+      interface = lib.mkOption {
+        description = ''
+          The name of the TPM command transmission interface (TCTI) library to
+          use.
+        '';
+        type = lib.types.enum [ "tabrmd" "device" ];
+        default = "device";
+      };
+
+      deviceConf = lib.mkOption {
+        description = ''
+          Configuration part of the device TCTI, e.g. the path to the TPM device.
+          Applies if interface is set to "device".
+          The format is specified in the
+          <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options">
+          tpm2-tools repository</link>.
+        '';
+        type = lib.types.str;
+        default = "/dev/tpmrm0";
+      };
+
+      tabrmdConf = lib.mkOption {
+        description = ''
+          Configuration part of the tabrmd TCTI, like the D-Bus bus name.
+          Applies if interface is set to "tabrmd".
+          The format is specified in the
+          <link xlink:href="https://github.com/tpm2-software/tpm2-tools/blob/master/man/common/tcti.md#tcti-options">
+          tpm2-tools repository</link>.
+        '';
+        type = lib.types.str;
+        default = "bus_name=com.intel.tss2.Tabrmd";
+      };
+    };
+  };
+
+  config = lib.mkIf cfg.enable (lib.mkMerge [
+    {
+      # PKCS11 tools and library
+      environment.systemPackages = lib.mkIf cfg.pkcs11.enable [
+        (lib.getBin cfg.pkcs11.package)
+        (lib.getLib cfg.pkcs11.package)
+      ];
+
+      services.udev.extraRules = lib.mkIf cfg.applyUdevRules
+        (udevRules cfg.tssUser cfg.tssGroup);
+
+      # Create the tss user and group only if the default value is used
+      users.users.${cfg.tssUser} = lib.mkIf (cfg.tssUser == "tss") {
+        isSystemUser = true;
+      };
+      users.groups.${cfg.tssGroup} = lib.mkIf (cfg.tssGroup == "tss") {};
+
+      environment.variables = lib.mkIf cfg.tctiEnvironment.enable (
+        lib.attrsets.genAttrs [
+          "TPM2TOOLS_TCTI"
+          "TPM2_PKCS11_TCTI"
+        ] (_: ''${cfg.tctiEnvironment.interface}:${
+          if cfg.tctiEnvironment.interface == "tabrmd" then
+            cfg.tctiEnvironment.tabrmdConf
+          else
+            cfg.tctiEnvironment.deviceConf
+        }'')
+      );
+    }
+
+    (lib.mkIf cfg.abrmd.enable {
+      systemd.services."tpm2-abrmd" = {
+        wantedBy = [ "multi-user.target" ];
+        serviceConfig = {
+          Type = "dbus";
+          Restart = "always";
+          RestartSec = 30;
+          BusName = "com.intel.tss2.Tabrmd";
+          StandardOutput = "syslog";
+          ExecStart = "${cfg.abrmd.package}/bin/tpm2-abrmd";
+          User = "tss";
+          Group = "nogroup";
+        };
+      };
+
+      services.dbus.packages = lib.singleton cfg.abrmd.package;
+    })
+  ]);
+
+  meta.maintainers = with lib.maintainers; [ lschuermann ];
+}
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 47738e7962e..a0fadb018ec 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -94,6 +94,10 @@ let
       ) programs;
 in
 {
+  imports = [
+    (lib.mkRemovedOptionModule [ "security" "setuidOwners" ] "Use security.wrappers instead")
+    (lib.mkRemovedOptionModule [ "security" "setuidPrograms" ] "Use security.wrappers instead")
+  ];
 
   ###### interface