summaryrefslogtreecommitdiffstats
path: root/nixos/tests/userborn-subids-immutable-etc.nix
blob: 9706271d21e802b40b188d7aca21a834a8ecfc32 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
{ lib, pkgs, ... }:

# Immutable /etc, subid files written to passwordFilesLocation and exposed
# in /etc via systemd bind mount units over build-time placeholders.

let
  common = {
    services.userborn.enable = true;
    boot.initrd.systemd.enable = true;
    networking.useNetworkd = true;
    system.etc.overlay = {
      enable = true;
      mutable = false;
    };
  };
  users = {
    # Auto allocation at runtime, must work on an immutable /etc.
    alice.isNormalUser = true;

    explicit = {
      isNormalUser = true;
      subUidRanges = [
        {
          startUid = 700000;
          count = 65536;
        }
      ];
      subGidRanges = [
        {
          startGid = 700000;
          count = 65536;
        }
      ];
    };
  };
in
{
  name = "userborn-subids-immutable-etc";

  meta.maintainers = with lib.maintainers; [ rvdp ];

  nodes.machine =
    { ... }:
    {
      imports = [ common ];

      users.users = users;

      specialisation.with-bob = {
        inheritParentConfig = false;
        configuration = {
          nixpkgs = { inherit pkgs; };
          imports = [ common ];
          users.users = users // {
            bob.isNormalUser = true;
          };
        };
      };
    };

  testScript = ''
    machine.wait_for_unit("userborn.service")

    def parse(path):
        out = {}
        for line in machine.succeed(f"cat {path}").splitlines():
            name, start, count = line.split(":")
            out.setdefault(name, []).append((int(start), int(count)))
        return out

    with subtest("/etc/subuid is a bind mount backed by passwordFilesLocation"):
        assert machine.succeed("stat -c '%F' /etc/subuid").strip() == "regular file"
        machine.succeed("mountpoint -q /etc/subuid")
        machine.succeed("systemctl is-active etc-subuid.mount etc-subgid.mount")
        a = machine.succeed("stat -c %d:%i /etc/subuid").strip()
        b = machine.succeed("stat -c %d:%i /var/lib/nixos/subuid").strip()
        assert a == b

    subuid = parse("/etc/subuid")
    subgid = parse("/etc/subgid")
    with subtest("alice got an auto range and explicit's range is present"):
        assert len(subuid["alice"]) == 1
        assert subuid["alice"][0][1] == 65536
        assert subuid["explicit"] == [(700000, 65536)]
        assert subgid["explicit"] == [(700000, 65536)]

    with subtest("newuidmap accepts the bind-mounted file"):
        machine.succeed("runuser -u alice -- unshare --user --map-auto -- true")

    before = machine.succeed("cat /etc/subuid")
    machine.succeed(
        "/run/current-system/specialisation/with-bob/bin/switch-to-configuration switch"
    )

    with subtest("bind is refreshed across activations without stacking"):
        n = machine.succeed("grep -c ' /etc/subuid ' /proc/self/mountinfo").strip()
        assert n == "1", f"expected 1 mount on /etc/subuid, got {n}"
        a = machine.succeed("stat -c %d:%i /etc/subuid").strip()
        b = machine.succeed("stat -c %d:%i /var/lib/nixos/subuid").strip()
        assert a == b

    with subtest("existing ranges survived and bob was added"):
        after = machine.succeed("cat /etc/subuid")
        assert "bob:" in after
        for line in before.splitlines():
            assert line in after

    with subtest("newuidmap still works after the switch"):
        machine.succeed("runuser -u alice -- unshare --user --map-auto -- true")
  '';
}