nixos/confinement: Allow to configure /bin/sh
Another thing requested by @edolstra in [1]: We should not provide a different /bin/sh in the chroot, that's just asking for confusion and random shell script breakage. It should be the same shell (i.e. bash) as in a regular environment. While I personally would even go as far to even have a very restricted shell that is not even a shell and basically *only* allows "/bin/sh -c" with only *very* minimal parsing of shell syntax, I do agree that people expect /bin/sh to be bash (or the one configured by environment.binsh) on NixOS. So this should make both others and me happy in that I could just use confinement.binSh = "${pkgs.dash}/bin/dash" for the services I confine. [1]: https://github.com/NixOS/nixpkgs/pull/57519#issuecomment-472855704 Signed-off-by: aszlig <aszlig@nix.build>
This commit is contained in:
parent
0ba48f46da
commit
46f7dd436f
@ -1,6 +1,7 @@
|
|||||||
{ config, pkgs, lib, ... }:
|
{ config, pkgs, lib, ... }:
|
||||||
|
|
||||||
let
|
let
|
||||||
|
toplevelConfig = config;
|
||||||
inherit (lib) types;
|
inherit (lib) types;
|
||||||
inherit (import ../system/boot/systemd-lib.nix {
|
inherit (import ../system/boot/systemd-lib.nix {
|
||||||
inherit config pkgs lib;
|
inherit config pkgs lib;
|
||||||
@ -44,12 +45,15 @@ in {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
options.confinement.withBinSh = lib.mkOption {
|
options.confinement.binSh = lib.mkOption {
|
||||||
type = types.bool;
|
type = types.nullOr types.path;
|
||||||
default = true;
|
default = toplevelConfig.environment.binsh;
|
||||||
|
defaultText = "config.environment.binsh";
|
||||||
|
example = lib.literalExample "\${pkgs.dash}/bin/dash";
|
||||||
description = ''
|
description = ''
|
||||||
Whether to symlink <command>dash</command> as
|
The program to make available as <filename>/bin/sh</filename> inside
|
||||||
<filename>/bin/sh</filename> to the chroot.
|
the chroot. If this is set to <literal>null</literal>, no
|
||||||
|
<filename>/bin/sh</filename> is provided at all.
|
||||||
|
|
||||||
This is useful for some applications, which for example use the
|
This is useful for some applications, which for example use the
|
||||||
<citerefentry>
|
<citerefentry>
|
||||||
@ -81,15 +85,14 @@ in {
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
config = lib.mkIf config.confinement.enable {
|
config = let
|
||||||
serviceConfig = let
|
rootName = "${mkPathSafeName name}-chroot";
|
||||||
rootName = "${mkPathSafeName name}-chroot";
|
inherit (config.confinement) binSh;
|
||||||
in {
|
in lib.mkIf config.confinement.enable {
|
||||||
|
serviceConfig = {
|
||||||
RootDirectory = pkgs.runCommand rootName {} "mkdir \"$out\"";
|
RootDirectory = pkgs.runCommand rootName {} "mkdir \"$out\"";
|
||||||
TemporaryFileSystem = "/";
|
TemporaryFileSystem = "/";
|
||||||
MountFlags = lib.mkDefault "private";
|
MountFlags = lib.mkDefault "private";
|
||||||
} // lib.optionalAttrs config.confinement.withBinSh {
|
|
||||||
BindReadOnlyPaths = [ "${pkgs.dash}/bin/dash:/bin/sh" ];
|
|
||||||
} // lib.optionalAttrs (config.confinement.mode == "full-apivfs") {
|
} // lib.optionalAttrs (config.confinement.mode == "full-apivfs") {
|
||||||
MountAPIVFS = true;
|
MountAPIVFS = true;
|
||||||
PrivateDevices = true;
|
PrivateDevices = true;
|
||||||
@ -108,7 +111,7 @@ in {
|
|||||||
execPkgs = lib.concatMap (opt: let
|
execPkgs = lib.concatMap (opt: let
|
||||||
isSet = config.serviceConfig ? ${opt};
|
isSet = config.serviceConfig ? ${opt};
|
||||||
in lib.optional isSet config.serviceConfig.${opt}) execOpts;
|
in lib.optional isSet config.serviceConfig.${opt}) execOpts;
|
||||||
in execPkgs ++ lib.optional config.confinement.withBinSh pkgs.dash;
|
in execPkgs ++ lib.optional (binSh != null) binSh;
|
||||||
};
|
};
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
@ -146,6 +149,14 @@ in {
|
|||||||
|
|
||||||
echo '[Service]' > "$serviceFile"
|
echo '[Service]' > "$serviceFile"
|
||||||
|
|
||||||
|
# /bin/sh is special here, because the option value could contain a
|
||||||
|
# symlink and we need to properly resolve it.
|
||||||
|
${lib.optionalString (cfg.confinement.binSh != null) ''
|
||||||
|
binsh=${lib.escapeShellArg cfg.confinement.binSh}
|
||||||
|
realprog="$(readlink -e "$binsh")"
|
||||||
|
echo "BindReadOnlyPaths=$realprog:/bin/sh" >> "$serviceFile"
|
||||||
|
''}
|
||||||
|
|
||||||
while read storePath; do
|
while read storePath; do
|
||||||
if [ -L "$storePath" ]; then
|
if [ -L "$storePath" ]; then
|
||||||
# Currently, systemd can't cope with symlinks in Bind(ReadOnly)Paths,
|
# Currently, systemd can't cope with symlinks in Bind(ReadOnly)Paths,
|
||||||
|
@ -106,6 +106,32 @@ import ./make-test.nix {
|
|||||||
$machine->succeed('test ! -e /tmp/canary');
|
$machine->succeed('test ! -e /tmp/canary');
|
||||||
'';
|
'';
|
||||||
}
|
}
|
||||||
|
{ description = "check if /bin/sh works";
|
||||||
|
testScript = ''
|
||||||
|
$machine->succeed(
|
||||||
|
'chroot-exec test -e /bin/sh',
|
||||||
|
'test "$(chroot-exec \'/bin/sh -c "echo bar"\')" = bar',
|
||||||
|
);
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
{ description = "check if suppressing /bin/sh works";
|
||||||
|
config.confinement.binSh = null;
|
||||||
|
testScript = ''
|
||||||
|
$machine->succeed(
|
||||||
|
'chroot-exec test ! -e /bin/sh',
|
||||||
|
'test "$(chroot-exec \'/bin/sh -c "echo foo"\')" != foo',
|
||||||
|
);
|
||||||
|
'';
|
||||||
|
}
|
||||||
|
{ description = "check if we can set /bin/sh to something different";
|
||||||
|
config.confinement.binSh = "${pkgs.hello}/bin/hello";
|
||||||
|
testScript = ''
|
||||||
|
$machine->succeed(
|
||||||
|
'chroot-exec test -e /bin/sh',
|
||||||
|
'test "$(chroot-exec /bin/sh -g foo)" = foo',
|
||||||
|
);
|
||||||
|
'';
|
||||||
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
options.__testSteps = lib.mkOption {
|
options.__testSteps = lib.mkOption {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user