lxc: added option for unprivileged containers.

Added extra option to enable unprivileged containers. This includes a
patch to remove the hard-coded path to `lxc-user-nic` and a new security
wrapper to set SUID to `lxc-user-nic`.

Changed files
+205 -3
nixos
modules
virtualisation
tests
pkgs
by-name
+38
nixos/modules/virtualisation/lxc.nix
···
'';
};
+
unprivilegedContainers = lib.mkEnableOption "support for unprivileged users to launch containers";
+
systemConfig =
lib.mkOption {
type = lib.types.lines;
···
administration access in LXC. See {manpage}`lxc-usernet(5)`.
'';
};
+
+
bridgeConfig =
+
lib.mkOption {
+
type = lib.types.lines;
+
default = "";
+
description = ''
+
This is the config file for override lxc-net bridge default settings.
+
'';
+
};
};
###### implementation
···
environment.etc."lxc/lxc.conf".text = cfg.systemConfig;
environment.etc."lxc/lxc-usernet".text = cfg.usernetConfig;
environment.etc."lxc/default.conf".text = cfg.defaultConfig;
+
environment.etc."lxc/lxc-net".text = cfg.bridgeConfig;
+
environment.pathsToLink = [ "/share/lxc" ];
systemd.tmpfiles.rules = [ "d /var/lib/lxc/rootfs 0755 root root -" ];
security.apparmor.packages = [ cfg.package ];
···
"lxc-containers".profile = ''
include ${cfg.package}/etc/apparmor.d/lxc-containers
'';
+
};
+
+
# We don't need the `lxc-user` group, unless the unprivileged containers are enabled.
+
users.groups = lib.mkIf cfg.unprivilegedContainers { lxc-user = {}; };
+
+
# `lxc-user-nic` needs suid to attach to bridge for unpriv containers.
+
security.wrappers = lib.mkIf cfg.unprivilegedContainers {
+
lxcUserNet = {
+
source = "${pkgs.lxc}/libexec/lxc/lxc-user-nic";
+
setuid = true;
+
owner = "root";
+
group = "lxc-user";
+
program = "lxc-user-nic";
+
permissions = "u+rx,g+x,o-rx";
+
};
+
};
+
+
# Add lxc-net service if unpriv mode is enabled.
+
systemd.packages = lib.mkIf cfg.unprivilegedContainers [ pkgs.lxc ];
+
systemd.services = lib.mkIf cfg.unprivilegedContainers {
+
lxc-net = {
+
enable = true;
+
wantedBy = [ "multi-user.target" ];
+
path = [ pkgs.iproute2 pkgs.iptables pkgs.getent pkgs.dnsmasq ];
+
};
};
};
}
+1
nixos/tests/all-tests.nix
···
loki = handleTest ./loki.nix {};
luks = handleTest ./luks.nix {};
lvm2 = handleTest ./lvm2 {};
+
lxc = handleTest ./lxc {};
lxd = pkgs.recurseIntoAttrs (handleTest ./lxd { inherit handleTestOn; });
lxd-image-server = handleTest ./lxd-image-server.nix {};
#logstash = handleTest ./logstash.nix {};
+6 -2
nixos/tests/incus/incusd-options.nix
···
};
};
-
container-image-metadata = "${releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}}/tarball/nixos-system-${pkgs.stdenv.hostPlatform.system}.tar.xz";
-
container-image-rootfs = "${releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system}}/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs";
+
container-image-metadata = "${
+
releases.incusContainerMeta.${pkgs.stdenv.hostPlatform.system}
+
}/tarball/nixos-system-${pkgs.stdenv.hostPlatform.system}.tar.xz";
+
container-image-rootfs = "${
+
releases.incusContainerImage.${pkgs.stdenv.hostPlatform.system}
+
}/nixos-lxc-image-${pkgs.stdenv.hostPlatform.system}.squashfs";
in
{
name = "incusd-options";
+124
nixos/tests/lxc/default.nix
···
+
import ../make-test-python.nix (
+
{ pkgs, lib, ... }:
+
+
let
+
releases = import ../../release.nix {
+
configuration = {
+
# Building documentation makes the test unnecessarily take a longer time:
+
documentation.enable = lib.mkForce false;
+
};
+
};
+
+
lxc-image-metadata = releases.lxdContainerMeta.${pkgs.stdenv.hostPlatform.system};
+
lxc-image-rootfs = releases.lxdContainerImage.${pkgs.stdenv.hostPlatform.system};
+
+
in
+
{
+
name = "lxc-container-unprivileged";
+
+
meta = {
+
maintainers = lib.teams.lxc.members;
+
};
+
+
nodes.machine =
+
{ lib, pkgs, ... }:
+
{
+
virtualisation = {
+
diskSize = 6144;
+
cores = 2;
+
memorySize = 512;
+
writableStore = true;
+
+
lxc = {
+
enable = true;
+
unprivilegedContainers = true;
+
systemConfig = ''
+
lxc.lxcpath = /tmp/lxc
+
'';
+
defaultConfig = ''
+
lxc.net.0.type = veth
+
lxc.net.0.link = lxcbr0
+
lxc.net.0.flags = up
+
lxc.net.0.hwaddr = 00:16:3e:xx:xx:xx
+
lxc.idmap = u 0 100000 65536
+
lxc.idmap = g 0 100000 65536
+
'';
+
# Permit user alice to connect to bridge
+
usernetConfig = ''
+
@lxc-user veth lxcbr0 10
+
'';
+
bridgeConfig = ''
+
LXC_IPV6_ADDR=""
+
LXC_IPV6_MASK=""
+
LXC_IPV6_NETWORK=""
+
LXC_IPV6_NAT="false"
+
'';
+
};
+
};
+
+
# Needed for lxc
+
environment.systemPackages = with pkgs; [
+
pkgs.wget
+
pkgs.dnsmasq
+
];
+
+
# Create user for test
+
users.users.alice = {
+
isNormalUser = true;
+
password = "test";
+
description = "Lxc unprivileged user with access to lxcbr0";
+
extraGroups = [ "lxc-user" ];
+
subGidRanges = [
+
{
+
startGid = 100000;
+
count = 65536;
+
}
+
];
+
subUidRanges = [
+
{
+
startUid = 100000;
+
count = 65536;
+
}
+
];
+
};
+
+
users.users.bob = {
+
isNormalUser = true;
+
password = "test";
+
description = "Lxc unprivileged user without access to lxcbr0";
+
subGidRanges = [
+
{
+
startGid = 100000;
+
count = 65536;
+
}
+
];
+
subUidRanges = [
+
{
+
startUid = 100000;
+
count = 65536;
+
}
+
];
+
};
+
};
+
+
testScript = ''
+
machine.wait_for_unit("lxc-net.service")
+
+
# Copy config files for alice
+
machine.execute("su -- alice -c 'mkdir -p ~/.config/lxc'")
+
machine.execute("su -- alice -c 'cp /etc/lxc/default.conf ~/.config/lxc/'")
+
machine.execute("su -- alice -c 'cp /etc/lxc/lxc.conf ~/.config/lxc/'")
+
+
machine.succeed("su -- alice -c 'lxc-create -t local -n test -- --metadata ${lxc-image-metadata}/*/*.tar.xz --fstree ${lxc-image-rootfs}/*/*.tar.xz'")
+
machine.succeed("su -- alice -c 'lxc-start test'")
+
machine.succeed("su -- alice -c 'lxc-stop test'")
+
+
# Copy config files for bob
+
machine.execute("su -- bob -c 'mkdir -p ~/.config/lxc'")
+
machine.execute("su -- bob -c 'cp /etc/lxc/default.conf ~/.config/lxc/'")
+
machine.execute("su -- bob -c 'cp /etc/lxc/lxc.conf ~/.config/lxc/'")
+
+
machine.fail("su -- bob -c 'lxc-start test'")
+
'';
+
}
+
)
+23 -1
pkgs/by-name/lx/lxc/package.nix
···
patches = [
# fix docbook2man version detection
./docbook-hack.patch
+
+
# Fix hardcoded path of lxc-user-nic
+
# This is needed to use unprivileged containers
+
./user-nic.diff
];
mesonFlags = [
-
"-Dinstall-init-files=false"
+
"-Dinstall-init-files=true"
"-Dinstall-state-dirs=false"
"-Dspecfile=false"
"-Dtools-multicall=true"
"-Dtools=false"
+
"-Dusernet-config-path=/etc/lxc/lxc-usernet"
+
"-Ddistrosysconfdir=${placeholder "out"}/etc/lxc"
+
"-Dsystemd-unitdir=${placeholder "out"}/lib/systemd/system"
];
+
# /run/current-system/sw/share
+
postInstall = ''
+
substituteInPlace $out/etc/lxc/lxc --replace-fail "$out/etc/lxc" "/etc/lxc"
+
substituteInPlace $out/libexec/lxc/lxc-net --replace-fail "$out/etc/lxc" "/etc/lxc"
+
+
substituteInPlace $out/share/lxc/templates/lxc-download --replace-fail "$out/share" "/run/current-system/sw/share"
+
substituteInPlace $out/share/lxc/templates/lxc-local --replace-fail "$out/share" "/run/current-system/sw/share"
+
substituteInPlace $out/share/lxc/templates/lxc-oci --replace-fail "$out/share" "/run/current-system/sw/share"
+
+
substituteInPlace $out/share/lxc/config/common.conf --replace-fail "$out/share" "/run/current-system/sw/share"
+
substituteInPlace $out/share/lxc/config/userns.conf --replace-fail "$out/share" "/run/current-system/sw/share"
+
substituteInPlace $out/share/lxc/config/oci.common.conf --replace-fail "$out/share" "/run/current-system/sw/share"
+
'';
+
enableParallelBuilding = true;
doCheck = true;
···
tests = {
incus-legacy-init = nixosTests.incus.container-legacy-init;
incus-systemd-init = nixosTests.incus.container-systemd-init;
+
lxc = nixosTests.lxc;
lxd = nixosTests.lxd.container;
};
+13
pkgs/by-name/lx/lxc/user-nic.diff
···
+
diff --git a/src/lxc/network.c b/src/lxc/network.c
+
index 0a99d32..850e975 100644
+
--- a/src/lxc/network.c
+
+++ b/src/lxc/network.c
+
@@ -2940,7 +2940,7 @@ int lxc_find_gateway_addresses(struct lxc_handler *handler)
+
+
#ifdef IN_LIBLXC
+
+
-#define LXC_USERNIC_PATH LIBEXECDIR "/lxc/lxc-user-nic"
+
+#define LXC_USERNIC_PATH "/run/wrappers/bin/lxc-user-nic"
+
static int lxc_create_network_unpriv_exec(const char *lxcpath,
+
const char *lxcname,
+
struct lxc_netdev *netdev, pid_t pid,