Unverified Commit 7ea9ba60 authored by Adam C. Stephens's avatar Adam C. Stephens Committed by GitHub
Browse files

nixos/incus: add incus-user service and socket (#355645)

parents d7467c7b 0214dd4e
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -872,6 +872,8 @@

- `qgis` and `qgis-ltr` are now built without `grass` by default. `grass` support can be enabled with `qgis.override { withGrass = true; }`.

- `virtualisation.incus` module gained new `incus-user.service` and `incus-user.socket` systemd units. It is now possible to add a user to `incus` group instead of `incus-admin` for increased security.

## Detailed Migration Information {#sec-release-24.11-migration}

### `sound` options removal {#sec-release-24.11-migration-sound}
+37 −1
Original line number Diff line number Diff line
@@ -153,7 +153,10 @@ in

        Users in the "incus-admin" group can interact with
        the daemon (e.g. to start or stop containers) using the
        {command}`incus` command line tool, among others
        {command}`incus` command line tool, among others.
        Users in the "incus" group can also interact with
        the daemon, but with lower permissions
        (i.e. administrative operations are forbidden).
      '';

      package = lib.mkPackageOption pkgs "incus-lts" { };
@@ -359,6 +362,27 @@ in
      };
    };

    systemd.services.incus-user = {
      description = "Incus Container and Virtual Machine Management User Daemon";

      inherit environment;

      after = [
        "incus.service"
        "incus-user.socket"
      ];

      requires = [
        "incus-user.socket"
      ];

      serviceConfig = {
        ExecStart = "${cfg.package}/bin/incus-user --group incus";

        Restart = "on-failure";
      };
    };

    systemd.services.incus-startup = lib.mkIf cfg.softDaemonRestart {
      description = "Incus Instances Startup/Shutdown";

@@ -391,6 +415,17 @@ in
      };
    };

    systemd.sockets.incus-user = {
      description = "Incus user UNIX socket";
      wantedBy = [ "sockets.target" ];

      socketConfig = {
        ListenStream = "/var/lib/incus/unix.socket.user";
        SocketMode = "0660";
        SocketGroup = "incus";
      };
    };

    systemd.services.incus-preseed = lib.mkIf (cfg.preseed != null) {
      description = "Incus initialization with preseed file";

@@ -409,6 +444,7 @@ in
      };
    };

    users.groups.incus = { };
    users.groups.incus-admin = { };

    users.users.root = {
+58 −32
Original line number Diff line number Diff line
@@ -44,7 +44,7 @@ import ../make-test-python.nix (
          preseed = {
            networks = [
              {
                name = "nixostestbr0";
                name = "incusbr0";
                type = "bridge";
                config = {
                  "ipv4.address" = "10.0.100.1/24";
@@ -58,12 +58,12 @@ import ../make-test-python.nix (
                devices = {
                  eth0 = {
                    name = "eth0";
                    network = "nixostestbr0";
                    network = "incusbr0";
                    type = "nic";
                  };
                  root = {
                    path = "/";
                    pool = "nixostest_pool";
                    pool = "default";
                    size = "35GiB";
                    type = "disk";
                  };
@@ -72,21 +72,35 @@ import ../make-test-python.nix (
            ];
            storage_pools = [
              {
                name = "nixostest_pool";
                name = "default";
                driver = "dir";
              }
            ];
          };
        };

      };

      networking.nftables.enable = true;

      users.users.testuser = {
        isNormalUser = true;
        shell = pkgs.bashInteractive;
        group = "incus";
        uid = 1000;
      };
    };

    testScript = ''
    testScript = # python
      ''
        def wait_for_instance(name: str, project: str = "default"):
            def instance_is_up(_) -> bool:
          status, _ = machine.execute("incus exec container --disable-stdin --force-interactive /run/current-system/sw/bin/systemctl -- is-system-running")
                status, _ = machine.execute(f"incus exec {name} --disable-stdin --force-interactive --project {project} -- /run/current-system/sw/bin/systemctl is-system-running")
                return status == 0

            with machine.nested(f"Waiting for instance {name} to start and be usable"):
              retry(instance_is_up)

        machine.wait_for_unit("incus.service")
        machine.wait_for_unit("incus-preseed.service")

@@ -94,21 +108,33 @@ import ../make-test-python.nix (
            machine.succeed("incus image import ${container-image-metadata} ${container-image-rootfs} --alias nixos")

        with subtest("Container can be launched and managed"):
          machine.succeed("incus launch nixos container")
          with machine.nested("Waiting for instance to start and be usable"):
            retry(instance_is_up)
          machine.succeed("echo true | incus exec container /run/current-system/sw/bin/bash -")
            machine.succeed("incus launch nixos instance1")
            wait_for_instance("instance1")
            machine.succeed("echo true | incus exec instance1 /run/current-system/sw/bin/bash -")

        with subtest("Verify preseed resources created"):
            machine.succeed("incus profile show default")
          machine.succeed("incus network info nixostestbr0")
          machine.succeed("incus storage show nixostest_pool")
            machine.succeed("incus network info incusbr0")
            machine.succeed("incus storage show default")

        with subtest("Instance is stopped when softDaemonRestart is disabled and services is stopped"):
          pid = machine.succeed("incus info container | grep 'PID'").split(":")[1].strip()
            pid = machine.succeed("incus info instance1 | grep 'PID'").split(":")[1].strip()
            machine.succeed(f"ps {pid}")
            machine.succeed("systemctl stop incus")
            machine.fail(f"ps {pid}")

        with subtest("incus-user allows restricted access for users"):
            machine.fail("incus project show user-1000")
            machine.succeed("su - testuser bash -c 'incus list'")
            # a project is created dynamically for the user
            machine.succeed("incus project show user-1000")
            # users shouldn't be able to list storage pools
            machine.fail("su - testuser bash -c 'incus storage list'")

        with subtest("incus-user allows users to launch instances"):
            machine.succeed("su - testuser bash -c 'incus image import ${container-image-metadata} ${container-image-rootfs} --alias nixos'")
            machine.succeed("su - testuser bash -c 'incus launch nixos instance2'")
            wait_for_instance("instance2", "user-1000")
      '';
  }
)