parent
9de070e620
commit
e92b8402b0
@ -1,191 +0,0 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
let |
||||
|
||||
inherit (config.security) run-permissionsWrapperDir permissionsWrapperDir; |
||||
|
||||
isNotNull = v: if v != null then true else false; |
||||
|
||||
cfg = config.security.permissionsWrappers; |
||||
|
||||
setcapWrappers = import ./setcap-wrapper-drv.nix { |
||||
inherit config lib pkgs; |
||||
}; |
||||
|
||||
setuidWrappers = import ./setuid-wrapper-drv.nix { |
||||
inherit config lib pkgs; |
||||
}; |
||||
|
||||
###### Activation script for the setcap wrappers |
||||
configureSetcapWrapper = |
||||
{ program |
||||
, capabilities |
||||
, source ? null |
||||
, owner ? "nobody" |
||||
, group ? "nogroup" |
||||
}: '' |
||||
cp ${setcapWrappers}/bin/${program}.wrapper $permissionsWrapperDir/${program} |
||||
|
||||
# Prevent races |
||||
chmod 0000 $permissionsWrapperDir/${program} |
||||
chown ${owner}.${group} $permissionsWrapperDir/${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. |
||||
# |
||||
# Only set the capabilities though if we're being told to |
||||
# do so. |
||||
${pkgs.libcap.out}/bin/setcap "cap_setpcap,${capabilities}" $permissionsWrapperDir/${program} |
||||
|
||||
# Set the executable bit |
||||
chmod u+rx,g+x,o+x $permissionsWrapperDir/${program} |
||||
''; |
||||
|
||||
###### Activation script for the setuid wrappers |
||||
configureSetuidWrapper = |
||||
{ program |
||||
, source ? null |
||||
, owner ? "nobody" |
||||
, group ? "nogroup" |
||||
, setuid ? false |
||||
, setgid ? false |
||||
, permissions ? "u+rx,g+x,o+x" |
||||
}: '' |
||||
cp ${setuidWrappers}/bin/${program}.wrapper $permissionsWrapperDir/${program} |
||||
|
||||
# Prevent races |
||||
chmod 0000 $permissionsWrapperDir/${program} |
||||
chown ${owner}.${group} $permissionsWrapperDir/${program} |
||||
|
||||
chmod "u${if setuid then "+" else "-"}s,g${if setgid then "+" else "-"}s,${permissions}" $permissionsWrapperDir/${program} |
||||
''; |
||||
in |
||||
{ |
||||
|
||||
###### interface |
||||
|
||||
options = { |
||||
security.permissionsWrappers.setcap = lib.mkOption { |
||||
type = lib.types.listOf lib.types.attrs; |
||||
default = []; |
||||
example = |
||||
[ { program = "ping"; |
||||
source = "${pkgs.iputils.out}/bin/ping"; |
||||
owner = "nobody"; |
||||
group = "nogroup"; |
||||
capabilities = "cap_net_raw+ep"; |
||||
} |
||||
]; |
||||
description = '' |
||||
This option sets capabilities on a wrapper program that |
||||
propagates those capabilities down to the wrapped, real |
||||
program. |
||||
|
||||
The `program` attribute is the name of the program to be |
||||
wrapped. If no `source` attribute is provided, specifying the |
||||
absolute path to the program, then the program will be |
||||
searched for in the path environment variable. |
||||
|
||||
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. |
||||
''; |
||||
}; |
||||
|
||||
security.permissionsWrappers.setuid = lib.mkOption { |
||||
type = lib.types.listOf lib.types.attrs; |
||||
default = []; |
||||
example = |
||||
[ { program = "sendmail"; |
||||
source = "/nix/store/.../bin/sendmail"; |
||||
owner = "nobody"; |
||||
group = "postdrop"; |
||||
setuid = false; |
||||
setgid = true; |
||||
permissions = "u+rx,g+x,o+x"; |
||||
} |
||||
]; |
||||
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). |
||||
''; |
||||
}; |
||||
|
||||
security.permissionsWrapperDir = lib.mkOption { |
||||
type = lib.types.path; |
||||
default = "/var/permissions-wrappers"; |
||||
internal = true; |
||||
description = '' |
||||
This option defines the path to the permissions wrappers. It |
||||
should not be overriden. |
||||
''; |
||||
}; |
||||
|
||||
security.run-permissionsWrapperDir = lib.mkOption { |
||||
type = lib.types.path; |
||||
default = "/run/permissions-wrapper-dirs"; |
||||
internal = true; |
||||
description = '' |
||||
This option defines the run path to the permissions |
||||
wrappers. It should not be overriden. |
||||
''; |
||||
}; |
||||
|
||||
}; |
||||
|
||||
|
||||
###### implementation |
||||
|
||||
config = { |
||||
|
||||
# Make sure our setcap-wrapper dir exports to the PATH env |
||||
# variable when initializing the shell |
||||
environment.extraInit = '' |
||||
# The permissions wrappers override other bin directories. |
||||
export PATH="${permissionsWrapperDir}:$PATH" |
||||
''; |
||||
|
||||
system.activationScripts.wrapper-dir = '' |
||||
mkdir -p "${permissionsWrapperDir}" |
||||
''; |
||||
|
||||
###### setcap activation script |
||||
system.activationScripts.permissions-wrappers = |
||||
lib.stringAfter [ "users" ] |
||||
'' |
||||
# Look in the system path and in the default profile for |
||||
# programs to be wrapped. |
||||
PERMISSIONS_WRAPPER_PATH=${config.system.path}/bin:${config.system.path}/sbin |
||||
|
||||
mkdir -p ${run-permissionsWrapperDir} |
||||
permissionsWrapperDir=$(mktemp --directory --tmpdir=${run-permissionsWrapperDir} permissions-wrappers.XXXXXXXXXX) |
||||
chmod a+rx $permissionsWrapperDir |
||||
|
||||
${lib.concatMapStrings configureSetcapWrapper (builtins.filter isNotNull cfg.setcap)} |
||||
${lib.concatMapStrings configureSetuidWrapper (builtins.filter isNotNull cfg.setuid)} |
||||
|
||||
if [ -L ${permissionsWrapperDir} ]; then |
||||
# Atomically replace the symlink |
||||
# See https://axialcorps.com/2013/07/03/atomically-replacing-files-and-directories/ |
||||
old=$(readlink ${permissionsWrapperDir}) |
||||
ln --symbolic --force --no-dereference $permissionsWrapperDir ${permissionsWrapperDir}-tmp |
||||
mv --no-target-directory ${permissionsWrapperDir}-tmp ${permissionsWrapperDir} |
||||
rm --force --recursive $old |
||||
elif [ -d ${permissionsWrapperDir} ]; then |
||||
# Compatibility with old state, just remove the folder and symlink |
||||
rm -f ${permissionsWrapperDir}/* |
||||
# if it happens to be a tmpfs |
||||
${pkgs.utillinux}/bin/umount ${permissionsWrapperDir} || true |
||||
rm -d ${permissionsWrapperDir} |
||||
ln -d --symbolic $permissionsWrapperDir ${permissionsWrapperDir} |
||||
else |
||||
# For initial setup |
||||
ln --symbolic $permissionsWrapperDir ${permissionsWrapperDir} |
||||
fi |
||||
''; |
||||
}; |
||||
} |
@ -0,0 +1,191 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
let |
||||
|
||||
inherit (config.security) wrapperDir; |
||||
|
||||
isNotNull = v: if v != null || v != "" then true else false; |
||||
|
||||
cfg = config.security.wrappers; |
||||
|
||||
setcapWrappers = import ./setcap-wrapper-drv.nix { |
||||
inherit config lib pkgs; |
||||
}; |
||||
|
||||
setuidWrappers = import ./setuid-wrapper-drv.nix { |
||||
inherit config lib pkgs; |
||||
}; |
||||
|
||||
###### Activation script for the setcap wrappers |
||||
mkSetcapProgram = |
||||
{ program |
||||
, capabilities |
||||
, source ? null |
||||
, owner ? "nobody" |
||||
, group ? "nogroup" |
||||
... |
||||
}: '' |
||||
cp ${setcapWrappers}/bin/${program}.wrapper $wrapperDir/${program} |
||||
|
||||
# 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. |
||||
# |
||||
# Only set the capabilities though if we're being told to |
||||
# do so. |
||||
${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 |
||||
, source ? null |
||||
, owner ? "nobody" |
||||
, group ? "nogroup" |
||||
, setuid ? false |
||||
, setgid ? false |
||||
, permissions ? "u+rx,g+x,o+x" |
||||
... |
||||
}: '' |
||||
cp ${setuidWrappers}/bin/${program}.wrapper $wrapperDir/${program} |
||||
|
||||
# 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} |
||||
''; |
||||
in |
||||
{ |
||||
|
||||
###### interface |
||||
|
||||
options = { |
||||
security.wrappers.setcap = lib.mkOption { |
||||
type = lib.types.listOf lib.types.attrs; |
||||
default = []; |
||||
example = |
||||
[ { program = "ping"; |
||||
source = "${pkgs.iputils.out}/bin/ping"; |
||||
owner = "nobody"; |
||||
group = "nogroup"; |
||||
capabilities = "cap_net_raw+ep"; |
||||
} |
||||
]; |
||||
description = '' |
||||
This option sets capabilities on a wrapper program that |
||||
propagates those capabilities down to the wrapped, real |
||||
program. |
||||
|
||||
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. |
||||
|
||||
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. |
||||
''; |
||||
}; |
||||
|
||||
security.setuidPrograms = mkOption { |
||||
type = types.listOf types.str; |
||||
default = []; |
||||
example = ["passwd"]; |
||||
description = '' |
||||
The Nix store cannot contain setuid/setgid programs directly. |
||||
For this reason, NixOS can automatically generate wrapper |
||||
programs that have the necessary privileges. This option |
||||
lists the names of programs in the system environment for |
||||
which setuid root wrappers should be created. |
||||
''; |
||||
}; |
||||
|
||||
security.wrappers = lib.mkOption { |
||||
type = lib.types.attrs; |
||||
default = {}; |
||||
example = { |
||||
sendmail.source = "/nix/store/.../bin/sendmail"; |
||||
}; |
||||
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). |
||||
''; |
||||
}; |
||||
|
||||
security.old-wrapperDir = lib.mkOption { |
||||
type = lib.types.path; |
||||
default = "/var/setuid-wrappers"; |
||||
internal = true; |
||||
description = '' |
||||
This option defines the path to the wrapper programs. It |
||||
should not be overriden. |
||||
''; |
||||
}; |
||||
|
||||
security.wrapperDir = lib.mkOption { |
||||
type = lib.types.path; |
||||
default = "/run/wrappers"; |
||||
internal = true; |
||||
description = '' |
||||
This option defines the path to the wrapper programs. It |
||||
should not be overriden. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
###### implementation |
||||
config = { |
||||
# Make sure our setcap-wrapper dir exports to the PATH env |
||||
# variable when initializing the shell |
||||
environment.extraInit = '' |
||||
# The permissions wrappers override other bin directories. |
||||
export PATH="${wrapperDir}:$PATH" |
||||
''; |
||||
|
||||
###### setcap activation script |
||||
system.activationScripts.wrappers = |
||||
let |
||||
programs = |
||||
(map (x: { program = x; owner = "root"; group = "root"; setuid = true; }) |
||||
config.security.setuidPrograms) |
||||
++ lib.mapAttrsToList |
||||
(n: v: (if v ? "program" then v else v // {program=n;})) |
||||
cfg.wrappers; |
||||
|
||||
wrapperPrograms = |
||||
builtins.map |
||||
(s: if (s ? "setuid" && s.setuid == true) || |
||||
(s ? "setguid" && s.setguid == true) || |
||||
(s ? "permissions") |
||||
then mkSetuidProgram s |
||||
else if (s ? "capabilities") |
||||
then mkSetcapProgram s |
||||
else "" |
||||
) programs; |
||||
|
||||
in lib.stringAfter [ "users" ] |
||||
'' |
||||
# 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 |
||||
|
||||
mkdir -p ${wrapperDir} |
||||
wrapperDir=$(mktemp --directory --tmpdir=${wrapperDir} wrappers.XXXXXXXXXX) |
||||
chmod a+rx $wrapperDir |
||||
|
||||
${lib.concatStringsSep "\n" (builtins.filter isNotNull cfg.wrappers)} |
||||
''; |
||||
}; |
||||
} |
@ -1,18 +1,18 @@ |
||||
{ config, lib, pkgs, ... }: |
||||
|
||||
let |
||||
cfg = config.security.permissionsWrappers; |
||||
cfg = config.security.wrappers; |
||||
|
||||
# Produce a shell-code splice intended to be stitched into one of |
||||
# the build or install phases within the derivation. |
||||
mkSetuidWrapper = { program, source ? null, ...}: '' |
||||
if ! source=${if source != null then source else "$(readlink -f $(PATH=$PERMISSIONS_WRAPPER_PATH type -tP ${program}))"}; then |
||||
# If we can't find the program, fall back to the |
||||
# system profile. |
||||
source=/nix/var/nix/profiles/default/bin/${program} |
||||
if ! source=${if source != null then source else "$(readlink -f $(PATH=$WRAPPER_PATH type -tP ${program}))"}; then |
||||
# If we can't find the program, fall back to the |
||||
# system profile. |
||||
source=/nix/var/nix/profiles/default/bin/${program} |
||||
fi |
||||
|
||||
gcc -Wall -O2 -DWRAPPER_SETUID=1 -DSOURCE_PROG=\"$source\" -DWRAPPER_DIR=\"${config.security.run-permissionsWrapperDir}\" \ |
||||
gcc -Wall -O2 -DWRAPPER_SETUID=1 -DSOURCE_PROG=\"$source\" -DWRAPPER_DIR=\"${config.security.run-wrapperDir}\" \ |
||||
-lcap-ng -lcap ${./permissions-wrapper.c} -o $out/bin/${program}.wrapper -L ${pkgs.libcap.lib}/lib -L ${pkgs.libcap_ng}/lib \ |
||||
-I ${pkgs.libcap.dev}/include -I ${pkgs.libcap_ng}/include -I ${pkgs.linuxHeaders}/include |
||||
''; |
Loading…
Reference in new issue