summary refs log tree commit diff
path: root/nixos/modules/security
diff options
context:
space:
mode:
authorMatthew Justin Bauer <mjbauer95@gmail.com>2018-04-21 14:53:23 -0500
committerGitHub <noreply@github.com>2018-04-21 14:53:23 -0500
commit1eea73eaa8185bd6f4fe2a0facc90652277ea399 (patch)
treefa5e6aaccbdf1ef9f4e71d0f74225252a5c2168c /nixos/modules/security
parente70d293b6b5f236d69d559ccccdafba19c6d29c3 (diff)
parent17b73ff90cf9f7631e543d880540441bdf56d719 (diff)
downloadnixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar.gz
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar.bz2
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar.lz
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar.xz
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.tar.zst
nixlib-1eea73eaa8185bd6f4fe2a0facc90652277ea399.zip
Merge branch 'master' into feat/acme/delay
Diffstat (limited to 'nixos/modules/security')
-rw-r--r--nixos/modules/security/acme.nix61
-rw-r--r--nixos/modules/security/audit.nix6
-rw-r--r--nixos/modules/security/duosec.nix4
-rw-r--r--nixos/modules/security/pam.nix50
-rw-r--r--nixos/modules/security/sudo.nix135
-rw-r--r--nixos/modules/security/wrappers/default.nix4
-rw-r--r--nixos/modules/security/wrappers/wrapper.c2
7 files changed, 217 insertions, 45 deletions
diff --git a/nixos/modules/security/acme.nix b/nixos/modules/security/acme.nix
index df0b4986eb82..eb705007d028 100644
--- a/nixos/modules/security/acme.nix
+++ b/nixos/modules/security/acme.nix
@@ -6,10 +6,11 @@ let
 
   cfg = config.security.acme;
 
-  certOpts = { ... }: {
+  certOpts = { name, ... }: {
     options = {
       webroot = mkOption {
         type = types.str;
+        example = "/var/lib/acme/acme-challenges";
         description = ''
           Where the webroot of the HTTP vhost is located.
           <filename>.well-known/acme-challenge/</filename> directory
@@ -20,8 +21,8 @@ let
       };
 
       domain = mkOption {
-        type = types.nullOr types.str;
-        default = null;
+        type = types.str;
+        default = name;
         description = "Domain to fetch certificate for (defaults to the entry name)";
       };
 
@@ -48,7 +49,7 @@ let
         default = false;
         description = ''
           Give read permissions to the specified group
-          (<option>security.acme.group</option>) to read SSL private certificates.
+          (<option>security.acme.cert.&lt;name&gt;.group</option>) to read SSL private certificates.
         '';
       };
 
@@ -110,7 +111,7 @@ let
           }
         '';
         description = ''
-          Extra domain names for which certificates are to be issued, with their
+          A list of extra domain names, which are included in the one certificate to be issued, with their
           own server roots if needed.
         '';
       };
@@ -211,8 +212,9 @@ in
                 domain = if data.domain != null then data.domain else cert;
                 cpath = lpath + optionalString (data.activationDelay != null) ".staging";
                 lpath = "${cfg.directory}/${cert}";
+                cpath = "${cfg.directory}/${cert}";
                 rights = if data.allowKeysForGroup then "750" else "700";
-                cmdline = [ "-v" "-d" domain "--default_root" data.webroot "--valid_min" cfg.validMin ]
+                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)
@@ -284,6 +286,7 @@ in
                 };
                 selfsignedService = {
                   description = "Create preliminary self-signed certificate for ${cert}";
+                  path = [ pkgs.openssl ];
                   preStart = ''
                       if [ ! -d '${cpath}' ]
                       then
@@ -294,37 +297,41 @@ in
                   '';
                   script = 
                     ''
-                      # Create self-signed key
-                      workdir="/run/acme-selfsigned-${cert}"
-                      ${pkgs.openssl.bin}/bin/openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
-                      ${pkgs.openssl.bin}/bin/openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
-                      ${pkgs.openssl.bin}/bin/openssl req -new -key $workdir/server.key -out $workdir/server.csr \
+                      workdir="$(mktemp -d)"
+
+                      # Create CA
+                      openssl genrsa -des3 -passout pass:x -out $workdir/ca.pass.key 2048
+                      openssl rsa -passin pass:x -in $workdir/ca.pass.key -out $workdir/ca.key
+                      openssl req -new -key $workdir/ca.key -out $workdir/ca.csr \
+                        -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=Security Department/CN=example.com"
+                      openssl x509 -req -days 1 -in $workdir/ca.csr -signkey $workdir/ca.key -out $workdir/ca.crt
+
+                      # Create key
+                      openssl genrsa -des3 -passout pass:x -out $workdir/server.pass.key 2048
+                      openssl rsa -passin pass:x -in $workdir/server.pass.key -out $workdir/server.key
+                      openssl req -new -key $workdir/server.key -out $workdir/server.csr \
                         -subj "/C=UK/ST=Warwickshire/L=Leamington/O=OrgName/OU=IT Department/CN=example.com"
-                      ${pkgs.openssl.bin}/bin/openssl x509 -req -days 1 -in $workdir/server.csr -signkey $workdir/server.key -out $workdir/server.crt
+                      openssl x509 -req -days 1 -in $workdir/server.csr -CA $workdir/ca.crt \
+                        -CAkey $workdir/ca.key -CAserial $workdir/ca.srl -CAcreateserial \
+                        -out $workdir/server.crt
 
-                      # Move key to destination
-                      mv $workdir/server.key ${cpath}/key.pem
-                      mv $workdir/server.crt ${cpath}/fullchain.pem
+                      # Copy key to destination
+                      cp $workdir/server.key ${cpath}/key.pem
 
-                      # Create full.pem for e.g. lighttpd (same format as "simp_le ... -f full.pem" creates)
-                      cat "${cpath}/key.pem" "${cpath}/fullchain.pem" > "${cpath}/full.pem"
+                      # Create fullchain.pem (same format as "simp_le ... -f fullchain.pem" creates)
+                      cat $workdir/{server.crt,ca.crt} > "${cpath}/fullchain.pem"
 
-                      # Clean up working directory
-                      rm $workdir/server.csr
-                      rm $workdir/server.pass.key
+                      # Create full.pem for e.g. lighttpd
+                      cat $workdir/{server.key,server.crt,ca.crt} > "${cpath}/full.pem"
 
                       # Give key acme permissions
-                      chmod ${rights} '${cpath}/key.pem'
-                      chown '${data.user}:${data.group}' '${cpath}/key.pem'
-                      chmod ${rights} '${cpath}/fullchain.pem'
-                      chown '${data.user}:${data.group}' '${cpath}/fullchain.pem'
-                      chmod ${rights} '${cpath}/full.pem'
-                      chown '${data.user}:${data.group}' '${cpath}/full.pem'
+                      chown '${data.user}:${data.group}' "${cpath}/"{key,fullchain,full}.pem
+                      chmod ${rights} "${cpath}/"{key,fullchain,full}.pem
                     '';
                   serviceConfig = {
                     Type = "oneshot";
-                    RuntimeDirectory = "acme-selfsigned-${cert}";
                     PermissionsStartOnly = true;
+                    PrivateTmp = true;
                     User = data.user;
                     Group = data.group;
                   };
diff --git a/nixos/modules/security/audit.nix b/nixos/modules/security/audit.nix
index 7ac21fd96507..2b22bdd9f0ae 100644
--- a/nixos/modules/security/audit.nix
+++ b/nixos/modules/security/audit.nix
@@ -13,7 +13,7 @@ let
   };
 
   disableScript = pkgs.writeScript "audit-disable" ''
-    #!${pkgs.stdenv.shell} -eu
+    #!${pkgs.runtimeShell} -eu
     # Explicitly disable everything, as otherwise journald might start it.
     auditctl -D
     auditctl -e 0 -a task,never
@@ -23,7 +23,7 @@ let
   # put in the store like this. At the same time, it doesn't feel like a huge deal and working
   # around that is a pain so I'm leaving it like this for now.
   startScript = pkgs.writeScript "audit-start" ''
-    #!${pkgs.stdenv.shell} -eu
+    #!${pkgs.runtimeShell} -eu
     # Clear out any rules we may start with
     auditctl -D
 
@@ -43,7 +43,7 @@ let
   '';
 
   stopScript = pkgs.writeScript "audit-stop" ''
-    #!${pkgs.stdenv.shell} -eu
+    #!${pkgs.runtimeShell} -eu
     # Clear the rules
     auditctl -D
 
diff --git a/nixos/modules/security/duosec.nix b/nixos/modules/security/duosec.nix
index 9ca818e86ffa..df6108dede7c 100644
--- a/nixos/modules/security/duosec.nix
+++ b/nixos/modules/security/duosec.nix
@@ -25,14 +25,14 @@ let
   loginCfgFile = optional cfg.ssh.enable
     { source = pkgs.writeText "login_duo.conf" configFile;
       mode   = "0600";
-      uid    = config.ids.uids.sshd;
+      user   = "sshd";
       target = "duo/login_duo.conf";
     };
 
   pamCfgFile = optional cfg.pam.enable
     { source = pkgs.writeText "pam_duo.conf" configFile;
       mode   = "0600";
-      uid    = config.ids.uids.sshd;
+      user   = "sshd";
       target = "duo/pam_duo.conf";
     };
 in
diff --git a/nixos/modules/security/pam.nix b/nixos/modules/security/pam.nix
index 2d6713311a45..48998285d89d 100644
--- a/nixos/modules/security/pam.nix
+++ b/nixos/modules/security/pam.nix
@@ -46,6 +46,18 @@ let
         '';
       };
 
+      googleAuthenticator = {
+        enable = mkOption {
+          default = false;
+          type = types.bool;
+          description = ''
+            If set, users with enabled Google Authenticator (created
+            <filename>~/.google_authenticator</filename>) will be required
+            to provide Google Authenticator token to log in.
+          '';
+        };
+      };
+
       usbAuth = mkOption {
         default = config.security.pam.usb.enable;
         type = types.bool;
@@ -222,6 +234,22 @@ let
           password, KDE will prompt separately after login.
         '';
       };
+      sssdStrictAccess = mkOption {
+        default = false;
+        type = types.bool;
+        description = "enforce sssd access control";
+      };
+
+      enableGnomeKeyring = mkOption {
+        default = false;
+        type = types.bool;
+        description = ''
+          If enabled, pam_gnome_keyring will attempt to automatically unlock the
+          user's default Gnome keyring upon login. If the user login password does
+          not match their keyring password, Gnome Keyring will prompt separately
+          after login.
+        '';
+      };
 
       text = mkOption {
         type = types.nullOr types.lines;
@@ -241,11 +269,13 @@ let
       text = mkDefault
         (''
           # Account management.
-          account sufficient pam_unix.so
+          account ${if cfg.sssdStrictAccess then "required" else "sufficient"} pam_unix.so
           ${optionalString use_ldap
               "account sufficient ${pam_ldap}/lib/security/pam_ldap.so"}
-          ${optionalString config.services.sssd.enable
+          ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess==false)
               "account sufficient ${pkgs.sssd}/lib/security/pam_sss.so"}
+          ${optionalString (config.services.sssd.enable && cfg.sssdStrictAccess)
+              "account [default=bad success=ok user_unknown=ignore] ${pkgs.sssd}/lib/security/pam_sss.so"}
           ${optionalString config.krb5.enable
               "account sufficient ${pam_krb5}/lib/security/pam_krb5.so"}
 
@@ -273,7 +303,12 @@ let
           # prompts the user for password so we run it once with 'required' at an
           # earlier point and it will run again with 'sufficient' further down.
           # We use try_first_pass the second time to avoid prompting password twice
-          (optionalString (cfg.unixAuth && (config.security.pam.enableEcryptfs || cfg.pamMount || cfg.enableKwallet)) ''
+          (optionalString (cfg.unixAuth &&
+          (config.security.pam.enableEcryptfs
+            || cfg.pamMount
+            || cfg.enableKwallet
+            || cfg.enableGnomeKeyring
+            || cfg.googleAuthenticator.enable)) ''
               auth required pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth
               ${optionalString config.security.pam.enableEcryptfs
                 "auth optional ${pkgs.ecryptfs}/lib/security/pam_ecryptfs.so unwrap"}
@@ -282,6 +317,10 @@ let
               ${optionalString cfg.enableKwallet
                 ("auth optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
                  " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
+              ${optionalString cfg.enableGnomeKeyring
+                ("auth optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so")}
+              ${optionalString cfg.googleAuthenticator.enable
+                  "auth required ${pkgs.googleAuthenticator}/lib/security/pam_google_authenticator.so no_increment_hotp"}
             '') + ''
           ${optionalString cfg.unixAuth
               "auth sufficient pam_unix.so ${optionalString cfg.allowNullPassword "nullok"} likeauth try_first_pass"}
@@ -351,6 +390,10 @@ let
           ${optionalString (cfg.enableKwallet)
               ("session optional ${pkgs.plasma5.kwallet-pam}/lib/security/pam_kwallet5.so" +
                " kwalletd=${pkgs.libsForQt5.kwallet.bin}/bin/kwalletd5")}
+          ${optionalString (cfg.enableGnomeKeyring)
+              "session optional ${pkgs.gnome3.gnome-keyring}/lib/security/pam_gnome_keyring.so auto_start"}
+          ${optionalString (config.virtualisation.lxc.lxcfs.enable)
+               "session optional ${pkgs.lxc}/lib/security/pam_cgfs.so -c all"}
         '');
     };
 
@@ -519,7 +562,6 @@ in
         ftp = {};
         i3lock = {};
         i3lock-color = {};
-        swaylock = {};
         screen = {};
         vlock = {};
         xlock = {};
diff --git a/nixos/modules/security/sudo.nix b/nixos/modules/security/sudo.nix
index cfd0595e63b7..24283e1d6165 100644
--- a/nixos/modules/security/sudo.nix
+++ b/nixos/modules/security/sudo.nix
@@ -8,6 +8,22 @@ let
 
   inherit (pkgs) sudo;
 
+  toUserString = user: if (isInt user) then "#${toString user}" else "${user}";
+  toGroupString = group: if (isInt group) then "%#${toString group}" else "%${group}";
+
+  toCommandOptionsString = options:
+    "${concatStringsSep ":" options}${optionalString (length options != 0) ":"} ";
+
+  toCommandsString = commands:
+    concatStringsSep ", " (
+      map (command:
+        if (isString command) then
+          command
+        else
+          "${toCommandOptionsString command.options}${command.command}"
+      ) commands
+    );
+
 in
 
 {
@@ -31,8 +47,8 @@ in
       default = true;
       description =
         ''
-          Whether users of the <code>wheel</code> group can execute
-          commands as super user without entering a password.
+          Whether users of the <code>wheel</code> group must
+          provide a password to run commands as super user via <command>sudo</command>.
         '';
       };
 
@@ -47,6 +63,97 @@ in
         '';
     };
 
+    security.sudo.extraRules = mkOption {
+      description = ''
+        Define specific rules to be in the <filename>sudoers</filename> file.
+      '';
+      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" ]; 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 {
+            type = with types; listOf (either string int);
+            description = ''
+              The usernames / UIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          groups = mkOption {
+            type = with types; listOf (either string int);
+            description = ''
+              The groups / GIDs this rule should apply for.
+            '';
+            default = [];
+          };
+
+          host = mkOption {
+            type = types.string;
+            default = "ALL";
+            description = ''
+              For what host this rule should apply.
+            '';
+          };
+
+          runAs = mkOption {
+            type = with types; string;
+            default = "ALL:ALL";
+            description = ''
+              Under which user/group the specified command is allowed to run.
+
+              A user can be specified using just the username: <code>"foo"</code>.
+              It is also possible to specify a user/group combination using <code>"foo:bar"</code>
+              or to only allow running as a specific group with <code>":bar"</code>.
+            '';
+          };
+
+          commands = mkOption {
+            description = ''
+              The commands for which the rule should apply.
+            '';
+            type = with types; listOf (either string (submodule {
+
+              options = {
+                command = mkOption {
+                  type = with types; string;
+                  description = ''
+                    A command being either just a path to a binary to allow any arguments,
+                    the full command with arguments pre-set or with <code>""</code> used as the argument,
+                    not allowing arguments to the command at all.
+                  '';
+                };
+
+                options = mkOption {
+                  type = with types; listOf (enum [ "NOPASSWD" "PASSWD" "NOEXEC" "EXEC" "SETENV" "NOSETENV" "LOG_INPUT" "NOLOG_INPUT" "LOG_OUTPUT" "NOLOG_OUTPUT" ]);
+                  description = ''
+                    Options for running the command. Refer to the <a href="https://www.sudo.ws/man/1.7.10/sudoers.man.html">sudo manual</a>.
+                  '';
+                  default = [];
+                };
+              };
+
+            }));
+          };
+        };
+      });
+    };
+
     security.sudo.extraConfig = mkOption {
       type = types.lines;
       default = "";
@@ -61,10 +168,16 @@ in
 
   config = mkIf cfg.enable {
 
+    security.sudo.extraRules = [
+      { groups = [ "wheel" ];
+        commands = [ { command = "ALL"; options = (if cfg.wheelNeedsPassword then [ "SETENV" ] else [ "NOPASSWD" "SETENV" ]); } ];
+      }
+    ];
+
     security.sudo.configFile =
       ''
         # Don't edit this file. Set the NixOS options ‘security.sudo.configFile’
-        # or ‘security.sudo.extraConfig’ instead.
+        # or ‘security.sudo.extraRules’ instead.
 
         # Keep SSH_AUTH_SOCK so that pam_ssh_agent_auth.so can do its magic.
         Defaults env_keep+=SSH_AUTH_SOCK
@@ -72,8 +185,18 @@ in
         # "root" is allowed to do anything.
         root        ALL=(ALL:ALL) SETENV: ALL
 
-        # Users in the "wheel" group can do anything.
-        %wheel      ALL=(ALL:ALL) ${if cfg.wheelNeedsPassword then "" else "NOPASSWD: ALL, "}SETENV: ALL
+        # extraRules
+        ${concatStringsSep "\n" (
+          lists.flatten (
+            map (
+              rule: if (length rule.commands != 0) then [
+                (map (user: "${toUserString user}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.users)
+                (map (group: "${toGroupString group}	${rule.host}=(${rule.runAs})	${toCommandsString rule.commands}") rule.groups)
+              ] else []
+            ) cfg.extraRules
+          )
+        )}
+
         ${cfg.extraConfig}
       '';
 
@@ -92,7 +215,7 @@ in
           { src = pkgs.writeText "sudoers-in" cfg.configFile; }
           # Make sure that the sudoers file is syntactically valid.
           # (currently disabled - NIXOS-66)
-          "${pkgs.sudo}/sbin/visudo -f $src -c && cp $src $out";
+          "${pkgs.buildPackages.sudo}/sbin/visudo -f $src -c && cp $src $out";
         target = "sudoers";
         mode = "0440";
       };
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 1f64213accd4..77e4b2a616d8 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -17,7 +17,7 @@ let
     hardeningEnable = [ "pie" ];
     installPhase = ''
       mkdir -p $out/bin
-      gcc -Wall -O2 -DWRAPPER_DIR=\"${parentWrapperDir}\" \
+      $CC -Wall -O2 -DWRAPPER_DIR=\"${parentWrapperDir}\" \
           -lcap-ng -lcap ${./wrapper.c} -o $out/bin/security-wrapper
     '';
   };
@@ -79,7 +79,7 @@ let
                  ({ owner = "root";
                     group = "root";
                   } // s)
-          else if 
+          else if
              (s ? "setuid" && s.setuid) ||
              (s ? "setgid" && s.setgid) ||
              (s ? "permissions")
diff --git a/nixos/modules/security/wrappers/wrapper.c b/nixos/modules/security/wrappers/wrapper.c
index 7091e314bb22..494e9e93ac22 100644
--- a/nixos/modules/security/wrappers/wrapper.c
+++ b/nixos/modules/security/wrappers/wrapper.c
@@ -10,8 +10,8 @@
 #include <errno.h>
 #include <linux/capability.h>
 #include <sys/capability.h>
-#include <linux/prctl.h>
 #include <sys/prctl.h>
+#include <limits.h>
 #include <cap-ng.h>
 
 // Make sure assertions are not compiled out, we use them to codify