Skip to content
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

Verify that disk volumes are mounted by UUID in fstab #50

Closed
wants to merge 9 commits into from
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,27 @@ The `mount_point` specifies the directory on which the file system will be mount
##### `mount_options`
The `mount_options` specifies custom mount options as a string, e.g.: 'ro'.

##### `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 bytes).

##### `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 @@ -24,3 +24,10 @@ storage_volume_defaults:
mount_check: 0
mount_passno: 0
mount_device_identifier: "uuid" # uuid|label|path

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 @@ -190,6 +193,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 @@ -219,10 +236,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 @@ -254,7 +314,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 @@ -275,16 +335,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 @@ -295,7 +358,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 @@ -313,7 +379,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 @@ -606,6 +672,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 @@ -615,6 +682,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 @@ -720,6 +788,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 @@ -751,6 +833,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 @@ -788,6 +878,7 @@ def run_module():
actions=list(),
leaves=list(),
mounts=list(),
crypts=list(),
pools=list(),
volumes=list(),
packages=list(),
Expand Down Expand Up @@ -864,7 +955,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 @@ -882,6 +974,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 @@ -2,7 +2,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 All @@ -16,13 +16,13 @@

- name: Make sure we got info about this volume
assert:
that: "{{ storage_test_volume._device in storage_test_blkinfo.info }}"
that: "{{ storage_test_volume._raw_device in storage_test_blkinfo.info }}"
msg: "Failed to gather info about volume '{{ storage_test_volume.name }}'"
when: _storage_test_volume_present

- name: Verify the volume's device type
assert:
that: "{{ storage_test_blkinfo.info[storage_test_volume._device].type == storage_test_volume.type }}"
that: "{{ storage_test_blkinfo.info[storage_test_volume._raw_device].type == storage_test_volume.type }}"
when: _storage_test_volume_present

# disks
Expand Down
Loading