-
Notifications
You must be signed in to change notification settings - Fork 59
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
Encrypted Pools #102
Merged
Merged
Encrypted Pools #102
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d94a255
Initial bones for pool member encryption.
dwlehman 1c4b709
Add validation of pool members.
dwlehman 230d021
Add validation of pool encryption.
dwlehman 902110b
Account for destructive pool member changes.
dwlehman ae33854
Create a common base class for pools and volumes.
dwlehman 4cb90e6
Use shared code for encryption management on a single device.
dwlehman 171ef79
Unset pool device after _destroy.
dwlehman 5b15f29
Fix up MD paths in blockdev_info.
dwlehman ab85947
Fix validation of pool member types for raid pools.
dwlehman 1c25922
Add raid_level to defaults for pools.
dwlehman File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -148,15 +148,72 @@ class BlivetAnsibleError(Exception): | |
pass | ||
|
||
|
||
class BlivetVolume(object): | ||
class BlivetBase(object): | ||
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. do you need unit tests for these changes? if so, they can be added later after the 15th |
||
blivet_device_class = None | ||
_type = None | ||
|
||
def __init__(self, blivet_obj, volume, bpool=None): | ||
def __init__(self, blivet_obj, spec_dict): | ||
self._blivet = blivet_obj | ||
self._volume = volume | ||
self._blivet_pool = bpool | ||
self._spec_dict = spec_dict | ||
self._device = None | ||
|
||
def _manage_one_encryption(self, device): | ||
ret = device | ||
# Make sure to handle adjusting both existing stacks and future stacks. | ||
if device == device.raw_device and self._spec_dict['encryption']: | ||
# add luks | ||
luks_name = "luks-%s" % device._name | ||
if not device.format.exists: | ||
fmt = device.format | ||
else: | ||
fmt = get_format(None) | ||
|
||
self._blivet.format_device(device, | ||
get_format("luks", | ||
name=luks_name, | ||
cipher=self._spec_dict.get('encryption_cipher'), | ||
key_size=self._spec_dict.get('encryption_key_size'), | ||
luks_version=self._spec_dict.get('encryption_luks_version'), | ||
passphrase=self._spec_dict.get('encryption_passphrase') or None, | ||
key_file=self._spec_dict.get('encryption_key_file') or None)) | ||
|
||
if not device.format.has_key: | ||
raise BlivetAnsibleError("encrypted %s '%s' missing key/passphrase" % (self._type, self._spec_dict['name'])) | ||
|
||
luks_device = devices.LUKSDevice(luks_name, | ||
fmt=fmt, | ||
parents=[device]) | ||
self._blivet.create_device(luks_device) | ||
ret = luks_device | ||
elif device != device.raw_device and not self._spec_dict['encryption']: | ||
# remove luks | ||
if not device.format.exists: | ||
fmt = device.format | ||
else: | ||
fmt = get_format(None) | ||
|
||
ret = self._device.raw_device | ||
self._blivet.destroy_device(device) | ||
if fmt.type is not None: | ||
self._blivet.format_device(ret, fmt) | ||
|
||
# XXX: blivet has to store cipher, key_size, luks_version for existing before we | ||
# can support re-encrypting based on changes to those parameters | ||
|
||
return ret | ||
|
||
|
||
class BlivetVolume(BlivetBase): | ||
_type = "volume" | ||
|
||
def __init__(self, blivet_obj, volume, bpool=None): | ||
super(BlivetVolume, self).__init__(blivet_obj, volume) | ||
self._blivet_pool = bpool | ||
|
||
@property | ||
def _volume(self): | ||
return self._spec_dict | ||
|
||
@property | ||
def required_packages(self): | ||
packages = list() | ||
|
@@ -243,46 +300,7 @@ def _destroy(self): | |
self._blivet.devicetree.recursive_remove(self._device.raw_device) | ||
|
||
def _manage_encryption(self): | ||
# Make sure to handle adjusting both existing stacks and future stacks. | ||
if self._device == self._device.raw_device and self._volume['encryption']: | ||
# add luks | ||
luks_name = "luks-%s" % self._device._name | ||
if not self._device.format.exists: | ||
fmt = self._device.format | ||
else: | ||
fmt = get_format(None) | ||
|
||
self._blivet.format_device(self._device, | ||
get_format("luks", | ||
name=luks_name, | ||
cipher=self._volume.get('encryption_cipher'), | ||
key_size=self._volume.get('encryption_key_size'), | ||
luks_version=self._volume.get('encryption_luks_version'), | ||
passphrase=self._volume.get('encryption_passphrase') or None, | ||
key_file=self._volume.get('encryption_key_file') or None)) | ||
|
||
if not self._device.format.has_key: | ||
raise BlivetAnsibleError("encrypted volume '%s' missing key/passphrase" % self._volume['name']) | ||
|
||
luks_device = devices.LUKSDevice(luks_name, | ||
fmt=fmt, | ||
parents=[self._device]) | ||
self._blivet.create_device(luks_device) | ||
self._device = luks_device | ||
elif self._device != self._device.raw_device and not self._volume['encryption']: | ||
# remove luks | ||
if not self._device.format.exists: | ||
fmt = self._device.format | ||
else: | ||
fmt = get_format(None) | ||
|
||
self._device = self._device.raw_device | ||
self._blivet.destroy_device(self._device.children[0]) | ||
if fmt.type is not None: | ||
self._blivet.format_device(self._device, fmt) | ||
|
||
# XXX: blivet has to store cipher, key_size, luks_version for existing before we | ||
# can support re-encrypting based on changes to those parameters | ||
self._device = self._manage_one_encryption(self._device) | ||
|
||
def _resize(self): | ||
""" Schedule actions as needed to ensure the device has the desired size. """ | ||
|
@@ -462,16 +480,18 @@ def _get_blivet_volume(blivet_obj, volume, bpool=None): | |
return _BLIVET_VOLUME_TYPES[volume_type](blivet_obj, volume, bpool=bpool) | ||
|
||
|
||
class BlivetPool(object): | ||
blivet_device_class = None | ||
class BlivetPool(BlivetBase): | ||
_type = "pool" | ||
|
||
def __init__(self, blivet_obj, pool): | ||
self._blivet = blivet_obj | ||
self._pool = pool | ||
self._device = None | ||
super(BlivetPool, self).__init__(blivet_obj, pool) | ||
self._disks = list() | ||
self._blivet_volumes = list() | ||
|
||
@property | ||
def _pool(self): | ||
return self._spec_dict | ||
|
||
@property | ||
def required_packages(self): | ||
packages = list() | ||
|
@@ -489,6 +509,9 @@ def ultimately_present(self): | |
def _is_raid(self): | ||
return self._pool.get('raid_level') not in [None, "null", ""] | ||
|
||
def _member_management_is_destructive(self): | ||
return False | ||
|
||
def _create(self): | ||
""" Schedule actions as needed to ensure the pool exists. """ | ||
pass | ||
|
@@ -515,6 +538,8 @@ def _destroy(self): | |
|
||
leaves = [a for a in ancestors if a.isleaf] | ||
|
||
self._device = None | ||
|
||
def _type_check(self): # pylint: disable=no-self-use | ||
return True | ||
|
||
|
@@ -624,10 +649,12 @@ def manage(self): | |
self._look_up_device() | ||
|
||
# schedule destroy if appropriate, including member type change | ||
if not self.ultimately_present: # TODO: member type changes | ||
self._manage_volumes() | ||
if not self.ultimately_present or self._member_management_is_destructive(): | ||
if not self.ultimately_present: | ||
self._manage_volumes() | ||
self._destroy() | ||
return | ||
if not self.ultimately_present: | ||
return | ||
|
||
# schedule create if appropriate | ||
self._create() | ||
|
@@ -660,19 +687,36 @@ class BlivetLVMPool(BlivetPool): | |
def _type_check(self): | ||
return self._device.type == "lvmvg" | ||
|
||
def _member_management_is_destructive(self): | ||
if self._device is None: | ||
return False | ||
|
||
if self._pool['encryption'] and not all(m.encrypted for m in self._device.parents): | ||
return True | ||
elif not self._pool['encryption'] and any(m.encrypted for m in self._device.parents): | ||
return True | ||
|
||
return False | ||
|
||
def _get_format(self): | ||
fmt = get_format("lvmpv") | ||
if not fmt.supported or not fmt.formattable: | ||
raise BlivetAnsibleError("required tools for managing LVM are missing") | ||
|
||
return fmt | ||
|
||
def _manage_encryption(self, members): | ||
managed_members = list() | ||
for member in members: | ||
managed_members.append(self._manage_one_encryption(member)) | ||
|
||
return managed_members | ||
|
||
def _create(self): | ||
if self._device: | ||
return | ||
|
||
members = self._create_members() | ||
|
||
members = self._manage_encryption(self._create_members()) | ||
try: | ||
pool_device = self._blivet.new_vg(name=self._pool['name'], parents=members) | ||
except Exception as e: | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
- set_fact: | ||
_storage_test_pool_pvs_lvm: "{{ ansible_lvm.pvs|dict2items(key_name='path', value_name='info')|json_query('[?info.vg==`\"{}\"`].path'.format(storage_test_pool.name))|list }}" | ||
_storage_test_pool_pvs: [] | ||
_storage_test_expected_pv_count: "{{ 0 if storage_test_pool.state == 'absent' else (storage_test_pool.raid_level | ternary(1, storage_test_pool.disks|length)) }}" | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- name: Get the canonical device path for each member device | ||
resolve_blockdev: | ||
spec: "{{ pv }}" | ||
loop: "{{ _storage_test_pool_pvs_lvm }}" | ||
loop_control: | ||
loop_var: pv | ||
register: pv_paths | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- set_fact: | ||
_storage_test_pool_pvs: "{{ _storage_test_pool_pvs }} + [ '{{ pv_paths.results[idx].device }}' ]" | ||
loop: "{{ _storage_test_pool_pvs_lvm }}" | ||
loop_control: | ||
index_var: idx | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- name: Verify PV count | ||
assert: | ||
that: "{{ ansible_lvm.pvs|dict2items|json_query('[?value.vg==`\"{}\"`]'.format(storage_test_pool.name))|length == _storage_test_expected_pv_count|int }}" | ||
msg: "Unexpected PV count for pool {{ storage_test_pool.name }}" | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- set_fact: | ||
_storage_test_expected_pv_type: "{{ 'crypt' if storage_test_pool.encryption else 'disk' }}" | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- set_fact: | ||
_storage_test_expected_pv_type: "{{ 'partition' if storage_use_partitions|default(false) else 'disk' }}" | ||
when: storage_test_pool.type == 'lvm' and not storage_test_pool.encryption | ||
|
||
- set_fact: | ||
_storage_test_expected_pv_type: "{{ storage_test_pool.raid_level }}" | ||
when: storage_test_pool.type == 'lvm' and storage_test_pool.raid_level | ||
|
||
- name: Check the type of each PV | ||
assert: | ||
that: "{{ storage_test_blkinfo.info[pv]['type'] == _storage_test_expected_pv_type }}" | ||
msg: "Incorrect type for PV {{ pv }} in pool {{ storage_test_pool.name }}" | ||
loop: "{{ _storage_test_pool_pvs }}" | ||
loop_control: | ||
loop_var: pv | ||
when: storage_test_pool.type == 'lvm' | ||
|
||
- name: Check member encryption | ||
include_tasks: verify-pool-members-encryption.yml | ||
|
||
- set_fact: | ||
_storage_test_expected_pv_type: null | ||
_storage_test_expected_pv_count: null | ||
_storage_test_pool_pvs_lvm: [] | ||
_storage_test_pool_pvs: [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
path on the control node or on the managed host?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Everything is on the managed host AFAIK. Any operations to get the key file to the managed nodes is on the user. Maybe I should note that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will adopt
encryption_passphrase
andencryption_key_file
. These seem more explicit.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use just
passphrase
andkey_file
. In your role it is obvious that it is encryption related (it is an encryption role). I was concerned about the gratuitouspass
vspassphrase
difference orkeyfile
vs.key_file
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dwlehman would it make sense to have
key_file
on the control node?nbde_client
uses a keyfile on the control node: linux-system-roles/nbde_client#3 (comment) . Are the use cases different enough to justify this difference in semantics?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it is more convenient for users and the consistency between roles is good.
Where on the managed node should we place the key files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sergio-correia what do you do in nbde_client?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I define an internal variable
__nbde_client_managed_dir
and have a task to copy the key files there and do the cleanup later. I pass this variable also to the python module, so it knows where to find the key files. It's currently defined as/var/run/linux-system-roles.nbde_client
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For our purposes I think we need to keep the key file around for reboots, while I (think that I) see that you are removing them in the nbde_client role after setting up nbde. For us to specify that they be on the controller necessitates developing a scheme for storing them on the managed nodes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, for us it's simpler that we need it to do some operations, then we don't need it anymore and can remove it.