diff --git a/molecule/vault_default/converge.yml b/molecule/vault_default/converge.yml new file mode 100644 index 0000000..1e2f0f3 --- /dev/null +++ b/molecule/vault_default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.vault" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.vault" diff --git a/molecule/vault_default/molecule.yml b/molecule/vault_default/molecule.yml new file mode 100644 index 0000000..f187a1d --- /dev/null +++ b/molecule/vault_default/molecule.yml @@ -0,0 +1,37 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: docker +platforms: + - name: instance + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: vault_default + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/vault_default/prepare.yml b/molecule/vault_default/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/vault_default/prepare.yml @@ -0,0 +1,13 @@ +--- +- name: Prepare + hosts: all + become: true + tasks: + - name: "Install pre-required system packages" + ansible.builtin.include_role: + name: ednz_cloud.manage_apt_packages + vars: + manage_apt_packages_list: + - name: unzip + version: latest + state: present diff --git a/molecule/vault_default/requirements.yml b/molecule/vault_default/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/vault_default/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/vault_default/verify.yml b/molecule/vault_default/verify.yml new file mode 100644 index 0000000..6109f93 --- /dev/null +++ b/molecule/vault_default/verify.yml @@ -0,0 +1,185 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: vault user and group" + block: + - name: "Getent user vault" + ansible.builtin.getent: + database: passwd + key: vault + register: vault_user + + - name: "Getent group vault" + ansible.builtin.getent: + database: group + key: vault + register: vault_group + + - name: "Verify vault user and group" + ansible.builtin.assert: + that: + - not vault_user.failed + - not vault_group.failed + - "'vault' in vault_user.ansible_facts.getent_passwd.keys()" + - "'/home/vault' in vault_user.ansible_facts.getent_passwd['vault']" + - "'/bin/false' in vault_user.ansible_facts.getent_passwd['vault']" + - "'vault' in vault_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/vault" + block: + - name: "Stat binary /usr/local/bin/vault" + ansible.builtin.stat: + path: "/usr/local/bin/vault" + register: stat_usr_local_bin_vault + + - name: "Verify binary /usr/local/bin/vault" + ansible.builtin.assert: + that: + - stat_usr_local_bin_vault.stat.exists + - stat_usr_local_bin_vault.stat.isreg + - stat_usr_local_bin_vault.stat.pw_name == 'root' + - stat_usr_local_bin_vault.stat.gr_name == 'root' + - stat_usr_local_bin_vault.stat.mode == '0755' + + - name: "Test: directory /etc/vault.d" + block: + - name: "Stat directory /etc/vault.d" + ansible.builtin.stat: + path: "/etc/vault.d" + register: stat_etc_vault_d + + - name: "Stat file /etc/vault.d/vault.env" + ansible.builtin.stat: + path: "/etc/vault.d/vault.env" + register: stat_etc_vault_d_vault_env + + - name: "Stat file /etc/vault.d/vault.json" + ansible.builtin.stat: + path: "/etc/vault.d/vault.json" + register: stat_etc_vault_d_vault_json + + - name: "Slurp file /etc/vault.d/vault.json" + ansible.builtin.slurp: + src: "/etc/vault.d/vault.json" + register: slurp_etc_vault_d_vault_json + + - name: "Verify directory /etc/vault.d" + ansible.builtin.assert: + that: + - stat_etc_vault_d.stat.exists + - stat_etc_vault_d.stat.isdir + - stat_etc_vault_d.stat.pw_name == 'vault' + - stat_etc_vault_d.stat.gr_name == 'vault' + - stat_etc_vault_d.stat.mode == '0755' + - stat_etc_vault_d_vault_env.stat.exists + - stat_etc_vault_d_vault_env.stat.isreg + - stat_etc_vault_d_vault_env.stat.pw_name == 'vault' + - stat_etc_vault_d_vault_env.stat.gr_name == 'vault' + - stat_etc_vault_d_vault_env.stat.mode == '0600' + - stat_etc_vault_d_vault_json.stat.exists + - stat_etc_vault_d_vault_json.stat.isreg + - stat_etc_vault_d_vault_json.stat.pw_name == 'vault' + - stat_etc_vault_d_vault_json.stat.gr_name == 'vault' + - stat_etc_vault_d_vault_json.stat.mode == '0600' + - slurp_etc_vault_d_vault_json.content != '' + + - name: "Test: directory /opt/vault" + block: + - name: "Stat directory /opt/vault" + ansible.builtin.stat: + path: "/opt/vault" + register: stat_opt_vault + + - name: "Verify directory /opt/vault" + ansible.builtin.assert: + that: + - stat_opt_vault.stat.exists + - stat_opt_vault.stat.isdir + - stat_opt_vault.stat.pw_name == 'vault' + - stat_opt_vault.stat.gr_name == 'vault' + - stat_opt_vault.stat.mode == '0755' + + - name: "Test: service vault" + block: + - name: "Get service vault" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/vault.service" + ansible.builtin.stat: + path: "/etc/systemd/system/vault.service" + register: stat_etc_systemd_system_vault_service + + - name: "Slurp file /etc/systemd/system/vault.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/vault.service" + register: slurp_etc_systemd_system_vault_service + + - name: "Verify service vault" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_vault_service.stat.exists + - stat_etc_systemd_system_vault_service.stat.isreg + - stat_etc_systemd_system_vault_service.stat.pw_name == 'root' + - stat_etc_systemd_system_vault_service.stat.gr_name == 'root' + - stat_etc_systemd_system_vault_service.stat.mode == '0644' + - slurp_etc_systemd_system_vault_service.content != '' + - ansible_facts.services['vault.service'] is defined + - ansible_facts.services['vault.service']['source'] == 'systemd' + - ansible_facts.services['vault.service']['state'] == 'running' + - ansible_facts.services['vault.service']['status'] == 'enabled' + + - name: "Test: bootstrap vault cluster" + block: + - name: "Command vault operator init" + ansible.builtin.command: "vault operator init -non-interactive -key-shares=3 -key-threshold=2 -format=json" + environment: + VAULT_ADDR: http://{{ ansible_default_ipv4.address }}:8200 + changed_when: false + register: vault_operator_init + + - name: "Test: unseal vault cluster" + vars: + vault_unseal_keys: "{{ vault_operator_init.stdout|from_json|json_query('unseal_keys_hex') }}" + block: + - name: "Command vault operator unseal" + ansible.builtin.command: "vault operator unseal -format=json {{ vault_unseal_keys[0] }}" + environment: + VAULT_ADDR: http://{{ ansible_default_ipv4.address }}:8200 + changed_when: false + register: vault_operator_unseal_0 + + - name: "Command vault operator unseal" + ansible.builtin.command: "vault operator unseal -format=json {{ vault_unseal_keys[1] }}" + environment: + VAULT_ADDR: http://{{ ansible_default_ipv4.address }}:8200 + changed_when: false + register: vault_operator_unseal_1 + + - name: "Verify vault operator unseal" + vars: + vault_seal_state_0: "{{ vault_operator_unseal_0.stdout|from_json|json_query('sealed') }}" + vault_seal_state_1: "{{ vault_operator_unseal_1.stdout|from_json|json_query('sealed') }}" + ansible.builtin.assert: + that: + - vault_seal_state_0 + - not vault_seal_state_1 + + - name: "Test: vault interaction" + vars: + root_token: "{{ vault_operator_init.stdout|from_json|json_query('root_token') }}" + block: + - name: "Command vault secret enable" + ansible.builtin.command: "vault secrets enable -version=1 kv" + environment: + VAULT_ADDR: http://{{ ansible_default_ipv4.address }}:8200 + VAULT_TOKEN: "{{ root_token }}" + changed_when: false + register: vault_secret_enable + + - name: "Verify vault interaction" + ansible.builtin.assert: + that: + - vault_secret_enable.stdout == 'Success! Enabled the kv secrets engine at: kv/' diff --git a/molecule/vault_with_raft_enabled/converge.yml b/molecule/vault_with_raft_enabled/converge.yml new file mode 100644 index 0000000..1e2f0f3 --- /dev/null +++ b/molecule/vault_with_raft_enabled/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.vault" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.vault" diff --git a/molecule/vault_with_raft_enabled/group_vars/all.yml b/molecule/vault_with_raft_enabled/group_vars/all.yml new file mode 100644 index 0000000..a879ae5 --- /dev/null +++ b/molecule/vault_with_raft_enabled/group_vars/all.yml @@ -0,0 +1,13 @@ +--- +# defaults file for hashicorp_vault + +######################### +# storage configuration # +######################### + +vault_storage_configuration: + raft: + path: "{{ vault_data_dir }}" + node_id: "{{ ansible_hostname }}" + retry_join: + - leader_api_addr: "http://{{ vault_cluster_addr }}:8200" diff --git a/molecule/vault_with_raft_enabled/molecule.yml b/molecule/vault_with_raft_enabled/molecule.yml new file mode 100644 index 0000000..d75494a --- /dev/null +++ b/molecule/vault_with_raft_enabled/molecule.yml @@ -0,0 +1,37 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: docker +platforms: + - name: instance + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: vault_with_raft_enabled + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/vault_with_raft_enabled/prepare.yml b/molecule/vault_with_raft_enabled/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/vault_with_raft_enabled/prepare.yml @@ -0,0 +1,13 @@ +--- +- name: Prepare + hosts: all + become: true + tasks: + - name: "Install pre-required system packages" + ansible.builtin.include_role: + name: ednz_cloud.manage_apt_packages + vars: + manage_apt_packages_list: + - name: unzip + version: latest + state: present diff --git a/molecule/vault_with_raft_enabled/requirements.yml b/molecule/vault_with_raft_enabled/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/vault_with_raft_enabled/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/vault_with_raft_enabled/verify.yml b/molecule/vault_with_raft_enabled/verify.yml new file mode 100644 index 0000000..6c2773f --- /dev/null +++ b/molecule/vault_with_raft_enabled/verify.yml @@ -0,0 +1,185 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: vault user and group" + block: + - name: "Getent user vault" + ansible.builtin.getent: + database: passwd + key: vault + register: vault_user + + - name: "Getent group vault" + ansible.builtin.getent: + database: group + key: vault + register: vault_group + + - name: "Verify vault user and group" + ansible.builtin.assert: + that: + - not vault_user.failed + - not vault_group.failed + - "'vault' in vault_user.ansible_facts.getent_passwd.keys()" + - "'/home/vault' in vault_user.ansible_facts.getent_passwd['vault']" + - "'/bin/false' in vault_user.ansible_facts.getent_passwd['vault']" + - "'vault' in vault_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/vault" + block: + - name: "Stat binary /usr/local/bin/vault" + ansible.builtin.stat: + path: "/usr/local/bin/vault" + register: stat_usr_local_bin_vault + + - name: "Verify binary /usr/local/bin/vault" + ansible.builtin.assert: + that: + - stat_usr_local_bin_vault.stat.exists + - stat_usr_local_bin_vault.stat.isreg + - stat_usr_local_bin_vault.stat.pw_name == 'root' + - stat_usr_local_bin_vault.stat.gr_name == 'root' + - stat_usr_local_bin_vault.stat.mode == '0755' + + - name: "Test: directory /etc/vault.d" + block: + - name: "Stat directory /etc/vault.d" + ansible.builtin.stat: + path: "/etc/vault.d" + register: stat_etc_vault_d + + - name: "Stat file /etc/vault.d/vault.env" + ansible.builtin.stat: + path: "/etc/vault.d/vault.env" + register: stat_etc_vault_d_vault_env + + - name: "Stat file /etc/vault.d/vault.json" + ansible.builtin.stat: + path: "/etc/vault.d/vault.json" + register: stat_etc_vault_d_vault_json + + - name: "Slurp file /etc/vault.d/vault.json" + ansible.builtin.slurp: + src: "/etc/vault.d/vault.json" + register: slurp_etc_vault_d_vault_json + + - name: "Verify directory /etc/vault.d" + ansible.builtin.assert: + that: + - stat_etc_vault_d.stat.exists + - stat_etc_vault_d.stat.isdir + - stat_etc_vault_d.stat.pw_name == 'vault' + - stat_etc_vault_d.stat.gr_name == 'vault' + - stat_etc_vault_d.stat.mode == '0755' + - stat_etc_vault_d_vault_env.stat.exists + - stat_etc_vault_d_vault_env.stat.isreg + - stat_etc_vault_d_vault_env.stat.pw_name == 'vault' + - stat_etc_vault_d_vault_env.stat.gr_name == 'vault' + - stat_etc_vault_d_vault_env.stat.mode == '0600' + - stat_etc_vault_d_vault_json.stat.exists + - stat_etc_vault_d_vault_json.stat.isreg + - stat_etc_vault_d_vault_json.stat.pw_name == 'vault' + - stat_etc_vault_d_vault_json.stat.gr_name == 'vault' + - stat_etc_vault_d_vault_json.stat.mode == '0600' + - slurp_etc_vault_d_vault_json.content != '' + + - name: "Test: directory /opt/vault" + block: + - name: "Stat directory /opt/vault" + ansible.builtin.stat: + path: "/opt/vault" + register: stat_opt_vault + + - name: "Verify directory /opt/vault" + ansible.builtin.assert: + that: + - stat_opt_vault.stat.exists + - stat_opt_vault.stat.isdir + - stat_opt_vault.stat.pw_name == 'vault' + - stat_opt_vault.stat.gr_name == 'vault' + - stat_opt_vault.stat.mode == '0755' + + - name: "Test: service vault" + block: + - name: "Get service vault" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/vault.service" + ansible.builtin.stat: + path: "/etc/systemd/system/vault.service" + register: stat_etc_systemd_system_vault_service + + - name: "Slurp file /etc/systemd/system/vault.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/vault.service" + register: slurp_etc_systemd_system_vault_service + + - name: "Verify service vault" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_vault_service.stat.exists + - stat_etc_systemd_system_vault_service.stat.isreg + - stat_etc_systemd_system_vault_service.stat.pw_name == 'root' + - stat_etc_systemd_system_vault_service.stat.gr_name == 'root' + - stat_etc_systemd_system_vault_service.stat.mode == '0644' + - slurp_etc_systemd_system_vault_service.content != '' + - ansible_facts.services['vault.service'] is defined + - ansible_facts.services['vault.service']['source'] == 'systemd' + - ansible_facts.services['vault.service']['state'] == 'running' + - ansible_facts.services['vault.service']['status'] == 'enabled' + + - name: "Test: bootstrap vault cluster" + block: + - name: "Command vault operator init" + ansible.builtin.command: "vault operator init -non-interactive -key-shares=3 -key-threshold=2 -tls-skip-verify -format=json" + environment: + VAULT_ADDR: "http://{{ ansible_default_ipv4.address }}:8200" + changed_when: false + register: vault_operator_init + + - name: "Test: unseal vault cluster" + vars: + vault_unseal_keys: "{{ vault_operator_init.stdout|from_json|json_query('unseal_keys_hex') }}" + block: + - name: "Command vault operator unseal" + ansible.builtin.command: "vault operator unseal -format=json -tls-skip-verify {{ vault_unseal_keys[0] }}" + environment: + VAULT_ADDR: "http://{{ ansible_default_ipv4.address }}:8200" + changed_when: false + register: vault_operator_unseal_0 + + - name: "Command vault operator unseal" + ansible.builtin.command: "vault operator unseal -format=json -tls-skip-verify {{ vault_unseal_keys[1] }}" + environment: + VAULT_ADDR: "http://{{ ansible_default_ipv4.address }}:8200" + changed_when: false + register: vault_operator_unseal_1 + + - name: "Verify vault operator unseal" + vars: + vault_seal_state_0: "{{ vault_operator_unseal_0.stdout|from_json|json_query('sealed') }}" + vault_seal_state_1: "{{ vault_operator_unseal_1.stdout|from_json|json_query('sealed') }}" + ansible.builtin.assert: + that: + - vault_seal_state_0 + - not vault_seal_state_1 + + - name: "Test: vault interaction" + vars: + root_token: "{{ vault_operator_init.stdout|from_json|json_query('root_token') }}" + block: + - name: "Command vault secret enable" + ansible.builtin.command: "vault secrets enable -version=1 -tls-skip-verify kv" + environment: + VAULT_ADDR: "http://{{ ansible_default_ipv4.address }}:8200" + VAULT_TOKEN: "{{ root_token }}" + changed_when: false + register: vault_secret_enable + + - name: "Verify vault interaction" + ansible.builtin.assert: + that: + - vault_secret_enable.stdout == 'Success! Enabled the kv secrets engine at: kv/' diff --git a/roles/vault/defaults/main.yml b/roles/vault/defaults/main.yml new file mode 100644 index 0000000..d1fb237 --- /dev/null +++ b/roles/vault/defaults/main.yml @@ -0,0 +1,96 @@ +--- +# defaults file for hashicorp_vault +vault_version: latest +vault_start_service: true +vault_config_dir: "/etc/vault.d" +vault_data_dir: "/opt/vault" +vault_certs_dir: "{{ vault_config_dir }}/tls" +vault_logs_dir: "/var/log/vault" + +vault_extra_files: false +vault_extra_files_list: [] + +vault_env_variables: {} + +####################### +# extra configuration # +####################### + +# You should prioritize adding configuration +# to the configuration entries below, this +# option should be used to add pieces of configuration not +# available through standard variables. + +vault_extra_configuration: {} + +########### +# general # +########### + +vault_cluster_name: vault +vault_bind_addr: "0.0.0.0" +vault_cluster_addr: "{{ ansible_default_ipv4.address }}" +vault_enable_ui: true +vault_disable_mlock: false +vault_disable_cache: false + +######################### +# storage configuration # +######################### + +vault_storage_configuration: + file: + path: "{{ vault_data_dir }}" + +########################## +# listener configuration # +########################## + +vault_enable_tls: false +vault_listener_configuration: + tcp: + address: "{{ vault_cluster_addr }}:8200" + tls_disable: true + +vault_tls_listener_configuration: + tcp: + tls_disable: false + tls_cert_file: "{{ vault_certs_dir }}/cert.pem" + tls_key_file: "{{ vault_certs_dir }}/key.pem" + tls_disable_client_certs: true + +vault_certificates_extra_files_dir: + [] + # - src: "" + # dest: "{{ vault_certs_dir }}" + +vault_extra_listener_configuration: {} + +######################## +# service registration # +######################## + +vault_enable_service_registration: false +vault_service_registration_configuration: + consul: + address: "127.0.0.1:8500" + scheme: "http" + token: "" + +######################### +# plugins configuration # +######################### + +vault_enable_plugins: false +vault_plugins_directory: "{{ vault_config_dir }}/plugins" + +################# +# vault logging # +################# + +vault_log_level: info +vault_enable_log_to_file: false +vault_log_to_file_configuration: + log_file: "{{ vault_logs_dir }}/vault.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 diff --git a/roles/vault/handlers/main.yml b/roles/vault/handlers/main.yml new file mode 100644 index 0000000..72dc0f5 --- /dev/null +++ b/roles/vault/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashicorp_vault diff --git a/roles/vault/meta/main.yml b/roles/vault/meta/main.yml new file mode 100644 index 0000000..9b856ed --- /dev/null +++ b/roles/vault/meta/main.yml @@ -0,0 +1,26 @@ +--- +# meta file for hashicorp_vault +galaxy_info: + namespace: "ednz_cloud" + role_name: "hashicorp_vault" + author: "Bertrand Lanson" + description: "Install and configure hashicorp vault for debian-based distros." + license: "license (BSD, MIT)" + min_ansible_version: "2.10" + platforms: + - name: Ubuntu + versions: + - focal + - jammy + - noble + - name: Debian + versions: + - bullseye + - bookworm + galaxy_tags: + - "ubuntu" + - "debian" + - "hashicorp" + - "vault" + +dependencies: [] diff --git a/roles/vault/tasks/configure.yml b/roles/vault/tasks/configure.yml new file mode 100644 index 0000000..798e4fd --- /dev/null +++ b/roles/vault/tasks/configure.yml @@ -0,0 +1,74 @@ +--- +# task/configure file for hashicorp_vault +- name: "Vault | Create vault.env" + ansible.builtin.template: + src: vault.env.j2 + dest: "{{ vault_config_dir }}/vault.env" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0600" + register: _vault_env_file + +- name: "Vault | Copy vault.json template" + ansible.builtin.template: + src: vault.json.j2 + dest: "{{ vault_config_dir }}/vault.json" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0600" + register: _vault_config_file + +- name: "Vault | Set restart-check variable" + ansible.builtin.set_fact: + _vault_service_need_restart: true + when: _vault_env_file.changed or + _vault_config_file.changed + +- name: "Vault | Copy extra configuration files" + when: vault_extra_files + block: + - name: "Vault | Get extra file types" + ansible.builtin.stat: + path: "{{ item.src }}" + loop: "{{ vault_extra_files_list }}" + register: vault_extra_file_stat + delegate_to: localhost + + - name: "Vault | Set list for file sources" + vars: + _vault_file_sources: [] + ansible.builtin.set_fact: + _vault_file_sources: "{{ _vault_file_sources + [item.item] }}" + when: item.stat.isreg + loop: "{{ vault_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Vault | Set list for directory sources" + vars: + _vault_dir_sources: [] + ansible.builtin.set_fact: + _vault_dir_sources: "{{ _vault_dir_sources + [item.item] }}" + when: item.stat.isdir + loop: "{{ vault_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Vault | Template extra file sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest | regex_replace('\\.j2$', '') }}" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0700" + loop: "{{ _vault_file_sources }}" + when: _vault_file_sources is defined + + - name: "Vault | Template extra directory sources" + ansible.builtin.include_tasks: recursive_copy_extra_dirs.yml + loop: "{{ _vault_dir_sources }}" + loop_control: + loop_var: dir_source_item + when: _vault_dir_sources is defined diff --git a/roles/vault/tasks/install.yml b/roles/vault/tasks/install.yml new file mode 100644 index 0000000..625651a --- /dev/null +++ b/roles/vault/tasks/install.yml @@ -0,0 +1,151 @@ +--- +# task/install file for hashicorp_vault +- name: "Vault | Get latest release of vault" + when: vault_version == 'latest' + block: + - name: "Vault | Get latest vault release from github api" + ansible.builtin.uri: + url: "{{ vault_github_api }}/hashicorp/vault/releases/latest" + return_content: true + register: _vault_latest_release + + - name: "Vault | Set wanted vault version to latest tag" + ansible.builtin.set_fact: + _vault_wanted_version: "{{ _vault_latest_release.json['tag_name']|regex_replace('v', '') }}" + +- name: "Vault | Set wanted vault version to {{ vault_version }}" + ansible.builtin.set_fact: + _vault_wanted_version: "{{ vault_version|regex_replace('v', '') }}" + when: vault_version != 'latest' + +- name: "Vault | Get current vault version" + block: + - name: "Vault | Stat vault version file" + ansible.builtin.stat: + path: "{{ vault_config_dir }}/.version" + changed_when: false + check_mode: false + register: _vault_version_file + + - name: "Vault | Get current vault version" + ansible.builtin.slurp: + src: "{{ _vault_version_file.stat.path }}" + when: + - _vault_version_file.stat.exists + - _vault_version_file.stat.isreg + register: _vault_current_version + +- name: "Vault | Download and install vault binary" + when: _vault_current_version is not defined + or _vault_wanted_version != (_vault_current_version.content|default('')|b64decode) + block: + - name: "Vault | Set vault package name to download" + ansible.builtin.set_fact: + _vault_package_name: >- + vault_{{ _vault_wanted_version }}_linux_{{ vault_deb_architecture_map[ansible_architecture] }}.zip + _vault_shasum_file_name: >- + vault_{{ _vault_wanted_version }}_SHA256SUMS + + - name: "Vault | Download checksum file for vault archive" + ansible.builtin.get_url: + url: "{{ vault_repository_url }}/{{ _vault_wanted_version }}/{{ _vault_shasum_file_name }}" + dest: "/tmp/{{ _vault_shasum_file_name }}" + mode: "0644" + register: _vault_checksum_file + until: _vault_checksum_file is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Vault | Extract correct checksum from checksum file" + ansible.builtin.command: + cmd: 'grep "{{ _vault_package_name }}" /tmp/{{ _vault_shasum_file_name }}' + changed_when: false + register: _vault_expected_checksum_line + + - name: "Vault | Parse the expected checksum" + ansible.builtin.set_fact: + _vault_expected_checksum: "{{ _vault_expected_checksum_line.stdout.split()[0] }}" + + - name: "Vault | Download vault binary archive" + ansible.builtin.get_url: + url: "{{ vault_repository_url }}/{{ _vault_wanted_version }}/{{ _vault_package_name }}" + dest: "/tmp/{{ _vault_package_name }}" + mode: "0644" + checksum: "sha256:{{ _vault_expected_checksum }}" + register: _vault_binary_archive + until: _vault_binary_archive is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Vault | Create temporary directory for archive decompression" + ansible.builtin.file: + path: /tmp/vault + state: directory + mode: "0755" + + - name: "Vault | Unpack vault archive" + ansible.builtin.unarchive: + src: "/tmp/{{ _vault_package_name }}" + dest: "/tmp/vault" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0755" + remote_src: true + + - name: "Vault | Copy vault binary to {{ vault_binary_path }}" + ansible.builtin.copy: + src: /tmp/vault/vault + dest: "{{ vault_binary_path }}" + owner: root + group: root + mode: "0755" + remote_src: true + force: true + + - name: "Vault | Update vault version file" + ansible.builtin.copy: + content: "{{ _vault_wanted_version }}" + dest: "{{ vault_config_dir }}/.version" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0600" + + - name: "Vault | Set restart-check variable" + ansible.builtin.set_fact: + _vault_service_need_restart: true + + - name: "Vault | Cleanup temporary directory" + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/vault + - /tmp/{{ _vault_package_name }} + - /tmp/{{ _vault_shasum_file_name }} + +- name: "Vault | Copy systemd service file for vault" + ansible.builtin.template: + src: "vault.service.j2" + dest: "/etc/systemd/system/{{ vault_service_name }}.service" + owner: root + group: root + mode: "0644" + register: _vault_unit_file + +- name: "Vault | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _vault_service_need_reload: true + _vault_service_need_restart: true + when: _vault_unit_file.changed # noqa: no-handler + +- name: "Vault | Copy systemd service file for vault" + ansible.builtin.template: + src: "vault.service.j2" + dest: "/etc/systemd/system/vault.service" + owner: root + group: root + mode: "0644" + notify: + - "systemctl-daemon-reload" diff --git a/roles/vault/tasks/main.yml b/roles/vault/tasks/main.yml new file mode 100644 index 0000000..e1af6da --- /dev/null +++ b/roles/vault/tasks/main.yml @@ -0,0 +1,43 @@ +--- +# task/main file for hashicorp_vault +- name: "Vault | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _vault_service_need_reload: false + _vault_service_need_restart: false + +- name: "Vault | Import merge_variables.yml" + ansible.builtin.include_tasks: merge_variables.yml + +- name: "Vault | Import prerequisites.yml" + ansible.builtin.include_tasks: prerequisites.yml + +- name: "Vault | Import install.yml" + ansible.builtin.include_tasks: "install.yml" + +- name: "Vault | Import configure.yml" + ansible.builtin.include_tasks: configure.yml + +- name: "Vault | Populate service facts" + ansible.builtin.service_facts: + +- name: "Vault | Set restart-check variable" + ansible.builtin.set_fact: + _vault_service_need_restart: true + when: ansible_facts.services[vault_service_name~'.service'].state != 'running' + +- name: "Vault | Enable service: {{ vault_service_name }}" + ansible.builtin.service: + name: "{{ vault_service_name }}" + enabled: true + +- name: "Vault | Reload systemd daemon" + ansible.builtin.systemd: + daemon_reload: true + when: _vault_service_need_reload + +- name: "Vault | Start service: {{ vault_service_name }}" + ansible.builtin.service: + name: "{{ vault_service_name }}" + state: restarted + throttle: 1 + when: _vault_service_need_restart diff --git a/roles/vault/tasks/merge_variables.yml b/roles/vault/tasks/merge_variables.yml new file mode 100644 index 0000000..c3df934 --- /dev/null +++ b/roles/vault/tasks/merge_variables.yml @@ -0,0 +1,73 @@ +--- +# task/merge_variables file for hashicorp_vault +- name: "Vault | Merge listener configuration" + block: + - name: "Vault | Merge tls listener configuration" + vars: + _config_to_merge: "{{ vault_tls_listener_configuration }}" + ansible.builtin.set_fact: + vault_listener_configuration: "{{ + vault_listener_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: vault_enable_tls + + - name: "Vault | Merge extra listener configuration" + vars: + _config_to_merge: "{{ vault_extra_listener_configuration }}" + ansible.builtin.set_fact: + vault_listener_configuration: "{{ + vault_listener_configuration | + combine(_config_to_merge, recursive=true) + }}" + + - name: "Vault | Add certificates directory to extra_files_dir" + ansible.builtin.set_fact: + vault_extra_files_list: "{{ + vault_extra_files_list + + vault_certificates_extra_files_dir + | unique + | sort + }}" + +- name: "Vault | Merge service registration configuration" + vars: + _config_to_merge: + service_registration: "{{ vault_service_registration_configuration }}" + ansible.builtin.set_fact: + vault_configuration: "{{ + vault_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: vault_enable_service_registration + +- name: "Vault | Merge plugins configuration" + vars: + _config_to_merge: + plugin_directory: "{{ vault_plugins_directory }}" + ansible.builtin.set_fact: + vault_configuration: "{{ + vault_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: vault_enable_plugins + +- name: "Vault | Merge logging configuration" + vars: + _config_to_merge: "{{ vault_log_to_file_configuration }}" + ansible.builtin.set_fact: + vault_configuration: "{{ + vault_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: vault_enable_log_to_file + +- name: "Vault | Merge extra configuration settings" + vars: + _config_to_merge: "{{ vault_extra_configuration }}" + ansible.builtin.set_fact: + vault_configuration: "{{ + vault_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: vault_extra_configuration is defined diff --git a/roles/vault/tasks/prerequisites.yml b/roles/vault/tasks/prerequisites.yml new file mode 100644 index 0000000..53e4197 --- /dev/null +++ b/roles/vault/tasks/prerequisites.yml @@ -0,0 +1,46 @@ +--- +# task/prerequisites file for hashicorp_vault +- name: "Vault | Create group {{ vault_group }}" + ansible.builtin.group: + name: "{{ vault_group }}" + state: present + +- name: "Vault | Create user {{ vault_user }}" + ansible.builtin.user: + name: "{{ vault_user }}" + group: "{{ vault_group }}" + shell: /bin/false + state: present + +- name: "Vault | Create directory {{ vault_config_dir }}" + ansible.builtin.file: + path: "{{ vault_config_dir }}" + state: directory + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0755" + +- name: "Vault | Create directory {{ vault_data_dir}}" + ansible.builtin.file: + path: "{{ vault_data_dir }}" + state: directory + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0755" + +- name: "Vault | Create directory {{ vault_certs_dir }}" + ansible.builtin.file: + path: "{{ vault_certs_dir }}" + state: directory + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0755" + +- name: "Vault | Create directory {{ vault_logs_dir }}" + ansible.builtin.file: + path: "{{ vault_logs_dir }}" + state: directory + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0755" + when: vault_enable_log_to_file diff --git a/roles/vault/tasks/recursive_copy_extra_dirs.yml b/roles/vault/tasks/recursive_copy_extra_dirs.yml new file mode 100644 index 0000000..22feb1f --- /dev/null +++ b/roles/vault/tasks/recursive_copy_extra_dirs.yml @@ -0,0 +1,26 @@ +--- +# task/recursive_copy_extra_dirs file for hashicorp_vault +- name: "Vault | Ensure destination directory exists" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}" + recurse: true + state: directory + mode: "0775" + +- name: "Vault | Create extra directory sources" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}/{{ item.path }}" + state: directory + mode: "0775" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'directory' + +- name: "Vault | Template extra directory sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ dir_source_item.dest }}/{{ item.path | regex_replace('\\.j2$', '') }}" + owner: "{{ vault_user }}" + group: "{{ vault_group }}" + mode: "0700" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'file' diff --git a/roles/vault/templates/vault.env.j2 b/roles/vault/templates/vault.env.j2 new file mode 100644 index 0000000..855ee42 --- /dev/null +++ b/roles/vault/templates/vault.env.j2 @@ -0,0 +1,4 @@ +# {{ ansible_managed }} +{% for item in vault_env_variables %} +{{ item }}="{{ vault_env_variables[item] }}" +{% endfor %} diff --git a/roles/vault/templates/vault.json.j2 b/roles/vault/templates/vault.json.j2 new file mode 100644 index 0000000..63beaa5 --- /dev/null +++ b/roles/vault/templates/vault.json.j2 @@ -0,0 +1 @@ +{{ vault_configuration|to_nice_json }} diff --git a/roles/vault/templates/vault.service.j2 b/roles/vault/templates/vault.service.j2 new file mode 100644 index 0000000..6af0ef4 --- /dev/null +++ b/roles/vault/templates/vault.service.j2 @@ -0,0 +1,38 @@ +[Unit] +Description="HashiCorp Vault - A tool for managing secrets" +Documentation=https://www.vaultproject.io/docs/ +Requires=network-online.target +After=network-online.target +ConditionFileNotEmpty={{ vault_config_dir }}/vault.json +StartLimitIntervalSec=60 +StartLimitBurst=3 +{% if vault_configuration.storage.consul is defined or vault_configuration.service_registration.consul is defined %} +Wants=consul.service +After=consul.service +{% endif %} + +[Service] +Type=notify +EnvironmentFile=-{{ vault_config_dir }}/vault.env +User={{ vault_user }} +Group={{ vault_group }} +ProtectSystem=full +ProtectHome=read-only +PrivateTmp=yes +PrivateDevices=yes +SecureBits=keep-caps +AmbientCapabilities=CAP_IPC_LOCK +CapabilityBoundingSet=CAP_SYSLOG CAP_IPC_LOCK +NoNewPrivileges=yes +ExecStart={{ vault_binary_path }} server -config={{ vault_config_dir }} +ExecReload=/bin/kill --signal HUP $MAINPID +KillMode=process +KillSignal=SIGINT +Restart=on-failure +RestartSec=5 +TimeoutStopSec=30 +LimitNOFILE=65536 +LimitMEMLOCK=infinity + +[Install] +WantedBy=multi-user.target diff --git a/roles/vault/vars/main.yml b/roles/vault/vars/main.yml new file mode 100644 index 0000000..5967bde --- /dev/null +++ b/roles/vault/vars/main.yml @@ -0,0 +1,26 @@ +--- +# vars file for hashicorp_vault +vault_user: "vault" +vault_group: "vault" +vault_binary_path: /usr/local/bin/vault +vault_deb_architecture_map: + x86_64: "amd64" + aarch64: "arm64" + armv7l: "arm" + armv6l: "arm" +vault_architecture: "{{ vault_deb_architecture_map[ansible_architecture] | default(ansible_architecture) }}" +vault_service_name: "vault" +vault_github_api: https://api.github.com/repos +vault_github_project: hashicorp/vault +vault_github_url: https://github.com +vault_repository_url: https://releases.hashicorp.com/vault + +vault_configuration: + cluster_name: "{{ vault_cluster_name }}" + cluster_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ vault_cluster_addr }}:8201" + api_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ vault_cluster_addr }}:8200" + ui: "{{ vault_enable_ui }}" + disable_mlock: "{{ vault_disable_mlock }}" + disable_cache: "{{ vault_disable_cache }}" + listener: "{{ vault_listener_configuration }}" + storage: "{{ vault_storage_configuration }}"