Skip to content

Commit

Permalink
Merge branch 'master' into master-mdraid_support_volumes
Browse files Browse the repository at this point in the history
  • Loading branch information
japokorn authored Jun 11, 2020
2 parents 13cc503 + 98fd8bd commit 3adcee1
Show file tree
Hide file tree
Showing 15 changed files with 533 additions and 14 deletions.
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,27 @@ When type is `raid` specifies RAID metadata version as a string, e.g.: '1.0'.
When type is `raid` specifies RAID chunk size as a string, e.g.: '512 KiB'.
Chunk size has to be multiple of 4 KiB.

##### `encryption`
This specifies whether or not the volume will be encrypted using LUKS.
__WARNING__: Toggling encryption for a volume is a destructive operation, meaning
all data on that volume will be removed as part of the process of
adding/removing the encryption layer.

##### `encryption_passphrase`
This string specifies a passphrase used to unlock/open the LUKS volume.

##### `encryption_key_file`
This string specifies the full path to the key file used to unlock the LUKS volume.

##### `encryption_cipher`
This string specifies a non-default cipher to be used by LUKS.

##### `encryption_key_size`
This integer specifies the LUKS key size (in bits).

##### `encryption_luks_version`
This integer specifies the LUKS version to use.

#### `storage_safe_mode`
When true (the default), an error will occur instead of automatically removing existing devices and/or formatting.

Expand Down
7 changes: 7 additions & 0 deletions defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,10 @@ storage_volume_defaults:
raid_spare_count: null
raid_chunk_size: null
raid_metadata_version: null

encryption: false
encryption_passphrase: null
encryption_key_file: null
encryption_cipher: null
encryption_key_size: null
encryption_luks_version: null
107 changes: 100 additions & 7 deletions library/blivet.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@
mounts:
description: list of dicts describing mounts to set up
type: list of dict
crypts:
description: list of dicts describing crypttab entries to set up
type: list of dict
pools:
description: list of dicts describing the pools w/ device path for each volume
type: list of dict
Expand Down Expand Up @@ -192,6 +195,20 @@ def _look_up_device(self):
if device is None:
return

if device.format.type == 'luks':
# XXX If we have no key we will always re-encrypt.
device.format._key_file = self._volume.get('encryption_key_file')
device.format.passphrase = self._volume.get('encryption_passphrase')

# set up the original format as well since it'll get used for processing
device.original_format._key_file = self._volume.get('encryption_key_file')
device.original_format.passphrase = self._volume.get('encryption_passphrase')
if device.isleaf:
self._blivet.populate()

if not device.isleaf:
device = device.children[0]

self._device = device

# check that the type is correct, raising an exception if there is a name conflict
Expand Down Expand Up @@ -221,10 +238,53 @@ def _destroy(self):

# save device identifiers for use by the role
self._volume['_device'] = self._device.path
self._volume['_raw_device'] = self._device.raw_device.path
self._volume['_mount_id'] = self._device.fstab_spec

# schedule removal of this device and any descendant devices
self._blivet.devicetree.recursive_remove(self._device)
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

def _resize(self):
""" Schedule actions as needed to ensure the device has the desired size. """
Expand Down Expand Up @@ -256,7 +316,7 @@ def _reformat(self):
if safe_mode and (self._device.format.type is not None or self._device.format.name != get_format(None).name):
raise BlivetAnsibleError("cannot remove existing formatting on volume '%s' in safe mode" % self._volume['name'])

if self._device.format.status:
if self._device.format.status and (self._device.format.mountable or self._device.format.type == "swap"):
self._device.format.teardown()
self._blivet.format_device(self._device, fmt)

Expand All @@ -277,16 +337,19 @@ def manage(self):
if self._device is None:
raise BlivetAnsibleError("failed to look up or create device '%s'" % self._volume['name'])

self._manage_encryption()

# schedule reformat if appropriate
if self._device.exists:
if self._device.raw_device.exists:
self._reformat()

# schedule resize if appropriate
if self._device.exists and self._volume['size']:
if self._device.raw_device.exists and self._volume['size']:
self._resize()

# save device identifiers for use by the role
self._volume['_device'] = self._device.path
self._volume['_raw_device'] = self._device.raw_device.path
self._volume['_mount_id'] = self._device.fstab_spec


Expand All @@ -297,7 +360,10 @@ def _get_device_id(self):
return self._volume['disks'][0]

def _type_check(self):
return self._device.is_disk
return self._device.raw_device.is_disk

def _create(self):
self._reformat()

def _look_up_device(self):
super(BlivetDiskVolume, self)._look_up_device()
Expand All @@ -314,7 +380,7 @@ class BlivetPartitionVolume(BlivetVolume):
blivet_device_class = devices.PartitionDevice

def _type_check(self):
return self._device.type == 'partition'
return self._device.raw_device.type == 'partition'

def _get_device_id(self):
return self._blivet_pool._disks[0].name + '1'
Expand Down Expand Up @@ -756,6 +822,7 @@ def manage_volume(b, volume):
bvolume = _get_blivet_volume(b, volume)
bvolume.manage()
volume['_device'] = bvolume._volume.get('_device', '')
volume['_raw_device'] = bvolume._volume.get('_raw_device', '')
volume['_mount_id'] = bvolume._volume.get('_mount_id', '')


Expand All @@ -765,6 +832,7 @@ def manage_pool(b, pool):
bpool.manage()
for (volume, bvolume) in zip(pool['volumes'], bpool._blivet_volumes):
volume['_device'] = bvolume._volume.get('_device', '')
volume['_raw_device'] = bvolume._volume.get('_raw_device', '')
volume['_mount_id'] = bvolume._volume.get('_mount_id', '')


Expand Down Expand Up @@ -870,6 +938,20 @@ def handle_new_mount(volume, fstab):
return mount_info


def get_crypt_info(actions):
info = list()
for action in actions:
if not (action.is_format and action.format.type == 'luks'):
continue

info.append(dict(backing_device=action.device.path,
name=action.format.map_name,
password=action.format.key_file or '-',
state='present' if action.is_create else 'absent'))

return sorted(info, key=lambda e: e['state'])


def get_required_packages(b, pools, volumes):
packages = list()
for pool in pools:
Expand Down Expand Up @@ -901,6 +983,14 @@ def update_fstab_identifiers(b, pools, volumes):
for volume in all_volumes:
if volume['state'] == 'present':
device = b.devicetree.resolve_device(volume['_mount_id'])
if device is None and volume['encryption']:
device = b.devicetree.resolve_device(volume['_raw_device'])
if device is not None and not device.isleaf:
device = device.children[0]
volume['_device'] = device.path

if device is None:
raise BlivetAnsibleError("failed to look up device for volume %s (%s/%s)" % (volume['name'], volume['_device'], volume['_mount_id']))
volume['_mount_id'] = device.fstab_spec
if device.format.type == 'swap':
device.format.setup()
Expand Down Expand Up @@ -938,6 +1028,7 @@ def run_module():
actions=list(),
leaves=list(),
mounts=list(),
crypts=list(),
pools=list(),
volumes=list(),
packages=list(),
Expand Down Expand Up @@ -1014,7 +1105,8 @@ def action_dict(action):
result['packages'] = b.packages[:]

for action in scheduled:
if action.is_destroy and action.is_format and action.format.exists:
if action.is_destroy and action.is_format and action.format.exists and \
(action.format.mountable or action.format.type == "swap"):
action.format.teardown()

if scheduled:
Expand All @@ -1032,6 +1124,7 @@ def action_dict(action):
activate_swaps(b, module.params['pools'], module.params['volumes'])

result['mounts'] = get_mount_info(module.params['pools'], module.params['volumes'], actions, fstab)
result['crypts'] = get_crypt_info(actions)
result['leaves'] = [d.path for d in b.devicetree.leaves]
result['pools'] = module.params['pools']
result['volumes'] = module.params['volumes']
Expand Down
1 change: 1 addition & 0 deletions molecule_extra_requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# SPDX-License-Identifier: MIT

# Write extra requirements for running molecule here:
jmespath
37 changes: 35 additions & 2 deletions tasks/main-blivet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,14 +129,18 @@
# changed options? (just add w/ new settings?)
# add new mounts
#
- name: manage mounts to match the specified state
# XXX Apparently we have to do the removals, then tell systemd to
# update its view, then set up the new mounts. Otherwise,
# systemd will forcibly prevent mounting a new volume to an
# existing mount point.
- name: remove obsolete mounts
mount:
src: "{{ mount_info['src']|default(omit) }}"
path: "{{ mount_info['path'] }}"
fstype: "{{ mount_info['fstype']|default(omit) }}"
opts: "{{ mount_info['opts']|default(omit) }}"
state: "{{ mount_info['state'] }}"
loop: "{{ blivet_output.mounts }}"
loop: "{{ blivet_output.mounts|json_query('[?state==`absent`]') }}"
loop_control:
loop_var: mount_info

Expand All @@ -145,6 +149,35 @@
daemon_reload: yes
when: blivet_output['mounts']

- name: set up new/current mounts
mount:
src: "{{ mount_info['src']|default(omit) }}"
path: "{{ mount_info['path'] }}"
fstype: "{{ mount_info['fstype']|default(omit) }}"
opts: "{{ mount_info['opts']|default(omit) }}"
state: "{{ mount_info['state'] }}"
loop: "{{ blivet_output.mounts|json_query('[?state!=`absent`]') }}"
loop_control:
loop_var: mount_info

- name: tell systemd to refresh its view of /etc/fstab
systemd:
daemon_reload: yes
when: blivet_output['mounts']

#
# Manage /etc/crypttab
#
- name: Manage /etc/crypttab to account for changes we just made
crypttab:
name: "{{ entry.name }}"
backing_device: "{{ entry.backing_device }}"
password: "{{ entry.password }}"
state: "{{ entry.state }}"
loop: "{{ blivet_output.crypts }}"
loop_control:
loop_var: entry

#
# Update facts since we may have changed system state.
#
Expand Down
6 changes: 3 additions & 3 deletions tests/test-verify-volume-device.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
#
- name: (1/3) Process device path (set initial value)
set_fact:
storage_test_device_path: "{{ storage_test_volume._device }}"
storage_test_device_path: "{{ storage_test_volume._raw_device }}"

# realpath fails when given empty string; the task then returns completely different dict.
- block:
- name: (2/3) Process device path (get device file info)
command: realpath "{{ storage_test_volume._device }}"
command: realpath "{{ storage_test_volume._raw_device }}"
register: storage_test_realpath

- name: (3/3) Process device path (replace device with its target if it is a symlink)
Expand All @@ -23,7 +23,7 @@
# name/path
- name: See whether the device node is present
stat:
path: "{{ storage_test_volume._device }}"
path: "{{ storage_test_volume._raw_device }}"
follow: yes
register: storage_test_dev

Expand Down
Loading

0 comments on commit 3adcee1

Please sign in to comment.