The Ansible Role For LUKS VM template

The Ansible Role For LUKS VM template

This is the role that does the actions I described previously. I decided to break it out to its own doc to make it easier for someone to skip if they don't care about the ansible part.

tasks/main.yml

First we will install programs we will need

- name: Update apt, and install basic tools needed for clevis
  apt:
    name: "{{ packages }}"
    state: present
  vars:
    packages:
      - clevis
      - clevis-initramfs
      - clevis-luks
      - clevis-tpm2
      - clevis-systemd

Then figure out the device that / is mounted from. So root_dev can be used as a variable later to refer to this device. This will look like /dev/mapper/root-crypt

- name: mount device name
  ansible.builtin.set_fact:
    root_dev: "{{ ansible_mounts | json_query('[?mount == `/`].device') | json_query('[0]') | string }}"

Set crypt_vol_name variable to just the volume name part (the root-crypt part of /dev/mapper/root-crypt).

- name: find the root device
  shell: echo "{{ root_dev }}" | grep  -o '[^/]*$'
  register: crypt_vol_name

If the / is not mounted from something in /dev/mapper, then you are probably running this role against a machine that doesn't have the root filesystem encrypted. If that's the case, we can just skip the rest of this. I'm sure there's a better way to exit early, but I haven't found it yet.

- name: Skip this role on this host if root isn't mounted from /dev/mapper dev
  ansible.builtin.set_fact:
    using_mapper: no
  when: "not 'dev/mapper' in ansible_mounts|json_query('[?mount == `/`].device')|string"

- name: Coninue this role on this host if root is mounted from /dev/mapper dev
  ansible.builtin.set_fact:
    using_mapper: yes
  when: "'dev/mapper' in ansible_mounts|json_query('[?mount == `/`].device')|string"

If the root is mounted from a device in /dev/mapper, go on to do the rest of this. Otherwise just skip to the end of this file.

- block:
  - name: get crypt volume parent partition
    shell: dmsetup deps {{ root_dev }} -o devname | sed 's/.*(\(.*\))/\1/g'
    register: crypt_partition

  - name: Does the initial_password file still exist?
    stat:
      path: /boot/initial_password
    register: file_data

  - name: Run the clevis_setup test if initial password still exists
    include: clevis_setup.yml
    when: file_data.stat.exists
  when: "using_mapper == true"

tasks/clevis_setup.yml

Find out what device contains the LUKS volume (this should look like /dev/sda3).

- name: get crypt volume parent partition
  shell: dmsetup deps {{ root_dev }} -o devname | sed 's/.*(\(.*\))/\1/g'
  register: crypt_partition

Verify that the fallback password is set. This should be a variable set in the inventory. It should be encrypted with ansible vault. If the variable is not set, or wasn't read because we didn't include the --ask-vault-pass switch, end running the playbook.

# Warn, if the fallback password is still the default.  It should be a 
# vault encrypted variable, so in addition to  testing if it wasn't set, 
# this will cause an early fail if the vault password wasn't provided, 
# saving us from doing half the work and then failing.

- block:
  - name: check if fallback password is the default
    debug:
      msg: |
        Warning, using the default fallback password.
        That's fine for testing, but don't use it for actual
        important machines.

  - meta: end_host
  when: luks_fallback_password == "password"

Do the re-encrypt so this VM doesn't use the same key as the template or any other VM.

- name: Re-encrypt to get new master key (will take a few minutes)
  shell: cryptsetup reencrypt /dev/{{ crypt_partition.stdout }} -d /boot/initial_password 

I put my tang servers on the IP ending in 254 in each of my subnets. This sets that IP as the variable tang_ip

- name: figure out tang server
  shell: echo "{{ ansible_default_ipv4.network }}" | sed 's/0$/254/g'
  register: tang_ip

Add tang server as a clevis pin that can unlock LUKS.

- name: Add tang key
  shell: timeout 60 clevis luks bind -d /dev/{{ crypt_partition.stdout }} -s8 -k /boot/initial_password -y tang '{"url":"http://{{ tang_ip.stdout }}"}'
  # don't try to use tang server to unlock its own luks
  when: tang_ip.stdout != ansible_host
  

Add the fallback password to LUKS.

- name: Change password
  community.crypto.luks_device:
    device: "/dev/{{ crypt_partition.stdout }}"
    keyfile: /boot/initial_password
    new_passphrase: "{{ luks_fallback_password }}"
    pbkdf:
      iteration_time: 8

Remove the old initial_password file as a password from LUKS, and delete the file.

- name: Remove old initial password
  community.crypto.luks_device:
    device: "/dev/{{ crypt_partition.stdout }}"
    remove_keyfile: /boot/initial_password
  
- name: Remove the initial password file
  ansible.builtin.file:
    path: /boot/initial_password
    state: absent

Change crypttab to not expect to find the password in a file anymore.

- name: Modify crypttab
  ansible.builtin.lineinfile:
    path: /etc/crypttab
    backrefs: yes
    line: '\1 none \2'
    regexp: '^({{ crypt_vol_name.stdout }}.*)/boot/initial_password(.*)$'

Change the initrd config to not include the initial-password file anymore, and then rebuild the initrd.

- name: No need to include the initial-password in initramfs anymore
  lineinfile:
    state: absent
    path: /etc/cryptsetup-initramfs/conf-hook
    regexp: "KEYFILE_PATTERN=/boot/initial_password"
    
- name: update initramfs
  shell: update-initramfs -u -k all ; sync

- name: update grub
  shell: update-grub2

Reboot so if there were any problems we find out before wasting any more time on the setup.

- name: Reboot to verify this all worked
  reboot:

And that's it. With this every server can have encrypted disks without it being a big hassle. .

Last updated