-
Notifications
You must be signed in to change notification settings - Fork 67
Unlock Bitlocker encrypted volumes #450
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from 2 commits
663190a
1dda26c
8f3ae63
876028f
931da07
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
|
|
||
| from oslo_log import log as logging | ||
|
|
||
| from coriolis import constants | ||
| from coriolis import exception | ||
| from coriolis.osmorphing.osmount import base | ||
| from coriolis import utils | ||
|
|
@@ -15,6 +16,13 @@ | |
|
|
||
|
|
||
| class WindowsMountTools(base.BaseOSMountTools): | ||
| def __init__(self, *args, **kwargs): | ||
| super().__init__(*args, **kwargs) | ||
|
|
||
| # A list of BitLocker encrypted volumes that were unlocked | ||
| # by us. We'll use a first-boot script to resume BitLocker. | ||
| self._unlocked_volumes: list[str] = [] | ||
|
|
||
| def _connect(self): | ||
| connection_info = self._connection_info | ||
|
|
||
|
|
@@ -223,9 +231,105 @@ def _set_volumes_drive_letter(self): | |
| f"Error was: {utils.get_exception_details()}") | ||
| self._rebring_disks_online(disk_nums=disk_nums) | ||
|
|
||
| def _get_encrypted_volume_ids(self): | ||
| out = self._conn.exec_ps_command( | ||
| 'gwmi -ns "Root\\CIMV2\\Security\\MicrosoftVolumeEncryption" ' | ||
| '-class Win32_EncryptableVolume | % {$_.DeviceID}') | ||
| return [x for x in out.replace("\r\n", "\n").split("\n") if x] | ||
|
|
||
| def _unlock_encrypted_volume(self, volume_id: str, recovery_password: str): | ||
| self._conn.exec_ps_command( | ||
| f'manage-bde -unlock "{volume_id}" ' | ||
| f'-RecoveryPassword "{recovery_password}"') | ||
|
|
||
| def _suspend_bitlocker(self, volume_id: str): | ||
| """Suspend BitLocker until the next reboot for a given volume. | ||
|
|
||
| It doesn't decrypt the device, it just adds a publicly accessible | ||
| BitLocker protector that automatically unlocks the volume. | ||
|
|
||
| When the replica instance boots, the TPM protector will be reconfigured | ||
| automatically. Unfortunately the '-RebootCount' parameter isn't | ||
| honored, perhaps due to the fact that the disks are attached to a | ||
| separate VM. For this reason, we'll use a first-boot script to resume | ||
| BitLocker explicitly. | ||
| """ | ||
| self._conn.exec_ps_command(f'Suspend-BitLocker "{volume_id}"') | ||
|
|
||
| def _unlock_encrypted_volumes(self): | ||
|
petrutlucian94 marked this conversation as resolved.
|
||
| recovery_password = self._osmorphing_info.get( | ||
| constants.ENCRYPTED_DISKS_PASS) | ||
| if not recovery_password: | ||
| LOG.info("No encrypted disk password specified, " | ||
| "skipping BitLocker unlock.") | ||
|
petrutlucian94 marked this conversation as resolved.
|
||
| return | ||
|
|
||
| encrypted_volume_ids = self._get_encrypted_volume_ids() | ||
| if not encrypted_volume_ids: | ||
| LOG.warning("Received encrypted disk password but no " | ||
| "BitLocker encrypted volumes found.") | ||
| return | ||
|
|
||
| unlocked = False | ||
| for encrypted_volume_id in encrypted_volume_ids: | ||
| try: | ||
| self._unlock_encrypted_volume( | ||
| encrypted_volume_id, recovery_password) | ||
| LOG.info( | ||
| "Successfully unlocked BitLocker encrypted volume: %s", | ||
| encrypted_volume_id) | ||
| unlocked = True | ||
| except Exception: | ||
| LOG.info( | ||
| "Could not unlock volume %s using the specified " | ||
| "recovery password.", | ||
| encrypted_volume_id) | ||
| continue | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we are talking about encrypted data here. Shouldn't we panic / raise a bit more if this is the case? If this is the case (this exception occured for the additional disks), and we proceed with the OS morphing and replica start, we'll see the VM start and consider that a success, but the other disks are still locked. If the disks are TPM-locked, can they still be recovered / unlocked, if a recovery password was not set up beforehand?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not really, we mainly care about the OS drive. In most cases, Windows group policies won't even allow re-using the same password for multiple disks. |
||
|
|
||
| # Suspend BitLocker until the replica boots. | ||
| # | ||
| # We'll intentionally propagate the failure if we managed to | ||
| # unlock the volume but failed to suspend BitLocker. | ||
| self._suspend_bitlocker(encrypted_volume_id) | ||
| self._unlocked_volumes.append(encrypted_volume_id) | ||
|
petrutlucian94 marked this conversation as resolved.
|
||
|
|
||
| if not unlocked: | ||
| raise exception.CoriolisException( | ||
| "Could not unlock any volume using the specified " | ||
| "BitLocker recovery password.") | ||
|
|
||
| def install_encryption_firstboot_setup( | ||
| self, | ||
| os_root_dir, | ||
| os_morphing_tools, | ||
| ): | ||
| if not self._unlocked_volumes: | ||
| LOG.info( | ||
| "No unlocked BitLocker volumes, skipping first-boot setup.") | ||
| return | ||
|
|
||
| # We'll inject a first-boot script to resume BitLocker explicitly. | ||
| # Unfortunately the "-RebootCount" parameter of "Suspend-BitLocker" | ||
| # isn't honored, perhaps due to the fact that the disks are attached | ||
| # to a different VM. | ||
| script_content = "" | ||
| for encrypted_volume_id in self._unlocked_volumes: | ||
| LOG.info( | ||
| "Resuming BitLocker after first boot, volume: %s", | ||
| encrypted_volume_id) | ||
| script_content += f'Resume-BitLocker "{encrypted_volume_id}"\r\n' | ||
|
|
||
| # Resume BitLocker after bringing the disks online, which has a script | ||
| # priority of 10. | ||
| os_morphing_tools.register_firstboot_script( | ||
| script_content, | ||
| user_provided=False, | ||
| script_filename="11-bitlocker-firstboot.ps1") | ||
|
|
||
| def mount_os(self): | ||
| self._set_basic_disks_rw_mode() | ||
| self._bring_disks_online() | ||
| self._unlock_encrypted_volumes() | ||
| self._set_volumes_drive_letter() | ||
| fs_roots = utils.retry_on_error(sleep_seconds=5)(self._get_fs_roots)( | ||
| fail_if_empty=True) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.