Unverified Commit 389de87a authored by Matteo Sozzi's avatar Matteo Sozzi Committed by Adam Stephens
Browse files

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`.
parent 056b6a1f
Loading
Loading
Loading
Loading
+38 −0
Original line number Diff line number Diff line
@@ -23,6 +23,8 @@ in
          '';
      };

    unprivilegedContainers = lib.mkEnableOption "support for unprivileged users to launch containers";

    systemConfig =
      lib.mkOption {
        type = lib.types.lines;
@@ -53,6 +55,15 @@ in
            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
@@ -62,6 +73,8 @@ in
    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 ];
@@ -73,5 +86,30 @@ in
        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 −0
Original line number Diff line number Diff line
@@ -538,6 +538,7 @@ in {
  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
Original line number Diff line number Diff line
@@ -16,8 +16,12 @@ import ../make-test-python.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 −0
Original line number Diff line number Diff line
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
Original line number Diff line number Diff line
@@ -48,16 +48,37 @@ stdenv.mkDerivation (finalAttrs: {
  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;
@@ -66,6 +87,7 @@ stdenv.mkDerivation (finalAttrs: {
    tests = {
      incus-legacy-init = nixosTests.incus.container-legacy-init;
      incus-systemd-init = nixosTests.incus.container-systemd-init;
      lxc = nixosTests.lxc;
      lxd = nixosTests.lxd.container;
    };

Loading