# On RHEL 7 FIPS is enabled as follows:
#
# 1. Install dracut-fips and grubby.
# 2. Add fips=1 and boot= (if needed) to bootloader kernel arguments
# 3. Reboot.
#
# https://access.redhat.com/solutions/137833
#
# On RHEL/CentosStream 8 and 9 FIPS is enabled as follows:
#
# 1. Install crypto-policies-scripts, dracut-fips and grubby
# 2. Execute fips-mode-setup --enable
# 3. Reboot
#
# https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/security_hardening/switching-rhel-to-fips-mode_security-hardening
#
# On RHEL/CentosStream 10 FIPS is enabled as follows:
#
# 1. Install grubby
# 2. Add fips=1 and boot= (if needed) to bootloader kernel arguments [*]
# 3. Reboot
#
# [*] fips-mode-setup is not longer available on RHEL/CentosStream 10
#
# https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/10-beta/html/security_hardening/switching-rhel-to-fips-mode
---

# Pre-setup to install Python 3.9 on RHEL/CentOS 8 systems
# This is needed because Ansible 2.17+ requires Python 3.7+ but RHEL/CentOS 8
# ships with Python 3.6, causing "future feature annotations is not defined" errors
- name: Workaround Ansible 2.17+ not supporting RHEL/CentOS 8 systems
  hosts: all
  gather_facts: false
  tasks:
    - name: Check OS release version
      ansible.builtin.raw: cat /etc/redhat-release
      register: release_out
      changed_when: false

    - name: Install Python 3.9 on RHEL/CentOS 8 systems
      ansible.builtin.raw: |
        dnf -y module enable python39
        dnf -y install python39
      args:
        executable: /bin/bash
      when: |
        'Red Hat Enterprise Linux release 8.' in release_out.stdout
        or 'CentOS Linux release 8' in release_out.stdout
        or 'CentOS Stream release 8' in release_out.stdout
      changed_when: false

- name: Enable FIPS mode on RHEL 7 or RHEL/CentosStream  8, 9 or 10
  hosts: all

  tasks:

    - name: Disable prelink
      ansible.builtin.shell: |
        if rpm -q prelink; then
          sed -i '/^PRELINKING/s,yes,no,' /etc/sysconfig/prelink
          prelink -uav
        fi
      register: output
      changed_when: '"package prelink is not installed" not in output.stdout'

    - name: Install dracut-fips and grubby on RHEL7
      ansible.builtin.command: yum install -y dracut-fips grubby # noqa: command-instead-of-module
      register: output
      changed_when: "output.rc == 0 and 'Nothing to do' not in output.stdout"
      when: ansible_facts["distribution_major_version"] | int == 7

    - name: Install crypto-policies-scripts, dracut-fips and grubby on RHEL8
      # noqa: command-instead-of-module
      ansible.builtin.command: dnf install -y crypto-policies-scripts dracut-fips grubby
      register: output
      changed_when: "output.rc == 0 and 'Nothing to do' not in output.stdout"
      when: ansible_facts["distribution_major_version"] | int == 8

    - name: Install crypto-policies-scripts, dracut-fips and grubby on RHEL9
      ansible.builtin.dnf:
        name:
          - crypto-policies-scripts
          - dracut-fips
          - grubby
        state: present
      when: ansible_facts["distribution_major_version"] | int == 9

    - name: Install grubby
      ansible.builtin.dnf:
        name: grubby
        state: present
      when: ansible_facts["distribution_major_version"] | int == 10

    - name: Modify bootloader
      block:

        - name: Add parameters fips and boot (if applicable)
          ansible.builtin.shell: |
            boot_device="$(stat -c %d:%m /boot)"
            root_device="$(stat -c %d:%m /)"
            boot_device_opt=""
            if [ "$boot_device" != "$root_device" ]; then
              # Trigger autofs if boot is mounted by automount.boot.
              pushd /boot >/dev/null 2>&1 && popd
              FINDMNT_UUID="findmnt --first-only -t noautofs --noheadings --output uuid"
              boot_uuid=$(
                $FINDMNT_UUID --mountpoint /boot --fstab ||
                $FINDMNT_UUID /boot --fstab ||
                $FINDMNT_UUID --mountpoint /boot ||
                $FINDMNT_UUID /boot
              )
              boot_device_opt=" boot=UUID=$boot_uuid"
            fi
            grubby --update-kernel=ALL --args="fips=1 $boot_device_opt"
          register: output
          changed_when: output.rc == 0

        - name: Execute zipl
          ansible.builtin.command: zipl
          register: output
          changed_when: output.rc == 0
          when: ansible_facts["architecture"] == "s390x"

    - name: Enable FIPS Policy
      ansible.builtin.command: update-crypto-policies --set FIPS
      register: output
      changed_when: "'is not sufficient for' in output.stderr"
      when: ansible_facts["distribution_major_version"] | int == 10

    - name: Enable FIPS mode
      ansible.builtin.command: fips-mode-setup --enable --no-bootcfg
      environment:
        FIPS_MODE_SETUP_SKIP_WARNING: "1"
      register: output
      changed_when: output.rc == 0
      when: ansible_facts["distribution_major_version"] | int in [8, 9]

    - name: Reboot
      ansible.builtin.reboot:

    - name: Kernel is running in FIPS mode
      ansible.builtin.command: grep 1 /proc/sys/crypto/fips_enabled
      changed_when: false

    - name: Userspace is running in FIPS mode
      ansible.builtin.command: test -e /etc/system-fips
      changed_when: false
      when: ansible_facts["distribution_major_version"] | int < 10

    - name: FIPS mode is reported by fips-mode-setup
      ansible.builtin.command: fips-mode-setup --is-enabled
      changed_when: false
      when: ansible_facts["distribution_major_version"] | int in [8, 9]

    - name: Check that FIPS policy is enabled
      ansible.builtin.shell: test "$(update-crypto-policies --show)" == "FIPS"
      changed_when: false
      when: ansible_facts["distribution_major_version"] | int > 7
