Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 8 additions & 17 deletions nixos/modules/virtualisation/oci-containers.nix
Original file line number Diff line number Diff line change
Expand Up @@ -295,28 +295,19 @@ let
'';
};

capAdd = mkOption {
capabilities = mkOption {
type = with types; lazyAttrsOf (nullOr bool);
default = { };
description = ''
Capabilities to add to container
'';
example = literalExpression ''
{
SYS_ADMIN = true;
{
'';
};

capDrop = mkOption {
type = with types; lazyAttrsOf (nullOr bool);
default = { };
description = ''
Capabilities to drop from container
Capabilities to configure for the container.
When set to true, capability is added to the container.
When set to false, capability is dropped from the container.
When null, default runtime settings apply.
'';
example = literalExpression ''
{
SYS_ADMIN = true;
SYS_WRITE = false;
{
'';
};
Expand Down Expand Up @@ -441,10 +432,10 @@ let
++ optional (container.workdir != null) "-w ${escapeShellArg container.workdir}"
++ optional (container.privileged) "--privileged"
++ mapAttrsToList (k: _: "--cap-add=${escapeShellArg k}") (
filterAttrs (_: v: v == true) container.capAdd
filterAttrs (_: v: v == true) container.capabilities
)
++ mapAttrsToList (k: _: "--cap-drop=${escapeShellArg k}") (
filterAttrs (_: v: v == true) container.capDrop
filterAttrs (_: v: v == false) container.capabilities
)
++ map (d: "--device=${escapeShellArg d}") container.devices
++ map (n: "--network=${escapeShellArg n}") container.networks
Expand Down
98 changes: 52 additions & 46 deletions nixos/tests/oci-containers.nix
Original file line number Diff line number Diff line change
@@ -1,64 +1,70 @@
{ system ? builtins.currentSystem
, config ? {}
, pkgs ? import ../.. { inherit system config; }
, lib ? pkgs.lib
{
system ? builtins.currentSystem,
config ? { },
pkgs ? import ../.. { inherit system config; },
lib ? pkgs.lib,
}:

let

inherit (import ../lib/testing-python.nix { inherit system pkgs; }) makeTest;

mkOCITest = backend: makeTest {
name = "oci-containers-${backend}";
mkOCITest =
backend:
makeTest {
name = "oci-containers-${backend}";

meta.maintainers = lib.teams.serokell.members
++ (with lib.maintainers; [ benley ]);
meta.maintainers = lib.teams.serokell.members ++ (with lib.maintainers; [ benley ]);

nodes = {
${backend} = { pkgs, ... }: {
virtualisation.oci-containers = {
inherit backend;
containers.nginx = {
image = "nginx-container";
imageStream = pkgs.dockerTools.examples.nginxStream;
ports = ["8181:80"];
capAdd = {
CAP_AUDIT_READ = true;
nodes = {
${backend} =
{ pkgs, ... }:
{
virtualisation.oci-containers = {
inherit backend;
containers.nginx = {
image = "nginx-container";
imageStream = pkgs.dockerTools.examples.nginxStream;
ports = [ "8181:80" ];
capabilities = {
CAP_AUDIT_READ = true;
CAP_AUDIT_WRITE = false;
};
privileged = false;
devices = [
"/dev/random:/dev/random"
];
};
};
capDrop = {
CAP_AUDIT_WRITE = true;
};
privileged = false;
devices = [
"/dev/random:/dev/random"
];
};
};

# Stop systemd from killing remaining processes if ExecStop script
# doesn't work, so that proper stopping can be tested.
systemd.services."${backend}-nginx".serviceConfig.KillSignal = "SIGCONT";
# Stop systemd from killing remaining processes if ExecStop script
# doesn't work, so that proper stopping can be tested.
systemd.services."${backend}-nginx".serviceConfig.KillSignal = "SIGCONT";
};
};
};

testScript = ''
import json
testScript = ''
import json

start_all()
${backend}.wait_for_unit("${backend}-nginx.service")
${backend}.wait_for_open_port(8181)
${backend}.wait_until_succeeds("curl -f http://localhost:8181 | grep Hello")
output = json.loads(${backend}.succeed("${backend} inspect nginx --format json").strip())[0]
${backend}.succeed("systemctl stop ${backend}-nginx.service", timeout=10)
assert output['HostConfig']['CapAdd'] == ["CAP_AUDIT_READ"]
assert output['HostConfig']['CapDrop'] == ${if backend == "docker" then "[\"CAP_AUDIT_WRITE\"]" else "[]"} # Rootless podman runs with no capabilities so it cannot drop them
assert output['HostConfig']['Privileged'] == False
assert output['HostConfig']['Devices'] == [{'PathOnHost': '/dev/random', 'PathInContainer': '/dev/random', 'CgroupPermissions': '${if backend == "docker" then "rwm" else ""}'}]
'';
};
start_all()
${backend}.wait_for_unit("${backend}-nginx.service")
${backend}.wait_for_open_port(8181)
${backend}.wait_until_succeeds("curl -f http://localhost:8181 | grep Hello")
output = json.loads(${backend}.succeed("${backend} inspect nginx --format json").strip())[0]
${backend}.succeed("systemctl stop ${backend}-nginx.service", timeout=10)
assert output['HostConfig']['CapAdd'] == ["CAP_AUDIT_READ"]
assert output['HostConfig']['CapDrop'] == ${
if backend == "docker" then "[\"CAP_AUDIT_WRITE\"]" else "[]"
} # Rootless podman runs with no capabilities so it cannot drop them
assert output['HostConfig']['Privileged'] == False
assert output['HostConfig']['Devices'] == [{'PathOnHost': '/dev/random', 'PathInContainer': '/dev/random', 'CgroupPermissions': '${
if backend == "docker" then "rwm" else ""
}'}]
'';
};

in
lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; }) {} [
lib.foldl' (attrs: backend: attrs // { ${backend} = mkOCITest backend; }) { } [
"docker"
"podman"
]