Install Hashicorp Vault on Red Hat Enterprise Linux

I use Hashicorp Vault as my primary secrets management system. Mostly because most of my on-premise OpenShift customers use it as well. Here’s how to install it on a RHEL 9.6 box using ansible.

One of the prerequisites is to get certbot installed on the host and configured to use DNS challenges. Why DNS challenges? Because I don’t like port 80 being open. I find the DNS challenge more palatable. Here’s the tasks for setting it up.

---
- name: Install required packages for certbot
  ansible.builtin.package:
    name: "{{ item }}"
    state: present
  loop:
    - certbot
    - python3-certbot-dns-cloudflare

- name: Create dns credentials file
  ansible.builtin.copy:
    dest: /etc/letsencrypt/cloudflare.ini
    content: |
      dns_cloudflare_api_token = {{ dns_cloudflare_api_token }}
    mode: '0600'
  when: dns_cloudflare_api_token is defined

Here’s the playbook for the vault install.

---
- name: Install and configure HashiCorp Vault with Let's Encrypt certs
  hosts: vault
  become: true
  vars:
    vault_domain: vault.lab.snimmo.com
    certbot_credentials: /etc/letsencrypt/cloudflare.ini
    vault_cert_dir: /etc/vault/certs
    deploy_hook_script: /usr/local/bin/deploy-vault-certs.sh

  tasks:

    - name: Add HashiCorp repository
      ansible.builtin.command: >
        dnf config-manager --add-repo https://rpm.releases.hashicorp.com/RHEL/hashicorp.repo
      args:
        creates: /etc/yum.repos.d/hashicorp.repo

    - name: Install Vault
      ansible.builtin.package:
        name: vault
        state: present

    - name: Request cert using certbot and Cloudflare DNS # noqa no-changed-when
      ansible.builtin.command: >
        certbot certonly --non-interactive --preferred-challenges dns-01
        --dns-cloudflare
        --dns-cloudflare-credentials {{ certbot_credentials }}
        --register-unsafely-without-email --agree-tos
        -d {{ vault_domain }}

    - name: Create Vault certs directory
      ansible.builtin.file:
        path: "{{ vault_cert_dir }}"
        state: directory
        owner: vault
        group: vault
        mode: '0750'

    - name: Create deploy hook script for Vault
      ansible.builtin.copy:
        dest: "{{ deploy_hook_script }}"
        mode: '0750'
        owner: root
        group: root
        content: |
          #!/bin/bash
          cp /etc/letsencrypt/live/{{ vault_domain }}/fullchain.pem {{ vault_cert_dir }}/
          cp /etc/letsencrypt/live/{{ vault_domain }}/privkey.pem {{ vault_cert_dir }}/
          chown vault:vault {{ vault_cert_dir }}/*
          chmod 600 {{ vault_cert_dir }}/*
          systemctl restart vault

    - name: Ensure Vault service is started and enabled
      ansible.builtin.service:
        name: vault
        state: started
        enabled: true

    - name: Create vault config
      ansible.builtin.template:
        src: vault.hcl.j2
        dest: /etc/vault.d/vault.hcl
        mode: '0644'
      notify: restart_vault

    - name: Allow Vault ports on firewall
      ansible.posix.firewalld:
        zone: public
        port: "{{ item }}"
        state: enabled
        permanent: true
        immediate: true
      loop:
        - 8200/tcp
        - 8201/tcp

    - name: Create systemd service for certbot renew
      ansible.builtin.copy:
        dest: /etc/systemd/system/vault-certbot-renew.service
        owner: root
        group: root
        mode: '0644'
        content: |
          [Unit]
          Description=Vault Certbot Renewal with Deploy Hook
          Wants=network-online.target
          After=network-online.target

          [Service]
          Type=oneshot
          ExecStart=/usr/bin/certbot renew --cert-name {{ vault_domain }} --deploy-hook '{{ deploy_hook_script }}'
      notify: reload_systemd_daemon

    - name: Create systemd timer for certbot renew
      ansible.builtin.copy:
        dest: /etc/systemd/system/vault-certbot-renew.timer
        owner: root
        group: root
        mode: '0644'
        content: |
          [Unit]
          Description=Timer for Certbot Renewal for Vault

          [Timer]
          OnCalendar=daily
          Persistent=true

          [Install]
          WantedBy=timers.target
      notify: reload_systemd_daemon

    - name: Enable and start certbot-renew.timer
      ansible.builtin.systemd:
        name: vault-certbot-renew.timer
        enabled: true
        state: started

  handlers:
    - name: reload_systemd_daemon
      ansible.builtin.command: systemctl daemon-reexec

    - name: restart_vault
      ansible.builtin.service:
        name: vault
        state: restarted