2017-01-29 04:48:03 +00:00
|
|
|
{ config, lib, pkgs, ... }:
|
|
|
|
let
|
|
|
|
|
2017-02-14 21:20:27 +00:00
|
|
|
inherit (config.security) wrapperDir wrappers;
|
2017-01-29 07:27:11 +00:00
|
|
|
|
|
|
|
programs =
|
2017-01-29 07:41:39 +00:00
|
|
|
(lib.mapAttrsToList
|
|
|
|
(n: v: (if v ? "program" then v else v // {program=n;}))
|
|
|
|
wrappers);
|
2017-01-29 04:48:03 +00:00
|
|
|
|
2017-02-14 00:03:06 +00:00
|
|
|
securityWrapper = pkgs.stdenv.mkDerivation {
|
2017-02-14 14:27:40 +00:00
|
|
|
name = "security-wrapper";
|
|
|
|
phases = [ "installPhase" "fixupPhase" ];
|
|
|
|
buildInputs = [ pkgs.libcap pkgs.libcap_ng pkgs.linuxHeaders ];
|
|
|
|
hardeningEnable = [ "pie" ];
|
2017-02-14 00:03:06 +00:00
|
|
|
installPhase = ''
|
|
|
|
mkdir -p $out/bin
|
|
|
|
parentWrapperDir=$(dirname ${wrapperDir})
|
|
|
|
gcc -Wall -O2 -DWRAPPER_DIR=\"$parentWrapperDir\" \
|
2017-02-14 14:27:40 +00:00
|
|
|
-lcap-ng -lcap ${./wrapper.c} -o $out/bin/security-wrapper
|
2017-02-14 00:03:06 +00:00
|
|
|
'';
|
|
|
|
};
|
2017-01-29 04:48:03 +00:00
|
|
|
|
|
|
|
###### Activation script for the setcap wrappers
|
|
|
|
mkSetcapProgram =
|
|
|
|
{ program
|
|
|
|
, capabilities
|
2017-02-14 00:28:13 +00:00
|
|
|
, source
|
2017-01-29 04:48:03 +00:00
|
|
|
, owner ? "nobody"
|
|
|
|
, group ? "nogroup"
|
2017-01-29 07:20:02 +00:00
|
|
|
, ...
|
2017-01-30 18:26:56 +00:00
|
|
|
}:
|
2017-01-29 07:07:12 +00:00
|
|
|
assert (lib.versionAtLeast (lib.getVersion config.boot.kernelPackages.kernel) "4.3");
|
2017-02-14 00:03:06 +00:00
|
|
|
''
|
|
|
|
cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
|
2017-02-14 00:28:13 +00:00
|
|
|
echo -n "${source}" > $wrapperDir/${program}.real
|
2017-01-29 04:48:03 +00:00
|
|
|
|
|
|
|
# Prevent races
|
|
|
|
chmod 0000 $wrapperDir/${program}
|
|
|
|
chown ${owner}.${group} $wrapperDir/${program}
|
|
|
|
|
|
|
|
# Set desired capabilities on the file plus cap_setpcap so
|
|
|
|
# the wrapper program can elevate the capabilities set on
|
|
|
|
# its file into the Ambient set.
|
|
|
|
${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $wrapperDir/${program}
|
|
|
|
|
|
|
|
# Set the executable bit
|
|
|
|
chmod u+rx,g+x,o+x $wrapperDir/${program}
|
|
|
|
'';
|
|
|
|
|
|
|
|
###### Activation script for the setuid wrappers
|
|
|
|
mkSetuidProgram =
|
|
|
|
{ program
|
2017-02-14 00:28:13 +00:00
|
|
|
, source
|
2017-01-29 04:48:03 +00:00
|
|
|
, owner ? "nobody"
|
|
|
|
, group ? "nogroup"
|
|
|
|
, setuid ? false
|
|
|
|
, setgid ? false
|
|
|
|
, permissions ? "u+rx,g+x,o+x"
|
2017-01-29 07:20:02 +00:00
|
|
|
, ...
|
2017-01-30 18:26:56 +00:00
|
|
|
}:
|
2017-02-14 00:03:06 +00:00
|
|
|
''
|
|
|
|
cp ${securityWrapper}/bin/security-wrapper $wrapperDir/${program}
|
2017-02-14 00:28:13 +00:00
|
|
|
echo -n "${source}" > $wrapperDir/${program}.real
|
2017-01-29 04:48:03 +00:00
|
|
|
|
|
|
|
# Prevent races
|
|
|
|
chmod 0000 $wrapperDir/${program}
|
|
|
|
chown ${owner}.${group} $wrapperDir/${program}
|
|
|
|
|
|
|
|
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $wrapperDir/${program}
|
|
|
|
'';
|
2017-01-29 07:27:11 +00:00
|
|
|
|
|
|
|
mkWrappedPrograms =
|
|
|
|
builtins.map
|
|
|
|
(s: if (s ? "capabilities")
|
2017-02-14 14:40:12 +00:00
|
|
|
then mkSetcapProgram
|
|
|
|
({ owner = "root";
|
2017-02-14 14:42:08 +00:00
|
|
|
group = "root";
|
2017-02-14 14:40:12 +00:00
|
|
|
} // s)
|
2017-01-29 07:27:11 +00:00
|
|
|
else if
|
|
|
|
(s ? "setuid" && s.setuid == true) ||
|
|
|
|
(s ? "setguid" && s.setguid == true) ||
|
|
|
|
(s ? "permissions")
|
|
|
|
then mkSetuidProgram s
|
2017-01-29 22:47:14 +00:00
|
|
|
else mkSetuidProgram
|
|
|
|
({ owner = "root";
|
|
|
|
group = "root";
|
|
|
|
setuid = true;
|
|
|
|
setgid = false;
|
|
|
|
permissions = "u+rx,g+x,o+x";
|
|
|
|
} // s)
|
2017-01-29 07:27:11 +00:00
|
|
|
) programs;
|
2017-01-29 04:48:03 +00:00
|
|
|
in
|
|
|
|
{
|
|
|
|
|
|
|
|
###### interface
|
|
|
|
|
|
|
|
options = {
|
|
|
|
security.wrappers = lib.mkOption {
|
|
|
|
type = lib.types.attrs;
|
|
|
|
default = {};
|
|
|
|
example = {
|
|
|
|
sendmail.source = "/nix/store/.../bin/sendmail";
|
2017-01-29 07:22:19 +00:00
|
|
|
ping = {
|
|
|
|
source = "${pkgs.iputils.out}/bin/ping";
|
|
|
|
owner = "nobody";
|
|
|
|
group = "nogroup";
|
|
|
|
capabilities = "cap_net_raw+ep";
|
|
|
|
};
|
2017-01-29 04:48:03 +00:00
|
|
|
};
|
|
|
|
description = ''
|
2017-02-15 03:30:04 +00:00
|
|
|
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>Additionally, this option can set capabilities on a
|
|
|
|
wrapper program that propagates those capabilities down to the
|
|
|
|
wrapped, real program.</para>
|
|
|
|
|
|
|
|
<para>The <literal>program</literal> attribute is the name of
|
|
|
|
the program to be wrapped. If no <literal>source</literal>
|
|
|
|
attribute is provided, specifying the absolute path to the
|
|
|
|
program, then the program will be searched for in the path
|
|
|
|
environment variable.</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>
|
2017-01-29 04:48:03 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
|
|
|
|
security.wrapperDir = lib.mkOption {
|
|
|
|
type = lib.types.path;
|
2017-01-29 10:11:01 +00:00
|
|
|
default = "/run/wrappers/bin";
|
2017-01-29 04:48:03 +00:00
|
|
|
internal = true;
|
|
|
|
description = ''
|
|
|
|
This option defines the path to the wrapper programs. It
|
|
|
|
should not be overriden.
|
|
|
|
'';
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
###### implementation
|
|
|
|
config = {
|
2017-01-29 23:10:32 +00:00
|
|
|
|
|
|
|
security.wrappers.fusermount.source = "${pkgs.fuse}/bin/fusermount";
|
|
|
|
|
2017-01-29 07:07:12 +00:00
|
|
|
# Make sure our wrapperDir exports to the PATH env variable when
|
|
|
|
# initializing the shell
|
2017-01-29 04:48:03 +00:00
|
|
|
environment.extraInit = ''
|
2017-01-29 07:07:12 +00:00
|
|
|
# Wrappers override other bin directories.
|
2017-01-29 04:48:03 +00:00
|
|
|
export PATH="${wrapperDir}:$PATH"
|
|
|
|
'';
|
|
|
|
|
|
|
|
###### setcap activation script
|
|
|
|
system.activationScripts.wrappers =
|
2017-01-29 07:27:11 +00:00
|
|
|
lib.stringAfter [ "users" ]
|
2017-01-29 04:48:03 +00:00
|
|
|
''
|
|
|
|
# Look in the system path and in the default profile for
|
|
|
|
# programs to be wrapped.
|
|
|
|
WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin
|
|
|
|
|
2017-01-29 11:05:30 +00:00
|
|
|
# Remove the old /var/setuid-wrappers path from the system...
|
2017-01-29 22:52:23 +00:00
|
|
|
#
|
2017-02-14 14:33:07 +00:00
|
|
|
# TODO: this is only necessary for ugprades 16.09 => 17.x;
|
2017-01-29 22:52:23 +00:00
|
|
|
# this conditional removal block needs to be removed after
|
|
|
|
# the release.
|
2017-02-14 14:33:07 +00:00
|
|
|
if [ -d /var/setuid-wrappers ]; then
|
|
|
|
rm -rf /var/setuid-wrappers
|
2017-01-29 10:11:01 +00:00
|
|
|
fi
|
|
|
|
|
2017-01-29 22:52:23 +00:00
|
|
|
# Remove the old /run/setuid-wrappers-dir path from the
|
|
|
|
# system as well...
|
|
|
|
#
|
|
|
|
# TDOO: this is only necessary for ugprades 16.09 => 17.x;
|
|
|
|
# this conditional removal block needs to be removed after
|
|
|
|
# the release.
|
2017-01-29 22:54:27 +00:00
|
|
|
if [ -d /run/setuid-wrapper-dirs ]; then
|
|
|
|
rm -rf /run/setuid-wrapper-dirs
|
2017-01-29 22:52:23 +00:00
|
|
|
fi
|
|
|
|
|
2017-01-29 11:05:30 +00:00
|
|
|
# Get the "/run/wrappers" path, we want to place the tmpdirs
|
|
|
|
# for the wrappers there
|
2017-01-29 10:11:01 +00:00
|
|
|
parentWrapperDir="$(dirname ${wrapperDir})"
|
|
|
|
|
2017-01-29 11:05:30 +00:00
|
|
|
mkdir -p "$parentWrapperDir"
|
2017-01-29 10:11:01 +00:00
|
|
|
wrapperDir=$(mktemp --directory --tmpdir="$parentWrapperDir" wrappers.XXXXXXXXXX)
|
2017-01-29 04:48:03 +00:00
|
|
|
chmod a+rx $wrapperDir
|
|
|
|
|
2017-01-29 07:07:12 +00:00
|
|
|
${lib.concatStringsSep "\n" mkWrappedPrograms}
|
2017-01-29 10:11:01 +00:00
|
|
|
|
|
|
|
if [ -L ${wrapperDir} ]; then
|
|
|
|
# Atomically replace the symlink
|
|
|
|
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/
|
2017-01-29 11:05:30 +00:00
|
|
|
old=$(readlink -f ${wrapperDir})
|
2017-01-29 10:11:01 +00:00
|
|
|
ln --symbolic --force --no-dereference $wrapperDir ${wrapperDir}-tmp
|
|
|
|
mv --no-target-directory ${wrapperDir}-tmp ${wrapperDir}
|
|
|
|
rm --force --recursive $old
|
|
|
|
elif [ -d ${wrapperDir} ]; then
|
|
|
|
# Compatibility with old state, just remove the folder and symlink
|
|
|
|
rm -f ${wrapperDir}/*
|
|
|
|
# if it happens to be a tmpfs
|
|
|
|
${pkgs.utillinux}/bin/umount ${wrapperDir} || true
|
|
|
|
rm -d ${wrapperDir}
|
|
|
|
ln -d --symbolic $wrapperDir ${wrapperDir}
|
|
|
|
else
|
|
|
|
# For initial setup
|
|
|
|
ln --symbolic $wrapperDir ${wrapperDir}
|
|
|
|
fi
|
2017-01-29 04:48:03 +00:00
|
|
|
'';
|
|
|
|
};
|
|
|
|
}
|