From 50154736c04b3baef1ff67716b66a7a96a9d137f Mon Sep 17 00:00:00 2001 From: Sergio Correia Date: Fri, 4 Apr 2025 14:07:03 +0100 Subject: [PATCH 1/4] fix: idempotence issue when binding fails to be added Sometimes, the role will not be able to add the required bindings, in which case it is expected to rollback and undo any change it has done. In certain cases, the rollback was not performed correctly, and caused idempotence issues. We fix that by performing a backup of the LUKS header before doing the operations, so that we can properly restore it in the case the operation cannot be completed successfully. Signed-off-by: Sergio Correia --- library/nbde_client_clevis.py | 310 ++++++++++++------ tests/tasks/bind_repeatedly_single_device.yml | 18 + tests/tasks/bind_slot_with_passphrase.yml | 72 ++++ tests/tasks/cleanup_test.yml | 3 + tests/tasks/setup_test.yml | 14 +- tests/tests_failed_bind.yml | 33 ++ tests/vars/main.yml | 1 + vars/Debian.yml | 1 + vars/Fedora.yml | 1 + vars/RedHat_10.yml | 1 + vars/RedHat_7.yml | 1 + vars/RedHat_8.yml | 1 + vars/RedHat_9.yml | 1 + 13 files changed, 364 insertions(+), 93 deletions(-) create mode 100644 tests/tasks/bind_repeatedly_single_device.yml create mode 100644 tests/tasks/bind_slot_with_passphrase.yml create mode 100644 tests/tests_failed_bind.yml diff --git a/library/nbde_client_clevis.py b/library/nbde_client_clevis.py index 92cc6c57..02b0695a 100644 --- a/library/nbde_client_clevis.py +++ b/library/nbde_client_clevis.py @@ -155,7 +155,7 @@ def initialize_device(module, luks_type, device): return None -def get_luks_type(module, device, initialize=True): +def get_luks_type(module, device): """Get the LUKS type of the device. Return: """ @@ -169,10 +169,7 @@ def get_luks_type(module, device, initialize=True): args = ["cryptsetup", "isLuks", "--type", luks, device] ret_code, _unused1, _unused2 = module.run_command(args) if ret_code == 0: - err = None - if initialize: - err = initialize_device(module, luks, device) - return luks, err + return luks, None # We did not identify device as either LUKS1 or LUKS2. return None, {"msg": "Not possible to detect whether LUKS1 or LUKS2"} @@ -254,11 +251,11 @@ def get_jwe_luks2(module, device, slot): return jwe, token_id, None -def get_jwe(module, device, slot, initialize=True): +def get_jwe(module, device, slot): """Get a clevis JWE from a given device and slot. Return: """ - luks, err = get_luks_type(module, device, initialize) + luks, err = get_luks_type(module, device) if err: return None, err @@ -440,7 +437,7 @@ def keyslots_in_use(module, device): else: slots, err = parse_keyslots_luks2(luks_dump) - if err: + if err or not slots: return None, err return sorted(slots), None @@ -450,7 +447,7 @@ def bound_slots(module, device): Return: """ slots, err = keyslots_in_use(module, device) - if err: + if err or not slots: return None, err # Now let's iterate through these slots and collect the bound ones. @@ -556,7 +553,7 @@ def retrieve_passphrase(module, device): Return: """ slots, err = bound_slots(module, device) - if err: + if err or not slots: return None, None, err for slot in slots: @@ -588,12 +585,27 @@ def save_slot_luks1(module, **kwargs): if len(kwargs["data"]) == 0: return False, {"msg": "We need data to save to a slot"} - bound, _unused = is_slot_bound(module, kwargs["device"], kwargs["slot"]) + # CVE-2025-11568: Validate metadata size to prevent overflow in LUKS1 gap. + # LUKS1 has limited gap space (~512KB shared across 8 slots), so we enforce + # a 64KB per-slot limit to prevent corruption of encrypted data. + MAX_LUKSMETA_SIZE = 65536 # 64KB limit + data_size = len(kwargs["data"]) + + if data_size > MAX_LUKSMETA_SIZE: + errmsg = ( + "Metadata size ({0} bytes) exceeds maximum allowed size " + "({1} bytes) for LUKS1 device {2}".format( + data_size, MAX_LUKSMETA_SIZE, kwargs["device"] + ) + ) + return False, {"msg": errmsg} - backup, err = backup_luks1_device(module, kwargs["device"]) + # Make sure device is initialized. + err = initialize_device(module, "luks1", kwargs["device"]) if err: return False, err + bound, _unused = is_slot_bound(module, kwargs["device"], kwargs["slot"]) if bound: if not kwargs["overwrite"]: errmsg = "{0}:{1} is already bound and no overwrite set".format( @@ -629,14 +641,11 @@ def save_slot_luks1(module, **kwargs): args, data=kwargs["data"], binary_data=True ) if ret_code != 0: - if bound: - restore_luks1_device(module, kwargs["device"], backup) return False, {"msg": stderr} # Now make sure we can read the data properly. new_data, err = get_jwe_luks1(module, kwargs["device"], kwargs["slot"]) if err or new_data != kwargs["data"]: - restore_luks1_device(module, kwargs["device"], backup) errmsg = "Error adding JWE to {0}:{1} ; no changes performed".format( kwargs["device"], kwargs["slot"] ) @@ -645,62 +654,193 @@ def save_slot_luks1(module, **kwargs): return True, None +def get_luks1_payload_offset(module, device): + """Get payload offset from cryptsetup luksDump for LUKS1 device. + Return: """ + + ret_code, stdout, stderr = module.run_command(["cryptsetup", "luksDump", device]) + if ret_code != 0: + return None, {"msg": "Failed to dump LUKS info: {}".format(stderr)} + + # Look for "Payload offset:" line + for line in stdout.split("\n"): + if "Payload offset:" in line: + # Extract number from "Payload offset: 4096 [sectors]" + match = re.search(r"Payload offset:\s+(\d+)", line) + if match: + return int(match.group(1)), None + + return None, {"msg": "Could not find payload offset in LUKS dump"} + + +def backup_luks1_gap_area(module, device, payload_offset_sectors): + """Backup everything from start of device until encrypted data starts. + This includes the full LUKS1 header plus any gap area with LUKSmeta data. + Return: """ + + # Backup from sector 0 to payload_offset_sectors (everything before encrypted data) + args = [ + "dd", + "if={}".format(device), + "count={}".format(payload_offset_sectors), + "bs=512", + "status=none", + ] + + ret_code, header_and_gap_data, stderr = module.run_command(args, binary_data=True) + if ret_code != 0: + return None, {"msg": "Failed to backup header and gap area: {}".format(stderr)} + + return header_and_gap_data, None + + +def restore_luks1_gap_area(module, device, payload_offset_sectors, header_and_gap_data): + """Restore everything from start of device until encrypted data starts. + This restores the full LUKS1 header plus any gap area with LUKSmeta data. + Return: """ + + if not header_and_gap_data: + return {"msg": "No header and gap data to restore"} + + # Restore from sector 0 to payload_offset_sectors (everything before encrypted data) + args = [ + "dd", + "of={}".format(device), + "count={}".format(payload_offset_sectors), + "bs=512", + "conv=notrunc", + "status=none", + ] + + ret_code, _unused, stderr = module.run_command( + args, data=header_and_gap_data, binary_data=True + ) + if ret_code != 0: + return {"msg": "Failed to restore header and gap area: {}".format(stderr)} + + return None + + def backup_luks1_device(module, device): - """Backup LUKSmeta metadata from LUKS1 device, as it can be corrupted when - saving new metadata. + """Backup LUKS1 header and gap area using dd for byte-perfect restoration. + This preserves the exact state of the LUKSmeta area, including whether + it was initialized or not. Return: """ - bound, err = bound_slots(module, device) + # Get payload offset to determine how much to backup + payload_offset, err = get_luks1_payload_offset(module, device) if err: return None, err - backup = {} - for slot in bound: - args = ["luksmeta", "load", "-d", device, "-s", str(slot), "-u", CLEVIS_UUID] - ret_code, data, stderr = module.run_command(args) - if ret_code != 0: - return None, {"msg": stderr} - backup[slot] = data + # Backup everything from start of device until encrypted data starts + header_and_gap_data, err = backup_luks1_gap_area(module, device, payload_offset) + if err: + return None, err + + backup = {"payload_offset": payload_offset, "gap_data": header_and_gap_data} + return backup, None -def restore_luks1_device(module, device, backup): - """Restore LUKSmeta metadata from the specified backup. +def restore_luks1_device(module, device, gap_backup): + """Restore LUKS1 header and gap area using dd for byte-perfect restoration. + This restores the exact state of the LUKSmeta area as it was before. Return: """ - args = ["luksmeta", "init", "-f", "-d", device] - ret_code, _unused, stderr = module.run_command(args) - if ret_code != 0: - return {"msg": stderr} + if ( + not gap_backup + or "payload_offset" not in gap_backup + or "gap_data" not in gap_backup + ): + return {"msg": "Invalid gap backup data"} - for slot in backup: - _unused, err = save_slot_luks1( - module, device=device, slot=slot, data=backup[slot], overwrite=True - ) - if err: - return err + payload_offset = gap_backup["payload_offset"] + gap_data = gap_backup["gap_data"] - return None + # Restore everything from start of device until encrypted data starts + result = restore_luks1_gap_area(module, device, payload_offset, gap_data) + + return result -def backup_luks2_token(module, device, token_id): - """Backup LUKS2 token, as we may need to restore the metadata. +def backup_luks_device(module, device): + """Backup LUKS device header, as we may need to restore it upon + a failed binding procedure. Return: """ - args = ["cryptsetup", "token", "export", "--token-id", token_id, device] - ret, token, err = module.run_command(args) + # If LUKS1, we will backup everything from the beginning of the device + # until the encrypted data. That will cover the LUKS1 header + the gap + # that contains the LUKSmeta data. for LUKS2, merely saving the header + # also saves the associated tokens. + luks, err = get_luks_type(module, device) + if err: + return None, err + + if luks == "luks1": + luks1_backup, err = backup_luks1_device(module, device) + if err: + return None, err + return luks1_backup, None + + # For LUKS2, cryptsetup luksHeaderBackup/luksHeaderRestore is enough. + header_backup = os.path.join(module.params["data_dir"], "header") + args = [ + "cryptsetup", + "luksHeaderBackup", + device, + "--batch-mode", + "--header-backup-file", + header_backup, + ] + ret, _unused, stderr = module.run_command(args) if ret != 0: - return None, {"msg": "Error during token backup: {0}".format(err)} + return None, {"msg": stderr} + return header_backup, None - try: - token_json = json.loads(token) - except ValueError as exc: - return None, {"msg": str(exc)} - return token_json, None + +def restore_luks_device(module, device, backup): + """Restore the LUKS device, the header and, if applicable + the LUKSMeta metadata. + Return: """ + + # Let's make sure the header backup exists. + if not backup: + return {"msg": "Error during rollback: invalid backup data"} + + # If LUKS1, we need to manually restore the backup with the header + # plus the LUKSMeta data. + luks, err = get_luks_type(module, device) + if err: + return err + + if luks == "luks1": + err = restore_luks1_device(module, device, backup) + if err: + return err + return None + + # For LUKS2, we use cryptsetup luksHeaderRestore, and backup is the + # header to be restored. + if not os.path.isfile(backup): + return {"msg": "Error during rollback: invalid LUKS header backup"} + + # Now we can restore the header. + args = [ + "cryptsetup", + "luksHeaderRestore", + device, + "--batch-mode", + "--header-backup-file", + backup, + ] + ret, _unused, stderr = module.run_command(args) + if ret != 0: + return {"msg": "Error during rollback: {}".format(stderr)} + return None def import_luks2_token(module, device, token): - """Restore LUKS2 token. + """Store a given LUKS2 token to a device. Return: """ if not token or len(token) == 0: @@ -726,7 +866,7 @@ def make_luks2_token(slot, data): try: metadata = {"type": "clevis", "keyslots": [str(slot)], "jwe": json.loads(data)} except ValueError as exc: - return False, {"msg": "Error making new token: {0}".format(str(exc))} + return None, {"msg": "Error making new token: {0}".format(str(exc))} return metadata, None @@ -765,10 +905,6 @@ def save_slot_luks2(module, **kwargs): ) return False, {"msg": errmsg} - old_data, err = backup_luks2_token(module, kwargs["device"], token_id) - if err: - return False, err - args = [ "cryptsetup", "token", @@ -783,16 +919,14 @@ def save_slot_luks2(module, **kwargs): jwe, err = format_jwe(module, kwargs["data"], False) if err: - import_luks2_token(module, kwargs["device"], old_data) return False, {"msg": "Error preparing JWE: {0}".format(err["msg"])} token, err = make_luks2_token(kwargs["slot"], jwe) - if err: + if err or not token: return False, err err = import_luks2_token(module, kwargs["device"], token) if err: - import_luks2_token(module, kwargs["device"], old_data) return False, err # Now the test to see if we stored the correct data. @@ -813,7 +947,6 @@ def save_slot_luks2(module, **kwargs): ] module.run_command(args) - import_luks2_token(module, kwargs["device"], old_data) errmsg = "Error storing token: {0} / {1}".format(kwargs["data"], metadata) return False, {"msg": errmsg} @@ -843,7 +976,7 @@ def is_keyslot_in_use(module, device, slot): Return: """ slots, err = keyslots_in_use(module, device) - if err: + if err or not slots: return False return str(slot) in slots @@ -1098,18 +1231,15 @@ def discard_passphrase(module, **kwargs): def prepare_to_rebind(module, device, slot): - """Backups metadata from device and also remove it, in preparation for a - rebind operation. - Return """ + """Prepares the device + slot for a rebinding. The device is already + backup'ed and can be restored if things go wrong. + Return """ luks_type, err = get_luks_type(module, device) if err: - return None, err + return err if luks_type == "luks1": - backup, err = backup_luks1_device(module, device) - if err: - return None, err args = [ "luksmeta", "wipe", @@ -1125,28 +1255,12 @@ def prepare_to_rebind(module, device, slot): _unused, token_id, err = get_jwe_luks2(module, device, slot) if err: return err - backup, err = backup_luks2_token(module, device, token_id) - if err: - return None, err args = ["cryptsetup", "token", "remove", "--token-id", token_id, device] - ret, _unused, err = module.run_command(args) + ret, _unused, stderr = module.run_command(args) if ret != 0: - return None, {"msg": err} - return backup, None - - -def restore_failed_rebind(module, device, backup): - """Restore metadata after a failed rebind operation. - Return """ - - luks_type, err = get_luks_type(module, device) - if err: - return None, err - - if luks_type == "luks1": - return restore_luks1_device(module, device, backup) - return import_luks2_token(module, device, backup) + return {"msg": stderr} + return None def get_valid_passphrase(module, **kwargs): @@ -1202,6 +1316,12 @@ def bind_slot(module, **kwargs): if err: return False, err + # At this point, we backup the device for later restoration, if binding + # fails. + backup, err = backup_luks_device(module, kwargs["device"]) + if err: + return False, err + passphrase, is_keyfile, err = get_valid_passphrase(module, **kwargs) if err: return False, err @@ -1214,18 +1334,20 @@ def bind_slot(module, **kwargs): # means discard_pw should be false. discard_pw = passphrase == kwargs.get("passphrase", None) - # At this point we can proceed to bind. key, jwe, err = new_pass_jwe( module, kwargs["device"], kwargs["auth"], kwargs["auth_cfg"] ) if err: return False, err + # At this point we can proceed to bind, after backing up the device. + bound, _unused = is_slot_bound(module, kwargs["device"], kwargs["slot"]) if bound: - backup, err = prepare_to_rebind(module, kwargs["device"], kwargs["slot"]) + err = prepare_to_rebind(module, kwargs["device"], kwargs["slot"]) if err: + restore_luks_device(module, kwargs["device"], backup) return False, err # We add the key first because it will be referenced by the metadata. @@ -1239,15 +1361,19 @@ def bind_slot(module, **kwargs): ) if err: - if bound: - restore_failed_rebind(module, kwargs["device"], backup) + restore_luks_device(module, kwargs["device"], backup) return False, err _unused, err = save_slot( - module, device=kwargs["device"], slot=kwargs["slot"], data=jwe, overwrite=True + module, + device=kwargs["device"], + slot=kwargs["slot"], + data=jwe, + overwrite=True, ) if err: + restore_luks_device(module, kwargs["device"], backup) return False, err # Check if we should discard the valid passphrase we used @@ -1363,7 +1489,7 @@ def decode_pin_config(module, jwe): Return """ jwe_json, err = decode_jwe(module, jwe) - if err: + if err or not jwe_json: return None, None, {}, err if "clevis" not in jwe_json or "pin" not in jwe_json["clevis"]: @@ -1395,7 +1521,7 @@ def already_bound(module, **kwargs): slot = kwargs["slot"] # Check #1 - verify whether we have clevis JWE in the slot. - jwe, err = get_jwe(module, device, slot, False) + jwe, err = get_jwe(module, device, slot) if err: return False diff --git a/tests/tasks/bind_repeatedly_single_device.yml b/tests/tasks/bind_repeatedly_single_device.yml new file mode 100644 index 00000000..1e6b6036 --- /dev/null +++ b/tests/tasks/bind_repeatedly_single_device.yml @@ -0,0 +1,18 @@ +--- +- name: Initialize nbde_client_failed_binding and nbde_client_test_slot for device {{ nbde_client_selected_device }} + set_fact: + nbde_client_failed_binding: false + nbde_client_test_slot: 0 + +- name: Keep binding until it fails + include_tasks: bind_slot_with_passphrase.yml + +- name: Display last used slot + debug: + msg: Last used slot was "{{ nbde_client_test_slot }}" for device "{{ nbde_client_selected_device }}" + +- name: Verify the binding failed to be added at least once + assert: + that: nbde_client_failed_binding is true + +# vim:set ts=2 sw=2 et: diff --git a/tests/tasks/bind_slot_with_passphrase.yml b/tests/tasks/bind_slot_with_passphrase.yml new file mode 100644 index 00000000..569a2b09 --- /dev/null +++ b/tests/tasks/bind_slot_with_passphrase.yml @@ -0,0 +1,72 @@ +--- +- name: Bind with passphrase repeatedly until it fails or slot is 7 + block: + - name: Select next slot + set_fact: + nbde_client_test_slot: "{{ nbde_client_test_slot | int + 1 }}" + + - name: Display selected slot + debug: + msg: Selected slot is {{ nbde_client_test_slot }} + + - name: Gather device checksum BEFORE binding operation + shell: > + set -euo pipefail; + sha256sum "{{ nbde_client_selected_device }}" | cut -f1 -d' ' + changed_when: false + register: nbde_client_device_checksum_before + + - name: Perform binding with nbde_client role + include_role: + name: linux-system-roles.nbde_client + public: true + vars: + nbde_client_bindings: + - device: "{{ nbde_client_selected_device }}" + slot: "{{ nbde_client_test_slot }}" + encryption_password: "{{ nbde_client_test_pass }}" + servers: + - http://localhost + - http://localhost + - http://localhost + + - name: Attempt to unlock device + include_tasks: verify_unlock_device.yml + + - name: Make sure the attempt to unlock succeeded + assert: + that: + - not nbde_client_unlock.failed + - not nbde_client_close.failed + + rescue: + - name: Set nbde_client_failed_binding to indicate a binding failed to be added + set_fact: + nbde_client_failed_binding: true + + - name: Gather device checksum AFTER failed binding operation + shell: > + set -euo pipefail; + sha256sum "{{ nbde_client_selected_device }}" | cut -f1 -d' ' + changed_when: false + register: nbde_client_device_checksum_after + + - name: Show checksums for comparison + debug: + msg: | + Checksum BEFORE: {{ nbde_client_device_checksum_before.stdout }} + Checksum AFTER: {{ nbde_client_device_checksum_after.stdout }} + + - name: Make sure the checksum from BEFORE and AFTER matches when binding fails + assert: + that: + - nbde_client_device_checksum_before.stdout == nbde_client_device_checksum_after.stdout + + always: + - name: Include this same task if it has not failed yet and slot is less than 7 + when: + - nbde_client_test_slot|int < 8 + - nbde_client_failed_binding is false + include_tasks: bind_slot_with_passphrase.yml + +# vim:set ts=2 sw=2 et: diff --git a/tests/tasks/cleanup_test.yml b/tests/tasks/cleanup_test.yml index df9a8189..ce3d641a 100644 --- a/tests/tasks/cleanup_test.yml +++ b/tests/tasks/cleanup_test.yml @@ -3,6 +3,9 @@ file: path: "{{ nbde_client_test_device }}" state: absent + loop: + - "{{ nbde_client_test_device }}" # LUKS2 (with modern cryptsetup). + - "{{ nbde_client_test_device_luks1 }}" # LUKS1. - name: Clean up test dir on controller file: diff --git a/tests/tasks/setup_test.yml b/tests/tasks/setup_test.yml index b6c34463..1ed143df 100644 --- a/tests/tasks/setup_test.yml +++ b/tests/tasks/setup_test.yml @@ -60,7 +60,11 @@ command: fallocate -l64m {{ nbde_client_test_device }} changed_when: false -- name: Format test device as LUKS +- name: Create LUKS1 device for testing + command: fallocate -l64m {{ nbde_client_test_device_luks1 }} + changed_when: false + +- name: Format test device as LUKS (LUKS2 with modern cryptsetup) shell: >- set -euo pipefail; echo -n {{ nbde_client_test_pass }} | @@ -68,6 +72,14 @@ --batch-mode --force-password {{ nbde_client_test_device }} changed_when: false +- name: Format another test device as LUKS1 + shell: >- + set -euo pipefail; + echo -n {{ nbde_client_test_pass }} | + cryptsetup luksFormat --type luks1 --pbkdf-force-iterations 1000 + --batch-mode --force-password {{ nbde_client_test_device_luks1 }} + changed_when: false + - name: Create key file for test device copy: content: "{{ nbde_client_test_pass }}" diff --git a/tests/tests_failed_bind.yml b/tests/tests_failed_bind.yml new file mode 100644 index 00000000..a8bdeb79 --- /dev/null +++ b/tests/tests_failed_bind.yml @@ -0,0 +1,33 @@ +--- +- name: Test failed binding operation + hosts: all + + tasks: + - name: Set up test environment + include_tasks: tasks/setup_test.yml + + # For this test we will create many tang keys, so that the metadata + # generated will be too large that it will not fit the LUKS header + # after a few binding attempts. + - name: Add multiple tang keys + command: /usr/libexec/tangd-keygen /var/db/tang/ + changed_when: false + with_sequence: count=32 + + # Now we will attempt to perform multiple binding operations, and at some + # point it will fail, due to the metadata being too large. We will also + # calculate the checksum of the device before each attempt, and, in case + # the binding fails, we will compare the after checksum to check whether + # any changes were performed, in these failed scenarios. + - name: Run the test for each device type + include_tasks: tasks/bind_repeatedly_single_device.yml + loop: + - "{{ nbde_client_test_device }}" # LUKS2 (with modern cryptsetup). + - "{{ nbde_client_test_device_luks1 }}" # LUKS1. + loop_control: + loop_var: nbde_client_selected_device + + - name: Clean up test environment + include_tasks: tasks/cleanup_test.yml + +# vim:set ts=2 sw=2 et: diff --git a/tests/vars/main.yml b/tests/vars/main.yml index 23d768fd..72df1b56 100644 --- a/tests/vars/main.yml +++ b/tests/vars/main.yml @@ -3,5 +3,6 @@ # Put the tests internal variables here that are not distribution specific. nbde_client_test_device: /tmp/.nbde_client_dev_test +nbde_client_test_device_luks1: /tmp/.nbde_client_dev_test_luks1 # vim:set ts=2 sw=2 et: diff --git a/vars/Debian.yml b/vars/Debian.yml index b7b521c6..d1e06434 100644 --- a/vars/Debian.yml +++ b/vars/Debian.yml @@ -3,5 +3,6 @@ __nbde_client_packages: - clevis - clevis-luks - clevis-systemd + - coreutils nbde_client_early_boot: false diff --git a/vars/Fedora.yml b/vars/Fedora.yml index 4f4156d9..5ee00e7f 100644 --- a/vars/Fedora.yml +++ b/vars/Fedora.yml @@ -7,6 +7,7 @@ __nbde_client_packages: - clevis-dracut - clevis-luks - clevis-systemd + - coreutils - iproute - NetworkManager diff --git a/vars/RedHat_10.yml b/vars/RedHat_10.yml index 4b253d40..745f338b 100644 --- a/vars/RedHat_10.yml +++ b/vars/RedHat_10.yml @@ -7,6 +7,7 @@ __nbde_client_packages: - clevis-dracut - clevis-luks - clevis-systemd + - coreutils - iproute - NetworkManager diff --git a/vars/RedHat_7.yml b/vars/RedHat_7.yml index d4467313..7b970ae8 100644 --- a/vars/RedHat_7.yml +++ b/vars/RedHat_7.yml @@ -7,6 +7,7 @@ __nbde_client_packages: - clevis-dracut - clevis-luks - clevis-systemd + - coreutils - iproute - NetworkManager diff --git a/vars/RedHat_8.yml b/vars/RedHat_8.yml index ea481693..94cdc964 100644 --- a/vars/RedHat_8.yml +++ b/vars/RedHat_8.yml @@ -7,6 +7,7 @@ __nbde_client_packages: - clevis-dracut - clevis-luks - clevis-systemd + - coreutils - iproute - NetworkManager diff --git a/vars/RedHat_9.yml b/vars/RedHat_9.yml index 9ac1ea95..57100fb3 100644 --- a/vars/RedHat_9.yml +++ b/vars/RedHat_9.yml @@ -7,6 +7,7 @@ __nbde_client_packages: - clevis-dracut - clevis-luks - clevis-systemd + - coreutils - iproute - NetworkManager From bf6852f47c34790cd065d096f0a22d4829b0d29e Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Wed, 12 Nov 2025 12:08:10 -0700 Subject: [PATCH 2/4] cleanup-tang-keys-and-ensure --- tests/tests_failed_bind.yml | 60 +++++++++++++++++++++++++++---------- 1 file changed, 45 insertions(+), 15 deletions(-) diff --git a/tests/tests_failed_bind.yml b/tests/tests_failed_bind.yml index a8bdeb79..d3e8ead9 100644 --- a/tests/tests_failed_bind.yml +++ b/tests/tests_failed_bind.yml @@ -6,6 +6,12 @@ - name: Set up test environment include_tasks: tasks/setup_test.yml + - name: Get the contents of the tang directory before adding keys + find: + path: /var/db/tang/ + patterns: "*.jwk" + register: tang_dir_contents_before + # For this test we will create many tang keys, so that the metadata # generated will be too large that it will not fit the LUKS header # after a few binding attempts. @@ -14,20 +20,44 @@ changed_when: false with_sequence: count=32 - # Now we will attempt to perform multiple binding operations, and at some - # point it will fail, due to the metadata being too large. We will also - # calculate the checksum of the device before each attempt, and, in case - # the binding fails, we will compare the after checksum to check whether - # any changes were performed, in these failed scenarios. - - name: Run the test for each device type - include_tasks: tasks/bind_repeatedly_single_device.yml - loop: - - "{{ nbde_client_test_device }}" # LUKS2 (with modern cryptsetup). - - "{{ nbde_client_test_device_luks1 }}" # LUKS1. - loop_control: - loop_var: nbde_client_selected_device - - - name: Clean up test environment - include_tasks: tasks/cleanup_test.yml + - name: Run the test + block: + # Now we will attempt to perform multiple binding operations, and at some + # point it will fail, due to the metadata being too large. We will also + # calculate the checksum of the device before each attempt, and, in case + # the binding fails, we will compare the after checksum to check whether + # any changes were performed, in these failed scenarios. + - name: Run the test for each device type + include_tasks: tasks/bind_repeatedly_single_device.yml + loop: + - "{{ nbde_client_test_device }}" # LUKS2 (with modern cryptsetup). + - "{{ nbde_client_test_device_luks1 }}" # LUKS1. + loop_control: + loop_var: nbde_client_selected_device + + always: + - name: Get the contents of the tang directory after adding keys + find: + path: /var/db/tang/ + patterns: "*.jwk" + register: tang_dir_contents_after + + - name: Remove any keys added during the test + file: + path: "{{ item }}" + state: absent + loop: "{{ tang_dir_contents_after.files | map(attribute='path') | list | + difference(tang_dir_contents_before.files | map(attribute='path') | list) }}" + + - name: Ensure directory is same as before + find: + path: /var/db/tang/ + patterns: "*.jwk" + register: tang_dir_contents_final + failed_when: tang_dir_contents_before.files | map(attribute='path') | list | + difference(tang_dir_contents_final.files | map(attribute='path') | list) | list | length > 0 + + - name: Clean up test environment + include_tasks: tasks/cleanup_test.yml # vim:set ts=2 sw=2 et: From 78d7ab707ae763d1dd73ccac11829882e7247f75 Mon Sep 17 00:00:00 2001 From: Rich Megginson Date: Wed, 12 Nov 2025 13:34:21 -0700 Subject: [PATCH 3/4] fix: do not compare to false; fix jinja formatting --- tests/tasks/bind_slot_with_passphrase.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tasks/bind_slot_with_passphrase.yml b/tests/tasks/bind_slot_with_passphrase.yml index 569a2b09..1323eb1b 100644 --- a/tests/tasks/bind_slot_with_passphrase.yml +++ b/tests/tasks/bind_slot_with_passphrase.yml @@ -65,8 +65,8 @@ always: - name: Include this same task if it has not failed yet and slot is less than 7 when: - - nbde_client_test_slot|int < 8 - - nbde_client_failed_binding is false + - nbde_client_test_slot | int < 8 + - not nbde_client_failed_binding include_tasks: bind_slot_with_passphrase.yml # vim:set ts=2 sw=2 et: From 5ea0350b0e223cc4e6963fce97b1e66d5e10d7fa Mon Sep 17 00:00:00 2001 From: Sergio Correia Date: Thu, 13 Nov 2025 20:27:43 +0000 Subject: [PATCH 4/4] Update tests/tasks/bind_repeatedly_single_device.yml Co-authored-by: Richard Megginson --- tests/tasks/bind_repeatedly_single_device.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tasks/bind_repeatedly_single_device.yml b/tests/tasks/bind_repeatedly_single_device.yml index 1e6b6036..13cc46aa 100644 --- a/tests/tasks/bind_repeatedly_single_device.yml +++ b/tests/tasks/bind_repeatedly_single_device.yml @@ -13,6 +13,6 @@ - name: Verify the binding failed to be added at least once assert: - that: nbde_client_failed_binding is true + that: nbde_client_failed_binding # vim:set ts=2 sw=2 et: