summary refs log tree commit diff
path: root/nixos/tests/systemd-credentials-tpm2.nix
blob: d2dc1fd7b615bc684b6ed0e6d73d44d4897f0bb6 (plain) (blame)
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
112
113
114
115
116
117
118
119
120
121
122
123
124
import ./make-test-python.nix ({ lib, pkgs, system, ... }:

let
  tpmSocketPath = "/tmp/swtpm-sock";
  tpmDeviceModels = {
    x86_64-linux = "tpm-tis";
    aarch64-linux = "tpm-tis-device";
  };
in

{
  name = "systemd-credentials-tpm2";

  meta = {
    maintainers = with pkgs.lib.maintainers; [ tmarkus ];
  };

  nodes.machine = { pkgs, ... }: {
    virtualisation = {
      qemu.options = [
        "-chardev socket,id=chrtpm,path=${tpmSocketPath}"
        "-tpmdev emulator,id=tpm_dev_0,chardev=chrtpm"
        "-device ${tpmDeviceModels.${system}},tpmdev=tpm_dev_0"
      ];
    };

    boot.initrd.availableKernelModules = [ "tpm_tis" ];

    environment.systemPackages = with pkgs; [ diffutils ];
  };

  testScript = ''
    import subprocess
    from tempfile import TemporaryDirectory

    # From systemd-initrd-luks-tpm2.nix
    class Tpm:
        def __init__(self):
            self.state_dir = TemporaryDirectory()
            self.start()

        def start(self):
            self.proc = subprocess.Popen(["${pkgs.swtpm}/bin/swtpm",
                "socket",
                "--tpmstate", f"dir={self.state_dir.name}",
                "--ctrl", "type=unixio,path=${tpmSocketPath}",
                "--tpm2",
                ])

            # Check whether starting swtpm failed
            try:
                exit_code = self.proc.wait(timeout=0.2)
                if exit_code is not None and exit_code != 0:
                    raise Exception("failed to start swtpm")
            except subprocess.TimeoutExpired:
                pass

        """Check whether the swtpm process exited due to an error"""
        def check(self):
            exit_code = self.proc.poll()
            if exit_code is not None and exit_code != 0:
                raise Exception("swtpm process died")

    CRED_NAME = "testkey"
    CRED_RAW_FILE = f"/root/{CRED_NAME}"
    CRED_FILE = f"/root/{CRED_NAME}.cred"

    def systemd_run(machine, cmd):
        machine.log(f"Executing command (via systemd-run): \"{cmd}\"")

        (status, out) = machine.execute( " ".join([
            "systemd-run",
            "--service-type=exec",
            "--quiet",
            "--wait",
            "-E PATH=\"$PATH\"",
            "-p StandardOutput=journal",
            "-p StandardError=journal",
            f"-p LoadCredentialEncrypted={CRED_NAME}:{CRED_FILE}",
            f"$SHELL -c '{cmd}'"
            ]) )

        if status != 0:
            raise Exception(f"systemd_run failed (status {status})")

        machine.log("systemd-run finished successfully")

    tpm = Tpm()

    @polling_condition
    def swtpm_running():
        tpm.check()

    machine.wait_for_unit("multi-user.target")

    with subtest("Check whether TPM device exists"):
        machine.succeed("test -e /dev/tpm0")
        machine.succeed("test -e /dev/tpmrm0")

    with subtest("Check whether systemd-creds detects TPM2 correctly"):
        cmd = "systemd-creds has-tpm2"
        machine.log(f"Running \"{cmd}\"")
        (status, _) = machine.execute(cmd)

        # Check exit code equals 0 or 1 (1 means firmware support is missing, which is OK here)
        if status != 0 and status != 1:
            raise Exception("systemd-creds failed to detect TPM2")

    with subtest("Encrypt credential using systemd-creds"):
        machine.succeed(f"dd if=/dev/urandom of={CRED_RAW_FILE} bs=1k count=16")
        machine.succeed(f"systemd-creds --with-key=host+tpm2 encrypt --name=testkey {CRED_RAW_FILE} {CRED_FILE}")

    with subtest("Write provided credential and check for equality"):
        CRED_OUT_FILE = f"/root/{CRED_NAME}.out"
        systemd_run(machine, f"systemd-creds cat testkey > {CRED_OUT_FILE}")
        machine.succeed(f"cmp --silent -- {CRED_RAW_FILE} {CRED_OUT_FILE}")

    with subtest("Check whether systemd service can see credential in systemd-creds list"):
        systemd_run(machine, f"systemd-creds list | grep {CRED_NAME}")

    with subtest("Check whether systemd service can access credential in $CREDENTIALS_DIRECTORY"):
        systemd_run(machine, f"cmp --silent -- $CREDENTIALS_DIRECTORY/{CRED_NAME} {CRED_RAW_FILE}")
  '';
})