DEBOPS.CRYPTSETUP(5)

NAME
debops.cryptsetup - Setup and manage encrypted filesystems

SYNOPSIS

debops service/cryptsetup [--limit group,host,...] [--diff] [--check] [--tags tag1,tag2,...] [--skip-tags tag1,tag2,...] [<ansible-playbook options>] ...

DESCRIPTION

debops.cryptsetup allows you to configure encrypted filesystems on top of any given block device using dm-crypt <https://en.wikipedia.org/wiki/Dm-crypt>/cryptsetup <https://gitlab.com/cryptsetup/cryptsetup> and LUKS <https://en.wikipedia.org/wiki/Linux_Unified_Key_Setup>. A random keyfile generated on the Ansible controller will be used for the encryption by default. It is your responsibility that the keyfile is kept secure for this to make sense. For example by storing the keyfile on an already encrypted filesystem (both on the Ansible controller and the remote system).

Features:

•   Create a random keyfile or use an already existing keyfile.

•   Manage /etc/crypttab and /etc/fstab and mount point directories.

•   Create a LUKS header backup and store it on the Ansible controller.

•   Decrypt and mount an encrypted filesystem and never store any key material on persistent storage on the remote system. You might need to take care of your Swap space yourself for this!

•   Setup an encrypted swap space (with random key or with persistent key).

•   Setup filesystems using a random key on boot.

•   cryptsetup plain, LUKS, TrueCrypt and VeraCrypt mode.

•   Multiple ciphers and corresponding keys chained to encrypt one filesystem.

GETTING STARTED

  Overview and terminology

The following layers are involved in configuring an encrypted filesystem using block device encryption:

1.  Ciphertext block device: This can be any block device or partition on a block device. It can also be a regular file which will be mapped to a block device using loop by cryptsetup.

2.  Plaintext device mapper target: Created by dm-crypt <https://en.wikipedia.org/wiki/Dm-crypt> under /dev/mapper/. Opening this layer is called "mapping" or "decrypting" which means making the plaintext device mapper target available by loading and keeping the master key for the block cypher in volatile memory (RAM).

3.  Plaintext mount point of the filesystem: Where the plaintext files can be accessed. Opening this layer is called "mounting".

  Example inventory

To configure encrypted filesystems on host given in debops_service_cryptsetup Ansible inventory group:

[debops_service_cryptsetup]
hostname

  Example playbook

If you are using this role without DebOps, here's an example Ansible playbook that uses the debops.cryptsetup role:

---
- name: Setup and manage encrypted filesystems
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_cryptsetup' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: cryptsetup
      tags: [ 'role::cryptsetup', 'skip::cryptsetup' ]

If you are using this role without DebOps, here's an example Ansible playbook that uses debops.cryptsetup together with the debops.persistent_paths <https://github.com/debops/ansible-persistent_paths> role:

---
- name: Setup and manage encrypted filesystems and ensure persistence
  collections: [ 'debops.debops', 'debops.roles01',
                 'debops.roles02', 'debops.roles03' ]
  hosts: [ 'debops_service_cryptsetup_persistent_paths' ]
  become: True
  environment: '{{ inventory__environment | d({})
                   | combine(inventory__group_environment | d({}))
                   | combine(inventory__host_environment  | d({})) }}'
  roles:
    - role: cryptsetup
      tags: [ 'role::cryptsetup', 'skip::cryptsetup' ]
    - role: persistent_paths
      tags: [ 'role::persistent_paths', 'skip::persistent_paths' ]
      persistent_paths__dependent_paths: '{{ cryptsetup__persistent_paths__dependent_paths }}'

  Ansible tags

You can use Ansible --tags or --skip-tags parameters to limit what tasks are performed during Ansible run. This can be used after a host is first configured to speed up playbook execution, when you are sure that most of the configuration is already in the desired state.

Available role tags:

role::cryptsetup
    Main role tag, should be used in the playbook to execute all of the role tasks as well as role dependencies.

role::cryptsetup:backup
    LUKS header backup related tasks.

  debops.persistent_paths support

In case the host in question happens to be a TemplateBasedVM on Qubes OS <https://en.wikipedia.org/wiki/Qubes_OS> or another system where persistence is not the default, it should be absent in debops_service_cryptsetup and instead be added to the debops_service_cryptsetup_persistent_paths Ansible inventory group so that the changes can be made persistent:

[debops_service_cryptsetup_persistent_paths]
hostname

Besides that, the cryptsetup__base_packages are expected to be present (typically installed in the TemplateVM).

Note that even if the same filesystem is bind mounted to different locations they are considered different file systems by mv which would case it fall back to content copying instead of just metadata updating. Be sure to always access the plaintext mount point by one path if you care about this. So either cryptsetup__mountpoint_parent_directory or /rw/bind-dirs/media/ on Qubes OS.

GUIDES AND EXAMPLES

Be sure that you installed the role and setup your Ansible project to use the role (Getting started). There is also the Getting Started guide to learn the basics.

  Setup an encrypted loop device

For testing purposes loop devices <https://en.wikipedia.org/wiki/Loop_device> can be used to get started with this role. So lets create a loop device:

truncate --size=42M /var/tmp/example1_loop_file.raw
losetup --show --find /var/tmp/example1_loop_file.raw

The printed loop device will be our ciphertext block device (Overview and terminology). /dev/loop0 is assumed from now on. Note that the role and cryptsetup can also use a regular file as ciphertext block device directly.

Now you can use one of the cryptsetup__devices variables as listed in the debops.cryptsetup default variables documentation. We are going to use cryptsetup__host_devices which is intended to go into the Ansible inventory file of a host (./ansible/inventory/host_vars/$hostname). You can use an entry like this:

cryptsetup__host_devices:
  - name: 'example1'
    ciphertext_block_device: '/dev/loop0'

The role should be "enabled" for this host as shown in Example inventory. Then run the playbook of the role:

debops service/cryptsetup -l "$hostname"

which should have the following effects:

•   Create a random keyfile on the Ansible controller under ./ansible/secret/cryptsetup/$hostname/example1/keyfile.raw

•   Copy the keyfile to the remote host under /var/local/keyfiles/example1_keyfile.raw

•   Initialize LUKS by creating a LUKS header on /dev/loop0 using the keyfile

•   Make a backup of the LUKS header on the remote host under /var/backups/luks_header_backup/example1_header_backup.raw

•   Copy the LUKS header backup to the Ansible controller under ./ansible/secret/cryptsetup/$hostname/example1/header_backup.raw

•   Open/map /dev/loop0 to /dev/mapper/example1 (Plaintext device mapper target)

•   Make the opening/mapping persistent in /etc/crypttab (either for automatic opening on system start or manually using cryptdisks_start which can be chosen by additional role configuration options)

•   Create a filesystem on /dev/mapper/example1

•   Create the mount point directory for the filesystem under /media/example1

•   Mount /dev/mapper/example1 under /media/example1 (Plaintext mount point of the filesystem)

•   Remember the filesystem information and mount point in /etc/fstab

    All of those tasks are idempotent so you can run the role repetitively against the host and the role will not reformat the filesystem nor reinitialize LUKS on the device.

    If the LUKS header has been changed between role runs, the role picks up the changed header and updates the two backups of it. The task "Store the header backup in secret directory on to the Ansible controller" will signal a changed header with the task state "changed".

    You can check that the plaintext mount point of the filesystem is mounted using:

    df -h /media/example1

    which should show something like:

    Filesystem            Size  Used Avail Use% Mounted on
    /dev/mapper/example1   35M  491K   32M   2% /media/example1

    You can now use /media/example1 to store files which are transparently encrypted and saved on /dev/loop0 (respectively /var/tmp/example1_loop_file.raw).

  Teardown an encrypted device

One nice part of using an encrypted filesystem is that access to the plaintext files can quickly be denied. This is supported by the role. You just need to change the inventory configuration of a configured device. Using the example from Setup an encrypted loop device this could look like the following:

cryptsetup__host_devices:
  - name: 'example1'
    ciphertext_block_device: '/dev/loop0'
    state: 'absent'

Then run the playbook of the role:

debops service/cryptsetup -l "$hostname"

which should have the following effects:

•   Unmount /media/example1

•   Remove the filesystem information and mount point from /etc/fstab

•   Remove the mount point directory /media/example1

•   Close/unmap /dev/mapper/example1

•   Remove the ciphertext block device information from /etc/crypttab

•   Shredder the keyfile on the remote host under /var/local/keyfiles/example1_keyfile.raw

•   Shredder the header backup on the remote host under /var/backups/luks_header_backup/example1_header_backup.raw

    Note that shredder means to overwrite the file 42 times before removing it. Depending on where those files where stored that might not have the desired effect.

    After the role run terminated, no access to plaintext files should be possible. If you want to access the plaintext files again, just change the state and rerun the role as all required information is still stored on the Ansible controller.

DEFAULT VARIABLE DETAILS

Some of debops.cryptsetup variables have more extensive configuration. Here you can find documentation and examples for them.

  cryptsetup__devices

The cryptsetup__devices and similar lists allow you to specify device configuration. The order can be important because devices depend on each other and this will determine the order in which the devices appear in /etc/crypttab.

Note the following list only documents the common parameters. The role allows you to use more specific parameters which are not documented below.

Each item of those lists is a dictionary with the following documented keys:

name
    Required, string. Name of the plaintext device mapper target and the mount point (unless overwritten by item.mount). Must be unique among all device mapper targets and should not be changed once it was used.

    If you want to change it, you can set state to absent, execute the role, rename the secrets directory corresponding to the name, adapt your inventory accordingly and run the role again to configure the item with the new name.

ciphertext_block_device
    Required, string. File path to the ciphertext block device, either the block device itself e. g. /dev/sdb, a partition on the block device e. g. /dev/sdb5 or a regular file e. g. /tmp/ciphertext_file.raw.

    Refer to item.use_uuid when you use a regular file.

use_uuid
    Optional, boolean. Use the UUID of the ciphertext block device in /etc/crypttab instead of the file path given by item.ciphertext_block_device.

    Note that this needs to be set to False if you are using a regular file as item.ciphertext_block_device.

    Default to cryptsetup__use_uuid.

mode
    Optional, string. The mode in which cryptsetup should operate. Supported modes/extensions:

•   plain

•   luks

•   tcrypt

•   veracrypt

    Defaults to luks. There is no global variable to change this default. Refer to cryptsetup(8) <https://manpages.debian.org/cryptsetup(8)> for more details.

offset
    Optional, integer start offset of the ciphertext block device which will be mapped to block 0 of the plaintext device mapper target. This option only has an effect in plain item.mode. There is no offset by default.

crypttab_options
    Optional, list of strings. Each string represents an option to configure for the device in /etc/crypttab. See crypttab(5) <https://manpages.debian.org/crypttab(5)> for details. Default to cryptsetup__crypttab_options.

    Note that cryptsetup options need to be specified using their corresponding parameters as documented in this section. If an option is not documented here, that is where you can use crypttab_options for. For example item.hash could also be specified using hash=sha256 as value for crypttab_options but this is not supported.

keyfile
    Optional, string. File path for the keyfile on the Ansible controller. Will be copied over to the remote system. If it does not exist yet it will be generated using the systems random number generator on the Ansible controller as it is expected that the entropy pool on the Ansible controller is better mixed. Defaults to:

    {{ cryptsetup__secret_path + "/" + item.name + "/keyfile.raw" }}

remote_keyfile
    Optional, string. File path for the keyfile on the remote system. If this option is given it will be used directly and the keyfile will have no effect. It is expected that this file is already present on the remote system. Also note that the remote keyfile is not copied or backed up anywhere. The given file path is just used for opening/mapping the device. This option can also be a device path which will be used by dm-crypt to read the key like /dev/urandom, note however that LUKS requires a persistent key and therefore does not support random data keys. If a state is set which causes the device to become absent, the given remote keyfile will be made absent as well (but only if it is a regular file)! This option does not work with the ansible_controller_mounted state and the role will abort immediately if that combination is used.

keyfile_gen_type
    Optional, string. Type of keyfile to generate. This does not effect already generated keyfiles. Defaults to cryptsetup__keyfile_gen_type.

binary
    A binary keyfile will be generated using dd from the random source specified by cryptsetup__keyfile_source_dev. This should ensure the maximum amount of entropy for keyfiles.

text
    The keyfile will be a random passphrase only consisting of printable characters suitable for automated or by-hand input. item.keyfile_gen_command will be used to output the passphrase.

    Refer to the example for adding another boot disk to a FDE system for how this can be used.

keyfile_gen_command
    Optional, string. The command which should be used to generate the keyfile when item.keyfile_gen_type is set to text. The command is expected to output one line to STDOUT.

    Note that all newline characters (\n) are removed using tr -d '\n' internally so that the generated text key can be entered as regular passphrase. This is required because most CLI programs properly end their output with a newline. But when cryptsetup reads the key from a keyfile (which is what this role always uses internally), it does not terminate input when reading a newline. When reading from STDIN or from a terminal, it does however terminate on the first newline and uses the passphrase with the trailing newline stripped. Refer to cryptsetup(8) <https://manpages.debian.org/cryptsetup(8)> under Notes on passphrase processing for (plain mode|LUKS).

    Defaults to cryptsetup__keyfile_gen_command.

backup_header
    Optional, boolean. Should a header backup be created and stored on the remote system and the Ansible controller?

    NOTE: The LUKS header is only stored once in the first few kilobytes of a given block device. When the header gets corrupted, the plaintext data might be inaccessible! Thus it is recommended to have a header backup on hand.

    Debian buster and newer ship with Cryptsetup >2.0 which defaults to the LUKS2 format that provides redudancy of metadata. For security reasons, there is no redundancy in keyslots binary data (encrypted keys) but the format allows adding such a feature in future. Thus it is still recommended to have a header backup on hand.

    Set to False to disable header backup creation and to ensure that the header backup is absent on the remote system. This option only has an effect in luks item.mode. For TrueCrypt/VeraCrypt you will need to create header backups manually! Defaults to cryptsetup__header_backup.

swap
    Optional, boolean. Should the device be used as encrypted swap space? When set to True, the option item.manage_filesystem is ignored. Refer to debops.sysctl for paging and swapping related kernel settings. Defaults to False.

    Refer to the example for an encrypted swap partition using a random key for how this can be used.

swap_priority
    Optional, integer. Default swap device priority, from -1 to 32767. Higher numbers indicate higher priority. Refer to swapon(8) <https://manpages.debian.org/swapon(8)> for details. Defaults to cryptsetup__swap_priority.

swap_options
    Optional, list of strings. Additional swap "mount" options. Not item.mount_options nor any other global default value is being used for swap options.

manage_filesystem
    Optional, boolean. Should a filesystem be created on the plaintext device mapper target and configured in /etc/fstab? Defaults to True.

create_filesystem
    Optional, boolean. Should a filesystem be created on the plaintext device mapper target? Allows to only disable the creation of the filesystems but still manage an existing filesystem in /etc/fstab when item.manage_filesystem is True. Defaults to item.manage_filesystem.

fstype
    Optional, string. Filesystem to create on the plaintext device mapper target and configure in /etc/fstab. Defaults to cryptsetup__fstype.

mount
    Optional, string. Plaintext mount point of the filesystem. Defaults to:

    {{ cryptsetup__mountpoint_parent_directory + "/" + item.name }}

mount_options
    Optional, list of strings. Mount options associated with the filesystem. For more details see mount(8) <https://manpages.debian.org/mount(8)>. Defaults to cryptsetup__mount_options.

state
    Optional, string. There are four states which can be chosen for each encrypted filesystem. Defaults to cryptsetup__state.

mounted
    Ensure that the encryption and filesystem layer are in place on the block device and the filesystem is mounted.

ansible_controller_mounted
    Same as mounted except that the keyfile is never stored on persistent storage of the remote system. Might be useful when you don’t have a secure place to store the keyfile on the remote system. With this option you will be required to run this role after each reboot to mount the filesystem again.

    Note that the implicit default for crypttab_options and mount_options is auto which means that your init system will try to mount the filesystem on boot and might drop you to a root shell if it can’t.

    To avoid this, you need to set the following options for the item:

    crypttab_options: '{{ ["noauto"] + (cryptsetup__crypttab_options|d([]) | list) }}'
    mount_options: '{{ ["noauto"] + (cryptsetup__mount_options|d([]) | list) }}'

    Note that this option is currently not idempotent because it copes the keyfile to the remote system and erases it again without checking before hand if the plaintext device mapper target is already present.

unmounted
    Ensure that the encryption and filesystem layer are in place on the block device and the filesystem is unmounted. Additionally ensures that the cryptsetup mapping is removed so that no direct access to the plain-text block device is possible.

present
    Ensure that the encryption and filesystem layer are in place on the block device. The plaintext device mapper target will be created and opened as needed during the Ansible run to ensure the filesystem on it is present. When the plaintext device mapper target was not opened prior to the Ansible run, then it will be stopped at the end of the role run again. So basically, this option never changes the mounted/unmounted state of the plaintext device mapper target or the plaintext mount point of the filesystem. Note that this option will not fail when the ciphertext block device is not available during the Ansible run and the keyfile has not been generated by Ansible. This was done to allow to provision remote systems with keys for ciphertext block devices which have been setup previously and are not available during execution of this role.

    Note that if the encrypted filesystem is not mounted when this option is used then this role will not be idempotent because the crypto layer needs to be opened in order to check if the filesystem has been created on top of it.

absent
    Same as unmounted but additionally removes all configuration, the keyfile and the header backup from the remote system.

hash
    Optional, string. Specifies the passphrase hash. For the luks item.mode it specifies the hash used in the LUKS key setup scheme and volume key digest for cryptsetup luksFormat. Defaults to cryptsetup__hash.

cipher
    Optional, string. Cipher specification. Defaults to cryptsetup__cipher.

key_size
    Optional, integer. Key size in bits. Defaults to cryptsetup__key_size.

iter_time
    Optional, int. The number of milliseconds to spend with PBKDF2 passphrase processing. This option only has an effect in luks item.mode. Defaults to cryptsetup__iter_time.

  Example for encrypting a partition

Setup an encrypted filesystem on top of /dev/sdb5 which will be mounted after role execution under /media/sdb5_crypt and will be automatically mounted at boot:

cryptsetup__devices:
  - name: 'sdb5_crypt'
    ciphertext_block_device: '/dev/sdb5'

  Example for an encrypted swap partition using a random key

Setup an encrypted swap partition which uses a new random key picked at each boot. Hibernation won’t work with that as the system won’t have access to the cleartext swap data the next time it starts as a new random key is being used to decrypt/encrypt the device on each boot.

cryptsetup__devices:
  - name: 'rand_key_swap0'
    mode: 'plain'
    swap: True
    remote_keyfile: '/dev/urandom'
    ciphertext_block_device: '/dev/disk/by-partuuid/a7a12244-a4aa-42b7-b605-997165b3fbac'

  Example for an encrypted /tmp using a random key

Setup an encrypted /tmp which uses a new random key picked at each boot. A new filesystem will be created on each boot. By default ext4 will be used.

cryptsetup__devices:
  - name: 'rand_key_tmp'
    mode: 'plain'
    mount: '/tmp'
    remote_keyfile: '/dev/urandom'
    ciphertext_block_device: '/dev/disk/by-partuuid/a7a12244-a4aa-42b7-b605-997165b3fbac'
    create_filesystem: False
    crypttab_options: '{{ ["tmp"] + (cryptsetup__crypttab_options|d([]) | list) }}'
    # crypttab_options: '{{ ["tmp=" + cryptsetup__fstype] + (cryptsetup__crypttab_options|d([]) | list) }}'
    ## This seems to not work with Debian jessie (results in systemd waiting forever for the cleartext target).
    ## Using "tmp" instead worked.

  Example for making a header backup of an existing FDE system

If you installed the OS using FDE and thus the encrypted filesystem was created by the installer you might still want to make a header backup. This can be done by setting remote_keyfile to none so that you will still be asked for the passphrase at boot and to avoid keyfile generation. Additionally manage_filesystem should be set to False so that an existing filesystem is not checked against fstype.

cryptsetup__devices:
  - name: 'vdb3_crypt'
    ciphertext_block_device: '/dev/disk/by-partuuid/55d1da1d-e1b0-4022-b17a-3b73cdc89286'
    manage_filesystem: False
    remote_keyfile: 'none'

  Example for adding another boot disk to a FDE system with a different passphrase for both

In case you installed a FDE system on one disk and want to create a redundant setup afterwards by adding another disk, encrypting it and re-balancing a SOTA <https://en.wikipedia.org/wiki/State_of_the_art> filesystem (Btrfs or ZFS) or growing a legacy RAID setup to it you can follow this example.

For this setup it is required that the added disk can be decrypted in the initramfs to assemble the root filesystem. To make this easier a passphrase will be used as keyfile instead of the default binary keyfile.

Using a passphrase also makes it easier to automate the key input at boot using FDEunlock <https://gitlab.com/ypid/fdeunlock> which is also described in this example. You can ignore/remove the custom keyfile setting if you don’t use FDEunlock <https://gitlab.com/ypid/fdeunlock>.

The keyfile is generated in the keys directory of the default FileVault implementation of FDEunlock <https://gitlab.com/ypid/fdeunlock>. Refer to FDEunlock <https://gitlab.com/ypid/fdeunlock> for details.

inventory_hostname can be used to make the configuration of the keyfile option easier to copy/paste. Note that inventory_hostname is used here because we don’t want to "to rely on the discovered hostname ansible_hostname or for other mysterious reasons" which the (ref: Magic Variables, and How To Access Information About Other Hosts <https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#accessing-information-about-other-hosts-with-magic-variables>). Seems we just found such a "mysterious reason". It is hoped that inventory_hostname is not spoofable because if it where, the role might hand out keys for others hosts to a host exploiting this potential vulnerability. You can set the keyfile manually if you want.

However, there is one issue to note here. The role normally configures devices to unlock them by keyfile or disable keyfile handling completely (when using remote_keyfile). In this example, a combination of both would be nice so that the role creates the crypto layer with the provided keyfile but does not configure it in /etc/crypttab. This is not directly supported and the role can not be extended easily to fully support this because of the internal role design. Changing that is not intended only to support this use case.

Also, this use case requires that the passphrase is never saved anywhere on persistent storage on the remote host.

There is a workaround which meets these requirements by making use of the ansible_controller_mounted state.

You will need two role runs with slightly changed configuration for this. For the first run, use something like this to ensure that the crypto layer is present and opened:

cryptsetup__devices:
  - name: 'sdb4_crypt'
    ciphertext_block_device: '/dev/disk/by-partuuid/3b014afe-1581-11e7-b65d-00163e5e6c0f'
    keyfile_gen_type: 'text'
    manage_filesystem: False
    keyfile: '/home/user/.config/fdeunlock/keys/{{ inventory_hostname }}-initramfs_dev_disk_by-partuuid_3b014afe-1581-11e7-b65d-00163e5e6c0f.key'
    ## Disable for initial setup else enable it:
    # remote_keyfile: 'none'
    ## Enable for initial setup else disable it:
    state: 'ansible_controller_mounted'

Now we will need the role to fix the entry in /etc/crypttab so that the passphrase is asked for on boot:

cryptsetup__devices:
  - name: 'sdb4_crypt'
    ciphertext_block_device: '/dev/disk/by-partuuid/3b014afe-1581-11e7-b65d-00163e5e6c0f'
    keyfile_gen_type: 'text'
    manage_filesystem: False
    keyfile: '/home/user/.config/fdeunlock/keys/{{ inventory_hostname }}-initramfs_dev_disk_by-partuuid_3b014afe-1581-11e7-b65d-00163e5e6c0f.key'
    ## Disable for initial setup else enable it:
    remote_keyfile: 'none'
    ## Enable for initial setup else disable it:
    # state: 'ansible_controller_mounted'

You should now be left with a decrypted sdb4_crypt plaintext device mapper target for which the key only exists in /home/user/.config/fdeunlock/keys/{ inventory_hostname }-initramfs_dev_disk_by-partuuid_3b014afe-1581-11e7-b65d-00163e5e6c0f.key on the Ansible controller.

  Example for adding another boot disk to a FDE system with the same passphrase for both

This section is very similar to the previous example and you are expected to have understood it to not have to repeat everything here. Compared to the previous section which configured two disks for automated decryption using external network tools, this example configures multiple disks for manual passphrase entering by a human. The idea therefore is to use the same passphrase for the disks.

There are two options to provide the passphrase. Either cryptsetup luksFormat the disks manually and then open the crypto layer with the expected name. Alternatively provide the passphrase on the Ansible controller in { cryptsetup__secret_path }/sdX5_crypt_passphrase.txt for example.

If you provided the passphrase on the Ansible controller, you will need the workaround as in the previous example by making use of the ansible_controller_mounted state. The role will need to be run two times with slightly changed configuration. For the first run, use something like this to ensure that the crypto layer is present and opened:

cryptsetup__devices:
  - name: 'sdb4_crypt'
    ciphertext_block_device: '/dev/disk/by-partuuid/6114134e-4796-11ea-8ec1-00163e5e6c00'
    manage_filesystem: False
    keyfile: '{{ cryptsetup__secret_path }}/sdX5_crypt_passphrase.txt'
    ## Disable for initial setup else enable it:
    # remote_keyfile: 'root_fs'
    # crypttab_options: '{{ ["keyscript=decrypt_keyctl"] + (cryptsetup__crypttab_options|d([]) | list) }}'
    ## Enable for initial setup else disable it:
    state: 'ansible_controller_mounted'

Now we will need the role to fix the entry in /etc/crypttab so that the passphrase is asked only once on boot. The keyfile parameter does nothing at this point with remote_keyfile specified so if you don’t want to store the passphrase on the Ansible controller and did cryptsetup luksFormat manually, then feel free to omit keyfile.

cryptsetup__devices:
  - name: 'sdb4_crypt'
    ciphertext_block_device: '/dev/disk/by-partuuid/6114134e-4796-11ea-8ec1-00163e5e6c00'
    manage_filesystem: False
    keyfile: '{{ cryptsetup__secret_path }}/sdX5_crypt_passphrase.txt'
    ## Disable for initial setup else enable it:
    remote_keyfile: 'root_fs'
    crypttab_options: '{{ ["keyscript=decrypt_keyctl"] + (cryptsetup__crypttab_options|d([]) | list) }}'
    ## Enable for initial setup else disable it:
    # state: 'ansible_controller_mounted'

  Example for chaining multiple ciphers

Setup a vault using three different ciphers and three different keys. A similar feature is supported by TrueCrypt/VeraCrypt.

Note that order is important here and that the cryptsetup__devices_execution_strategy option has to be set to serial when using such an example.

cryptsetup__devices_execution_strategy: 'serial'
cryptsetup__devices:
  ## Use AES for the most outer layer to not rise suspicion just yet :)
  - name: 'vault_ciphertext0'
    ciphertext_block_device: '/tmp/ciphertext_vault_file.raw'
    manage_filesystem: False
    # Don’t try to use a UUID for a regular file.
    use_uuid: False
  - name: 'vault_ciphertext1'
    ciphertext_block_device: '/dev/mapper/vault_ciphertext0'
    manage_filesystem: False
    cipher: 'twofish-xts-plain64'
    key_size: 512
  - name: 'vault'
    ciphertext_block_device: '/dev/mapper/vault_ciphertext1'
    cipher: 'serpent-xts-plain64'
    key_size: 512

This will encrypt /tmp/ciphertext_vault_file.raw using the default cipher (cryptsetup__cipher which defaults to AES) and make the "clear text" of that outer layer available under /dev/mapper/vault_ciphertext0. /dev/mapper/vault_ciphertext0 is then en/decrypted using Twofish and the "clear text" of that is mapped to /dev/mapper/vault_ciphertext1. /dev/mapper/vault_ciphertext1 is then en/decrypted using Serpent and mapped to the real clear text block device /dev/mapper/vault on which a filesystem will be created and which will be mounted as usual.

This is surely a more extreme example but it has been tested in a lab environment and the setup seems to work just fine. Also automatic mapping/mounting of all layers works seamlessly on system boot if configured to do so (which is the default).

You can even boot from such a chained number of devices but you might need to manually list the vault_ciphertext device(s) in /etc/initramfs-tools/conf.d/cryptroot. At least on Debian Stretch this is required. mkinitramfs -k -o /tmp/initramfs_tmp and cat /var/tmp/mkinitramfs_$XXXX/conf/conf.d/cryptroot can help you to see if the full chain is known to the initramfs. If so, regenerate the actual initramfs and reboot to test it.

The list of cyphers and key sizes can be checked with cryptsetup benchmark. You can check that the ciphers are chained as expected using cryptsetup status vault, cryptsetup status vault_ciphertext1 and so on.

If you intend to do this then note that in most scenarios the used cipher(s) will not be your weakest link. For example AES should be suitable on it’s own to provide reasonable Information Security <https://en.wikipedia.org/wiki/Information_security>. You must also think about other areas of Computer Security <https://en.wikipedia.org/wiki/Computer_security> and Operations security <https://en.wikipedia.org/wiki/Operations_security> for this example to make sense.

  Example for TrueCrypt/VeraCrypt encrypted devices

cryptsetup supports to open TrueCrypt ciphertext block devices and starting with cryptsetup version 1.6.7 also VeraCrypt. As TrueCrypt has been superseded by VeraCrypt, only the later one will be mentioned in this section from now on.

Because VeraCrypt is uncommon in a purely GNU/Linux based environment and is not packaged for Debian, this role does not interact in any way with VeraCrypt. You don’t need to install it on hosts you run this role against.

You will need to use VeraCrypt for creation as cryptsetup and this role do not support this. Note that currently only a passphrase is supported which can be passed in the usual manner by writing it into the keyfile on the Ansible controller. The keyfile should not contain newline characters (\n), see item.keyfile_gen_command. Note that you will need to create a header backup manually!

Because VeraCrypt is great for platform portability, you might choose a different filesystem as done in this example:

cryptsetup__devices:
  - name: 'mydatadisk'
    ciphertext_block_device: '/dev/disk/by-partuuid/65ca7bc4-6cb7-11e7-b49b-00163e5e6c0f'
    mode: 'veracrypt'
    fstype: 'ntfs'
    create_filesystem: False
    mount_options: '{{ cryptsetup__mount_options + ["umask=027", "fmask=117", "uid=1000", "gid=1000"] }}'

AUTHOR
Robin Schneider

COPYRIGHT
2014-2022, Maciej Delmanowski, Nick Janetakis, Robin Schneider and others