luks_device: add support for tpm2 and keyslot priority (#972)

* luks_device: add support for tpm2 and fido2 devices

* Update documentation per code review comments

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add support for keyslot priority

* Add changelog fragment.

* Remove fido2 support. Add idempotency for remove-tpm2

* Fix testing

* Fix testing again

* Fix formatting

* Fix format

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

* Add required_by and fix formatting

* vscode did something stupid...

* Address code review comments

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
Aram Akhavan
2025-12-22 00:15:52 -08:00
committed by GitHub
parent 663d1a1321
commit 83806cafc7
3 changed files with 477 additions and 52 deletions

View File

@@ -98,7 +98,6 @@ LUKS_CREATE_DATA: list[
("dummy", "key", None, "present", False, None, "dummy", "dummy", True),
(None, "key", None, "present", False, None, "dummy", "dummy", False),
(None, "key", None, "present", False, "labelName", "dummy", "dummy", True),
("dummy", None, None, "present", False, None, "dummy", "dummy", False),
("dummy", "key", None, "absent", False, None, "dummy", "dummy", False),
("dummy", "key", None, "opened", True, None, "dummy", "dummy", False),
("dummy", "key", None, "closed", True, None, "dummy", "dummy", False),
@@ -106,7 +105,6 @@ LUKS_CREATE_DATA: list[
("dummy", None, "foo", "present", False, None, "dummy", "dummy", True),
(None, None, "bar", "present", False, None, "dummy", "dummy", False),
(None, None, "baz", "present", False, "labelName", "dummy", "dummy", True),
("dummy", None, None, "present", False, None, "dummy", "dummy", False),
("dummy", None, "quz", "absent", False, None, "dummy", "dummy", False),
("dummy", None, "qux", "opened", True, None, "dummy", "dummy", False),
("dummy", None, "quux", "closed", True, None, "dummy", "dummy", False),
@@ -114,6 +112,11 @@ LUKS_CREATE_DATA: list[
("dummy", "key", None, "present", False, None, None, None, True),
("dummy", "key", None, "present", False, None, None, "dummy", True),
("dummy", "key", None, "present", False, None, "dummy", None, True),
("dummy", None, None, "present", False, None, "dummy", None, "exception"),
("dummy", None, None, "opened", False, None, "dummy", None, "exception"),
("dummy", None, None, "closed", False, None, "dummy", None, "exception"),
("dummy", None, None, "absent", False, None, "dummy", None, False),
("dummy", None, None, "opened", True, None, "dummy", None, False),
]
# device, state, is_luks, expected
@@ -131,34 +134,35 @@ LUKS_REMOVE_DATA: list[
("dummy", "absent", False, False),
]
# device, key, passphrase, state, name, name_by_dev, expected
# device, key, passphrase, tpm2_device, state, name, name_by_dev, expected
LUKS_OPEN_DATA: list[
tuple[
str | None,
str | None,
str | None,
str | None,
t.Literal["present", "absent", "opened", "closed"],
str | None,
str | None,
bool | t.Literal["exception"],
]
] = [
("dummy", "key", None, "present", "name", None, False),
("dummy", "key", None, "absent", "name", None, False),
("dummy", "key", None, "closed", "name", None, False),
("dummy", "key", None, "opened", "name", None, True),
(None, "key", None, "opened", "name", None, False),
("dummy", None, None, "opened", "name", None, False),
("dummy", "key", None, "opened", "name", "name", False),
("dummy", "key", None, "opened", "beer", "name", "exception"),
("dummy", None, "foo", "present", "name", None, False),
("dummy", None, "bar", "absent", "name", None, False),
("dummy", None, "baz", "closed", "name", None, False),
("dummy", None, "qux", "opened", "name", None, True),
(None, None, "quux", "opened", "name", None, False),
("dummy", None, None, "opened", "name", None, False),
("dummy", None, "quuz", "opened", "name", "name", False),
("dummy", None, "corge", "opened", "beer", "name", "exception"),
("dummy", "key", None, None, "present", "name", None, False),
("dummy", "key", None, None, "absent", "name", None, False),
("dummy", "key", None, None, "closed", "name", None, False),
("dummy", "key", None, None, "opened", "name", None, True),
(None, "key", None, None, "opened", "name", None, False),
("dummy", "key", None, None, "opened", "name", "name", False),
("dummy", "key", None, None, "opened", "beer", "name", "exception"),
("dummy", None, "foo", None, "present", "name", None, False),
("dummy", None, "bar", None, "absent", "name", None, False),
("dummy", None, "baz", None, "closed", "name", None, False),
("dummy", None, "qux", None, "opened", "name", None, True),
(None, None, "quux", None, "opened", "name", None, False),
("dummy", None, "quuz", None, "opened", "name", "name", False),
("dummy", None, "corge", None, "opened", "beer", "name", "exception"),
("dummy", None, None, None, "opened", "name", None, "exception"),
("dummy", None, None, "auto", "opened", "name", None, True),
]
# device, dev_by_name, name, name_by_dev, state, label, expected
@@ -237,6 +241,42 @@ LUKS_REMOVE_KEY_DATA: list[
("dummy", None, "foo", None, "absent", None, "exception"),
]
# device, new_tpm2, new_tpm2_pcrs, remove_tpm2, existing_tpm2, expected
SYSTEMD_CRYPTENROLL_DATA: list[
tuple[
str | None,
str | None,
str | None,
bool,
bool,
bool | t.Literal["exception"],
]
] = [
("dummy", None, None, False, False, False),
("dummy", None, None, False, True, False),
("dummy", None, None, True, False, False),
("dummy", None, None, True, True, True),
("dummy", "auto", "0+1", False, False, True),
("dummy", "auto", "0+1", True, False, True),
("dummy", "auto", None, False, False, "exception"),
]
# device, keyslot_priority, existing_priority, expected
LUKS_CONFIG_DATA: list[
tuple[str | None, str | None, int | None, bool | t.Literal["exception"]]
] = [
("dummy", "prefer", 0, True),
("dummy", "prefer", None, True),
("dummy", "prefer", 2, False),
("dummy", "normal", 0, True),
("dummy", "normal", None, False),
("dummy", "normal", 2, True),
("dummy", "ignore", 0, False),
("dummy", "ignore", None, True),
("dummy", "ignore", 2, True),
(None, "normal", 0, False),
]
@pytest.mark.parametrize(
"device, keyfile, passphrase, state, is_luks, " + "label, cipher, hash_, expected",
@@ -307,13 +347,14 @@ def test_luks_remove(
@pytest.mark.parametrize(
"device, keyfile, passphrase, state, name, name_by_dev, expected",
((d[0], d[1], d[2], d[3], d[4], d[5], d[6]) for d in LUKS_OPEN_DATA),
"device, keyfile, passphrase, tpm2_device, state, name, name_by_dev, expected",
((d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7]) for d in LUKS_OPEN_DATA),
)
def test_luks_open(
device: str | None,
keyfile: str | None,
passphrase: str | None,
tpm2_device: str | None,
state: t.Literal["present", "absent", "opened", "closed"],
name: str | None,
name_by_dev: str | None,
@@ -324,6 +365,7 @@ def test_luks_open(
module.params["device"] = device
module.params["keyfile"] = keyfile
module.params["passphrase"] = passphrase
module.params["tpm2_device"] = tpm2_device
module.params["passphrase_encoding"] = "text"
module.params["state"] = state
module.params["name"] = name
@@ -468,3 +510,86 @@ def test_luks_remove_key(
assert conditions.luks_remove_key() == expected
except ValueError:
assert expected == "exception"
@pytest.mark.parametrize(
"device, new_tpm2, new_tpm2_pcrs, remove_tpm2, existing_tpm2, expected",
((d[0], d[1], d[2], d[3], d[4], d[5]) for d in SYSTEMD_CRYPTENROLL_DATA),
)
def test_systemd_cryptenroll(
device: str | None,
new_tpm2: str | None,
new_tpm2_pcrs: str | None,
remove_tpm2: bool,
existing_tpm2: bool,
expected: bool | t.Literal["exception"],
monkeypatch: pytest.MonkeyPatch,
) -> None:
module = DummyModule()
module.params["device"] = device
module.params["passphrase_encoding"] = "text"
module.params["new_tpm2"] = new_tpm2
module.params["new_tpm2_pcrs"] = new_tpm2_pcrs
module.params["remove_tpm2"] = remove_tpm2
crypt = luks_device.CryptHandler(module) # type: ignore
def mock_luks_dump_json_metadata(
self: luks_device.CryptHandler, device: str
) -> dict[str, t.Any]:
return (
{"tokens": {"0": {"type": "systemd-tpm2"}}}
if existing_tpm2
else {"tokens": {}}
)
monkeypatch.setattr(
luks_device.CryptHandler,
"luks_dump_json_metadata",
mock_luks_dump_json_metadata,
)
try:
conditions = luks_device.ConditionsHandler(module, crypt) # type: ignore
assert conditions.systemd_cryptenroll() == expected
except ValueError:
assert expected == "exception"
@pytest.mark.parametrize(
"device, keyslot_priority, existing_priority, expected",
((d[0], d[1], d[2], d[3]) for d in LUKS_CONFIG_DATA),
)
def test_luks_config(
device: str | None,
keyslot_priority: str | None,
existing_priority: int | None,
expected: bool | t.Literal["exception"],
monkeypatch: pytest.MonkeyPatch,
) -> None:
module = DummyModule()
module.params["device"] = device
module.params["passphrase_encoding"] = "text"
module.params["keyslot"] = 1
module.params["keyslot_priority"] = keyslot_priority
crypt = luks_device.CryptHandler(module) # type: ignore
def mock_luks_dump_json_metadata(
self: luks_device.CryptHandler, device: str
) -> dict[str, t.Any]:
return {"keyslots": {"1": {"priority": existing_priority}}}
monkeypatch.setattr(
luks_device.CryptHandler,
"luks_dump_json_metadata",
mock_luks_dump_json_metadata,
)
try:
conditions = luks_device.ConditionsHandler(module, crypt) # type: ignore
assert conditions.luks_config() == expected
except ValueError:
assert expected == "exception"