mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-06-16 14:39:23 +03:00
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.
This commit is contained in:
parent
c80b1155c9
commit
904f68fb0f
1 changed files with 119 additions and 57 deletions
|
@ -5,23 +5,108 @@ let
|
||||||
|
|
||||||
parentWrapperDir = dirOf wrapperDir;
|
parentWrapperDir = dirOf wrapperDir;
|
||||||
|
|
||||||
programs =
|
|
||||||
(lib.mapAttrsToList
|
|
||||||
(n: v: (if v ? program then v else v // {program=n;}))
|
|
||||||
wrappers);
|
|
||||||
|
|
||||||
securityWrapper = pkgs.callPackage ./wrapper.nix {
|
securityWrapper = pkgs.callPackage ./wrapper.nix {
|
||||||
inherit parentWrapperDir;
|
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
|
###### Activation script for the setcap wrappers
|
||||||
mkSetcapProgram =
|
mkSetcapProgram =
|
||||||
{ program
|
{ program
|
||||||
, capabilities
|
, capabilities
|
||||||
, source
|
, source
|
||||||
, owner ? "nobody"
|
, owner
|
||||||
, group ? "nogroup"
|
, group
|
||||||
, permissions ? "u+rx,g+x,o+x"
|
, permissions
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
|
assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
|
||||||
|
@ -46,11 +131,11 @@ let
|
||||||
mkSetuidProgram =
|
mkSetuidProgram =
|
||||||
{ program
|
{ program
|
||||||
, source
|
, source
|
||||||
, owner ? "nobody"
|
, owner
|
||||||
, group ? "nogroup"
|
, group
|
||||||
, setuid ? false
|
, setuid
|
||||||
, setgid ? false
|
, setgid
|
||||||
, permissions ? "u+rx,g+x,o+x"
|
, permissions
|
||||||
, ...
|
, ...
|
||||||
}:
|
}:
|
||||||
''
|
''
|
||||||
|
@ -66,24 +151,11 @@ let
|
||||||
|
|
||||||
mkWrappedPrograms =
|
mkWrappedPrograms =
|
||||||
builtins.map
|
builtins.map
|
||||||
(s: if (s ? capabilities)
|
(opts:
|
||||||
then mkSetcapProgram
|
if opts.capabilities != ""
|
||||||
({ owner = "root";
|
then mkSetcapProgram opts
|
||||||
group = "root";
|
else mkSetuidProgram opts
|
||||||
} // s)
|
) (lib.attrValues wrappers);
|
||||||
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;
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
imports = [
|
imports = [
|
||||||
|
@ -95,7 +167,7 @@ in
|
||||||
|
|
||||||
options = {
|
options = {
|
||||||
security.wrappers = lib.mkOption {
|
security.wrappers = lib.mkOption {
|
||||||
type = lib.types.attrs;
|
type = lib.types.attrsOf wrapperType;
|
||||||
default = {};
|
default = {};
|
||||||
example = lib.literalExample
|
example = lib.literalExample
|
||||||
''
|
''
|
||||||
|
@ -109,31 +181,11 @@ in
|
||||||
}
|
}
|
||||||
'';
|
'';
|
||||||
description = ''
|
description = ''
|
||||||
This option allows the ownership and permissions on the setuid
|
This option effectively allows adding setuid/setgid bits, capabilities,
|
||||||
wrappers for specific programs to be overridden from the
|
changing file ownership and permissions of a program without directly
|
||||||
default (setuid root, but not setgid root).
|
modifying it. This works by creating a wrapper program under the
|
||||||
|
<option>security.wrapperDir</option> directory, which is then added to
|
||||||
<note>
|
the shell <literal>PATH</literal>.
|
||||||
<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>
|
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -151,6 +203,16 @@ in
|
||||||
###### implementation
|
###### implementation
|
||||||
config = {
|
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 = {
|
security.wrappers = {
|
||||||
# These are mount related wrappers that require the +s permission.
|
# These are mount related wrappers that require the +s permission.
|
||||||
fusermount.source = "${pkgs.fuse}/bin/fusermount";
|
fusermount.source = "${pkgs.fuse}/bin/fusermount";
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue