summary refs log tree commit diff
path: root/nixos/modules/security/wrappers
diff options
context:
space:
mode:
authorrnhmjoj <rnhmjoj@inventati.org>2021-06-09 01:41:26 +0200
committerrnhmjoj <rnhmjoj@inventati.org>2021-09-12 21:43:03 +0200
commit904f68fb0fc01cf4072c1215416eb4e2b9fc4e56 (patch)
treee71d7e4ac7d0820c1436ee5ec0fd67e6458a6c12 /nixos/modules/security/wrappers
parentc80b1155c9dd13c10473ed9d825589351f60d06c (diff)
downloadnixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar.gz
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar.bz2
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar.lz
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar.xz
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.tar.zst
nixpkgs-904f68fb0fc01cf4072c1215416eb4e2b9fc4e56.zip
nixos/security/wrappers: make well-typed
The security.wrappers option is morally a set of submodules but it's
actually (un)typed as a generic attribute set. This is bad for several
reasons:

1. Some of the "submodule" option are not document;
2. the default values are not documented and are chosen based on
   somewhat bizarre rules (issue #23217);
3. It's not possible to override an existing wrapper due to the
   dumb types.attrs.merge strategy;
4. It's easy to make mistakes that will go unnoticed, which is
   really bad given the sensitivity of this module (issue #47839).

This makes the option a proper set of submodule and add strict types and
descriptions to every sub-option. Considering it's not yet clear if the
way the default values are picked is intended, this reproduces the current
behavior, but it's now documented explicitly.
Diffstat (limited to 'nixos/modules/security/wrappers')
-rw-r--r--nixos/modules/security/wrappers/default.nix176
1 files changed, 119 insertions, 57 deletions
diff --git a/nixos/modules/security/wrappers/default.nix b/nixos/modules/security/wrappers/default.nix
index 1e65f451515..74dfd86b86a 100644
--- a/nixos/modules/security/wrappers/default.nix
+++ b/nixos/modules/security/wrappers/default.nix
@@ -5,23 +5,108 @@ let
 
   parentWrapperDir = dirOf wrapperDir;
 
-  programs =
-    (lib.mapAttrsToList
-      (n: v: (if v ? program then v else v // {program=n;}))
-      wrappers);
-
   securityWrapper = pkgs.callPackage ./wrapper.nix {
     inherit parentWrapperDir;
   };
 
+  fileModeType =
+    let
+      # taken from the chmod(1) man page
+      symbolic = "[ugoa]*([-+=]([rwxXst]*|[ugo]))+|[-+=][0-7]+";
+      numeric = "[-+=]?[0-7]{0,4}";
+      mode = "((${symbolic})(,${symbolic})*)|(${numeric})";
+    in
+     lib.types.strMatching mode
+     // { description = "file mode string"; };
+
+  wrapperType = lib.types.submodule ({ name, config, ... }: {
+    options.source = lib.mkOption
+      { type = lib.types.path;
+        description = "The absolute path to the program to be wrapped.";
+      };
+    options.program = lib.mkOption
+      { type = with lib.types; nullOr str;
+        default = name;
+        description = ''
+          The name of the wrapper program. Defaults to the attribute name.
+        '';
+      };
+    options.owner = lib.mkOption
+      { type = lib.types.str;
+        default = with config;
+          if (capabilities != "") || !(setuid || setgid || permissions != null)
+          then "root"
+          else "nobody";
+        description = ''
+          The owner of the wrapper program. Defaults to <literal>root</literal>
+          if any capability is set and setuid/setgid/permissions are not, otherwise to
+          <literal>nobody</litera>.
+        '';
+      };
+    options.group = lib.mkOption
+      { type = lib.types.str;
+        default = with config;
+          if (capabilities != "") || !(setuid || setgid || permissions != null)
+          then "root"
+          else "nogroup";
+        description = ''
+          The group of the wrapper program. Defaults to <literal>root</literal>
+          if any capability is set and setuid/setgid/permissions are not,
+          otherwise to <literal>nogroup</litera>.
+        '';
+      };
+    options.permissions = lib.mkOption
+      { type = lib.types.nullOr fileModeType;
+        default = null;
+        example = "u+rx,g+x,o+x";
+        apply = x: if x == null then "u+rx,g+x,o+x" else x;
+        description = ''
+          The permissions of the wrapper program. The format is that of a
+          symbolic or numeric file mode understood by <command>chmod</command>.
+        '';
+      };
+    options.capabilities = lib.mkOption
+      { type = lib.types.commas;
+        default = "";
+        description = ''
+          A comma-separated list of capabilities to be given to the wrapper
+          program. For capabilities supported by the system check the
+          <citerefentry>
+            <refentrytitle>capabilities</refentrytitle>
+            <manvolnum>7</manvolnum>
+          </citerefentry>
+          manual page.
+
+          <note><para>
+            <literal>cap_setpcap</literal>, which is required for the wrapper
+            program to be able to raise caps into the Ambient set is NOT raised
+            to the Ambient set so that the real program cannot modify its own
+            capabilities!! This may be too restrictive for cases in which the
+            real program needs cap_setpcap but it at least leans on the side
+            security paranoid vs. too relaxed.
+          </para></note>
+        '';
+      };
+    options.setuid = lib.mkOption
+      { type = lib.types.bool;
+        default = false;
+        description = "Whether to add the setuid bit the wrapper program.";
+      };
+    options.setgid = lib.mkOption
+      { type = lib.types.bool;
+        default = false;
+        description = "Whether to add the setgid bit the wrapper program.";
+      };
+  });
+
   ###### Activation script for the setcap wrappers
   mkSetcapProgram =
     { program
     , capabilities
     , source
-    , owner  ? "nobody"
-    , group  ? "nogroup"
-    , permissions ? "u+rx,g+x,o+x"
+    , owner
+    , group
+    , permissions
     , ...
     }:
     assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
@@ -46,11 +131,11 @@ let
   mkSetuidProgram =
     { program
     , source
-    , owner  ? "nobody"
-    , group  ? "nogroup"
-    , setuid ? false
-    , setgid ? false
-    , permissions ? "u+rx,g+x,o+x"
+    , owner
+    , group
+    , setuid
+    , setgid
+    , permissions
     , ...
     }:
     ''
@@ -66,24 +151,11 @@ let
 
   mkWrappedPrograms =
     builtins.map
-      (s: if (s ? capabilities)
-          then mkSetcapProgram
-                 ({ owner = "root";
-                    group = "root";
-                  } // s)
-          else if
-             (s ? setuid && s.setuid) ||
-             (s ? setgid && s.setgid) ||
-             (s ? permissions)
-          then mkSetuidProgram s
-          else mkSetuidProgram
-                 ({ owner  = "root";
-                    group  = "root";
-                    setuid = true;
-                    setgid = false;
-                    permissions = "u+rx,g+x,o+x";
-                  } // s)
-      ) programs;
+      (opts:
+        if opts.capabilities != ""
+          then mkSetcapProgram opts
+          else mkSetuidProgram opts
+      ) (lib.attrValues wrappers);
 in
 {
   imports = [
@@ -95,7 +167,7 @@ in
 
   options = {
     security.wrappers = lib.mkOption {
-      type = lib.types.attrs;
+      type = lib.types.attrsOf wrapperType;
       default = {};
       example = lib.literalExample
         ''
@@ -109,31 +181,11 @@ in
           }
         '';
       description = ''
-        This option allows the ownership and permissions on the setuid
-        wrappers for specific programs to be overridden from the
-        default (setuid root, but not setgid root).
-
-        <note>
-          <para>The sub-attribute <literal>source</literal> is mandatory,
-          it must be the absolute path to the program to be wrapped.
-          </para>
-
-          <para>The sub-attribute <literal>program</literal> is optional and
-          can give the wrapper program a new name. The default name is the same
-          as the attribute name itself.</para>
-
-          <para>Additionally, this option can set capabilities on a
-          wrapper program that propagates those capabilities down to the
-          wrapped, real program.</para>
-
-          <para>NOTE: cap_setpcap, which is required for the wrapper
-          program to be able to raise caps into the Ambient set is NOT
-          raised to the Ambient set so that the real program cannot
-          modify its own capabilities!! This may be too restrictive for
-          cases in which the real program needs cap_setpcap but it at
-          least leans on the side security paranoid vs. too
-          relaxed.</para>
-        </note>
+        This option effectively allows adding setuid/setgid bits, capabilities,
+        changing file ownership and permissions of a program without directly
+        modifying it. This works by creating a wrapper program under the
+        <option>security.wrapperDir</option> directory, which is then added to
+        the shell <literal>PATH</literal>.
       '';
     };
 
@@ -151,6 +203,16 @@ in
   ###### implementation
   config = {
 
+    assertions = lib.mapAttrsToList
+      (name: opts:
+        { assertion = opts.setuid || opts.setgid -> opts.capabilities == "";
+          message = ''
+            The security.wrappers.${name} wrapper is not valid:
+                setuid/setgid and capabilities are mutually exclusive.
+          '';
+        }
+      ) wrappers;
+
     security.wrappers = {
       # These are mount related wrappers that require the +s permission.
       fusermount.source = "${pkgs.fuse}/bin/fusermount";