diff --git a/.cz.toml b/.cz.toml index 44008ce..088e162 100644 --- a/.cz.toml +++ b/.cz.toml @@ -4,3 +4,4 @@ version_provider = "scm" version_files = ["galaxy.yml:^version"] update_changelog_on_bump = true major_version_zero = true +tag_format = "v$version" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 81f2416..6403a89 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +--- repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.5.0 diff --git a/docs/general.md b/docs/general.md index 3af4378..11459ae 100644 --- a/docs/general.md +++ b/docs/general.md @@ -6,7 +6,7 @@ Hashistack Ansible uses a configuration directory to store all the configuration files and other artifacts. -This directory is defined with the variable `configuration_directory`. By default, it will look at `{{ lookup('env', 'PWD') }}/etc/hashistack`, which equals `$(pwd)/etc/hashistack`. +This directory is defined with the variable `hashistack_configuration_directory`. By default, it will look at `{{ lookup('env', 'PWD') }}/etc/hashistack`, which equals `$(pwd)/etc/hashistack`. Under this directory, you are expected to place the `globals.yml` file, with your configuration. @@ -18,9 +18,9 @@ Additionally, subdirectories can be used to tailor the configuration further. Each group within the `inventory` will look at a directory named after itself: -- nomad_servers group will look for `{{ configuration_directory }}/nomad_servers` -- vault_servers group will look for `{{ configuration_directory }}/vault_servers` -- consul_servers group will look for `{{ configuration_directory }}/consul_servers` +- nomad_servers group will look for `{{ hashistack_configuration_directory }}/nomad_servers` +- vault_servers group will look for `{{ hashistack_configuration_directory }}/vault_servers` +- consul_servers group will look for `{{ hashistack_configuration_directory }}/consul_servers` Within each of these directories, you can place an additional `globals.yml file`, that will superseed the file at the root of the configuration directory. diff --git a/docs/quick_start.md b/docs/quick_start.md index a822545..c06db76 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -112,6 +112,16 @@ This will install roles that are not packaged with the collection, but are still You should now have some roles inside `./roles/`. +9. Copy `inventory` file and `globals.yml `file locally + +```bash +cp collections/ansible_collections/ednz_cloud/hashistack/playbooks/inventory/multinode.ini inventory/ +``` + +```bash +cp collections/ansible_collections/ednz_cloud/hashistack/playbooks/group_vars/all/globals.yml etc/hashistack/globals.yml +``` + ## Generate Credentials Before deploying your infrastructure with Hashistack-Ansible, you need to generate credentials that will be used to bootstrap the various clusters. diff --git a/docs/tls_guide.md b/docs/tls_guide.md index f26e45d..2eb4699 100644 --- a/docs/tls_guide.md +++ b/docs/tls_guide.md @@ -1 +1,6 @@ # TLS Guide + + +create certificate/ca directory + +`ansible-playbook -i inventory/multinode.ini ednz_cloud.hashistack.generate_certs.yml` diff --git a/galaxy.yml b/galaxy.yml index c0fb16c..687cc32 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -11,7 +11,7 @@ license_file: "LICENSE" # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: [] +tags: ["tools"] dependencies: {} repository: https://git.ednz.fr/ansible-collections/hashistack documentation: http://docs.example.com diff --git a/molecule/cni_default/converge.yml b/molecule/cni_default/converge.yml new file mode 100644 index 0000000..5fc2cb1 --- /dev/null +++ b/molecule/cni_default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.cni" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.cni" diff --git a/molecule/cni_default/molecule.yml b/molecule/cni_default/molecule.yml new file mode 100644 index 0000000..6482b97 --- /dev/null +++ b/molecule/cni_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: cni_default + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/cni_default/prepare.yml b/molecule/cni_default/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/cni_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/cni_default/requirements.yml b/molecule/cni_default/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/cni_default/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/cni_default/verify.yml b/molecule/cni_default/verify.yml new file mode 100644 index 0000000..5f1bb76 --- /dev/null +++ b/molecule/cni_default/verify.yml @@ -0,0 +1,6 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: [] diff --git a/molecule/consul_default/converge.yml b/molecule/consul_default/converge.yml new file mode 100644 index 0000000..91712f2 --- /dev/null +++ b/molecule/consul_default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.consul" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.consul" diff --git a/molecule/consul_default/molecule.yml b/molecule/consul_default/molecule.yml new file mode 100644 index 0000000..8314c8e --- /dev/null +++ b/molecule/consul_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: consul_default + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/consul_default/prepare.yml b/molecule/consul_default/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/consul_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/consul_default/requirements.yml b/molecule/consul_default/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/consul_default/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/consul_default/verify.yml b/molecule/consul_default/verify.yml new file mode 100644 index 0000000..db7c5d1 --- /dev/null +++ b/molecule/consul_default/verify.yml @@ -0,0 +1,170 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: consul user and group" + block: + - name: "Getent user consul" + ansible.builtin.getent: + database: passwd + key: consul + register: consul_user + + - name: "Getent group consul" + ansible.builtin.getent: + database: group + key: consul + register: consul_group + + - name: "Verify consul user and group" + ansible.builtin.assert: + that: + - not consul_user.failed + - not consul_group.failed + - "'consul' in consul_user.ansible_facts.getent_passwd.keys()" + - "'/home/consul' in consul_user.ansible_facts.getent_passwd['consul']" + - "'/bin/false' in consul_user.ansible_facts.getent_passwd['consul']" + - "'consul' in consul_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/consul" + block: + - name: "Stat binary /usr/local/bin/consul" + ansible.builtin.stat: + path: "/usr/local/bin/consul" + register: stat_usr_local_bin_consul + + - name: "Verify binary /usr/local/bin/consul" + ansible.builtin.assert: + that: + - stat_usr_local_bin_consul.stat.exists + - stat_usr_local_bin_consul.stat.isreg + - stat_usr_local_bin_consul.stat.pw_name == 'root' + - stat_usr_local_bin_consul.stat.gr_name == 'root' + - stat_usr_local_bin_consul.stat.mode == '0755' + + - name: "Test: directory /etc/consul.d" + block: + - name: "Stat directory /etc/consul.d" + ansible.builtin.stat: + path: "/etc/consul.d" + register: stat_etc_consul_d + + - name: "Stat file /etc/consul.d/consul.env" + ansible.builtin.stat: + path: "/etc/consul.d/consul.env" + register: stat_etc_consul_d_consul_env + + - name: "Stat file /etc/consul.d/consul.json" + ansible.builtin.stat: + path: "/etc/consul.d/consul.json" + register: stat_etc_consul_d_consul_json + + - name: "Slurp file /etc/consul.d/consul.json" + ansible.builtin.slurp: + src: "/etc/consul.d/consul.json" + register: slurp_etc_consul_d_consul_json + + - name: "Verify directory /etc/consul.d" + ansible.builtin.assert: + that: + - stat_etc_consul_d.stat.exists + - stat_etc_consul_d.stat.isdir + - stat_etc_consul_d.stat.pw_name == 'consul' + - stat_etc_consul_d.stat.gr_name == 'consul' + - stat_etc_consul_d.stat.mode == '0755' + - stat_etc_consul_d_consul_env.stat.exists + - stat_etc_consul_d_consul_env.stat.isreg + - stat_etc_consul_d_consul_env.stat.pw_name == 'consul' + - stat_etc_consul_d_consul_env.stat.gr_name == 'consul' + - stat_etc_consul_d_consul_env.stat.mode == '0600' + - stat_etc_consul_d_consul_json.stat.exists + - stat_etc_consul_d_consul_json.stat.isreg + - stat_etc_consul_d_consul_json.stat.pw_name == 'consul' + - stat_etc_consul_d_consul_json.stat.gr_name == 'consul' + - stat_etc_consul_d_consul_json.stat.mode == '0600' + - slurp_etc_consul_d_consul_json.content != '' + + - name: "Test: directory /opt/consul" + block: + - name: "Stat directory /opt/consul" + ansible.builtin.stat: + path: "/opt/consul" + register: stat_opt_consul + + - name: "Verify directory /opt/consul" + ansible.builtin.assert: + that: + - stat_opt_consul.stat.exists + - stat_opt_consul.stat.isdir + - stat_opt_consul.stat.pw_name == 'consul' + - stat_opt_consul.stat.gr_name == 'consul' + - stat_opt_consul.stat.mode == '0755' + + - name: "Test: service consul" + block: + - name: "Get service consul" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/consul.service" + ansible.builtin.stat: + path: "/etc/systemd/system/consul.service" + register: stat_etc_systemd_system_consul_service + + - name: "Slurp file /etc/systemd/system/consul.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/consul.service" + register: slurp_etc_systemd_system_consul_service + + - name: "Verify service consul" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_consul_service.stat.exists + - stat_etc_systemd_system_consul_service.stat.isreg + - stat_etc_systemd_system_consul_service.stat.pw_name == 'root' + - stat_etc_systemd_system_consul_service.stat.gr_name == 'root' + - stat_etc_systemd_system_consul_service.stat.mode == '0644' + - slurp_etc_systemd_system_consul_service.content != '' + - ansible_facts.services['consul.service'] is defined + - ansible_facts.services['consul.service']['source'] == 'systemd' + - ansible_facts.services['consul.service']['state'] == 'running' + - ansible_facts.services['consul.service']['status'] == 'enabled' + + - name: "Test: interaction consul" + block: + - name: "Command consul kv put" + ansible.builtin.command: "consul kv put foo bar" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + changed_when: false + register: consul_kv_put + + - name: "Command consul kv get" + ansible.builtin.command: "consul kv get foo" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + changed_when: false + register: consul_kv_get + + - name: "Command consul kv delete" + ansible.builtin.command: "consul kv delete foo" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + changed_when: false + register: consul_kv_delete + + - name: "Command consul members" + ansible.builtin.command: "consul members" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + changed_when: false + register: consul_members + + - name: "Verify consul interaction" + ansible.builtin.assert: + that: + - "'instance' in consul_members.stdout" + - consul_kv_put.stdout == 'Success! Data written to: foo' + - consul_kv_get.stdout == 'bar' + - consul_kv_delete.stdout == 'Success! Deleted key: foo' diff --git a/molecule/consul_with_acl_enabled/converge.yml b/molecule/consul_with_acl_enabled/converge.yml new file mode 100644 index 0000000..91712f2 --- /dev/null +++ b/molecule/consul_with_acl_enabled/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.consul" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.consul" diff --git a/molecule/consul_with_acl_enabled/group_vars/all.yml b/molecule/consul_with_acl_enabled/group_vars/all.yml new file mode 100644 index 0000000..ff14db7 --- /dev/null +++ b/molecule/consul_with_acl_enabled/group_vars/all.yml @@ -0,0 +1,23 @@ +--- +##################### +# ACL configuration # +##################### + +consul_acl_configuration: + enabled: true + default_policy: "deny" + enable_token_persistence: true + tokens: + initial_management: "1a1f2ce5-3730-47de-9a9c-89e037376bab" + agent: "1a1f2ce5-3730-47de-9a9c-89e037376bab" + +########### +# logging # +########### + +consul_log_level: info +consul_enable_log_to_file: true +consul_log_to_file_configuration: + log_file: "{{ consul_logs_dir }}/consul.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 diff --git a/molecule/consul_with_acl_enabled/molecule.yml b/molecule/consul_with_acl_enabled/molecule.yml new file mode 100644 index 0000000..82508a0 --- /dev/null +++ b/molecule/consul_with_acl_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: consul_with_acl_enabled + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/consul_with_acl_enabled/prepare.yml b/molecule/consul_with_acl_enabled/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/consul_with_acl_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/consul_with_acl_enabled/requirements.yml b/molecule/consul_with_acl_enabled/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/consul_with_acl_enabled/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/consul_with_acl_enabled/verify.yml b/molecule/consul_with_acl_enabled/verify.yml new file mode 100644 index 0000000..8ae0242 --- /dev/null +++ b/molecule/consul_with_acl_enabled/verify.yml @@ -0,0 +1,176 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: consul user and group" + block: + - name: "Getent user consul" + ansible.builtin.getent: + database: passwd + key: consul + register: consul_user + + - name: "Getent group consul" + ansible.builtin.getent: + database: group + key: consul + register: consul_group + + - name: "Verify consul user and group" + ansible.builtin.assert: + that: + - not consul_user.failed + - not consul_group.failed + - "'consul' in consul_user.ansible_facts.getent_passwd.keys()" + - "'/home/consul' in consul_user.ansible_facts.getent_passwd['consul']" + - "'/bin/false' in consul_user.ansible_facts.getent_passwd['consul']" + - "'consul' in consul_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/consul" + block: + - name: "Stat binary /usr/local/bin/consul" + ansible.builtin.stat: + path: "/usr/local/bin/consul" + register: stat_usr_local_bin_consul + + - name: "Verify binary /usr/local/bin/consul" + ansible.builtin.assert: + that: + - stat_usr_local_bin_consul.stat.exists + - stat_usr_local_bin_consul.stat.isreg + - stat_usr_local_bin_consul.stat.pw_name == 'root' + - stat_usr_local_bin_consul.stat.gr_name == 'root' + - stat_usr_local_bin_consul.stat.mode == '0755' + + - name: "Test: directory /etc/consul.d" + block: + - name: "Stat directory /etc/consul.d" + ansible.builtin.stat: + path: "/etc/consul.d" + register: stat_etc_consul_d + + - name: "Stat file /etc/consul.d/consul.env" + ansible.builtin.stat: + path: "/etc/consul.d/consul.env" + register: stat_etc_consul_d_consul_env + + - name: "Stat file /etc/consul.d/consul.json" + ansible.builtin.stat: + path: "/etc/consul.d/consul.json" + register: stat_etc_consul_d_consul_json + + - name: "Slurp file /etc/consul.d/consul.json" + ansible.builtin.slurp: + src: "/etc/consul.d/consul.json" + register: slurp_etc_consul_d_consul_json + + - name: "Verify directory /etc/consul.d" + ansible.builtin.assert: + that: + - stat_etc_consul_d.stat.exists + - stat_etc_consul_d.stat.isdir + - stat_etc_consul_d.stat.pw_name == 'consul' + - stat_etc_consul_d.stat.gr_name == 'consul' + - stat_etc_consul_d.stat.mode == '0755' + - stat_etc_consul_d_consul_env.stat.exists + - stat_etc_consul_d_consul_env.stat.isreg + - stat_etc_consul_d_consul_env.stat.pw_name == 'consul' + - stat_etc_consul_d_consul_env.stat.gr_name == 'consul' + - stat_etc_consul_d_consul_env.stat.mode == '0600' + - stat_etc_consul_d_consul_json.stat.exists + - stat_etc_consul_d_consul_json.stat.isreg + - stat_etc_consul_d_consul_json.stat.pw_name == 'consul' + - stat_etc_consul_d_consul_json.stat.gr_name == 'consul' + - stat_etc_consul_d_consul_json.stat.mode == '0600' + - slurp_etc_consul_d_consul_json.content != '' + + - name: "Test: directory /opt/consul" + block: + - name: "Stat directory /opt/consul" + ansible.builtin.stat: + path: "/opt/consul" + register: stat_opt_consul + + - name: "Verify directory /opt/consul" + ansible.builtin.assert: + that: + - stat_opt_consul.stat.exists + - stat_opt_consul.stat.isdir + - stat_opt_consul.stat.pw_name == 'consul' + - stat_opt_consul.stat.gr_name == 'consul' + - stat_opt_consul.stat.mode == '0755' + + - name: "Test: service consul" + block: + - name: "Get service consul" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/consul.service" + ansible.builtin.stat: + path: "/etc/systemd/system/consul.service" + register: stat_etc_systemd_system_consul_service + + - name: "Slurp file /etc/systemd/system/consul.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/consul.service" + register: slurp_etc_systemd_system_consul_service + + - name: "Verify service consul" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_consul_service.stat.exists + - stat_etc_systemd_system_consul_service.stat.isreg + - stat_etc_systemd_system_consul_service.stat.pw_name == 'root' + - stat_etc_systemd_system_consul_service.stat.gr_name == 'root' + - stat_etc_systemd_system_consul_service.stat.mode == '0644' + - slurp_etc_systemd_system_consul_service.content != '' + - ansible_facts.services['consul.service'] is defined + - ansible_facts.services['consul.service']['source'] == 'systemd' + - ansible_facts.services['consul.service']['state'] == 'running' + - ansible_facts.services['consul.service']['status'] == 'enabled' + + - name: "Test: interaction consul" + vars: + acl_token: "1a1f2ce5-3730-47de-9a9c-89e037376bab" + block: + - name: "Command consul kv put" + ansible.builtin.command: "consul kv put foo bar" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + CONSUL_HTTP_TOKEN: "{{ acl_token }}" + changed_when: false + register: consul_kv_put + + - name: "Command consul kv get" + ansible.builtin.command: "consul kv get foo" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + CONSUL_HTTP_TOKEN: "{{ acl_token }}" + changed_when: false + register: consul_kv_get + + - name: "Command consul kv delete" + ansible.builtin.command: "consul kv delete foo" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + CONSUL_HTTP_TOKEN: "{{ acl_token }}" + changed_when: false + register: consul_kv_delete + + - name: "Command consul members" + ansible.builtin.command: "consul members" + environment: + CONSUL_HTTP_ADDR: "http://{{ ansible_default_ipv4.address }}:8500" + CONSUL_HTTP_TOKEN: "{{ acl_token }}" + changed_when: false + register: consul_members + + - name: "Verify consul interaction" + ansible.builtin.assert: + that: + - "'instance' in consul_members.stdout" + - consul_kv_put.stdout == 'Success! Data written to: foo' + - consul_kv_get.stdout == 'bar' + - consul_kv_delete.stdout == 'Success! Deleted key: foo' diff --git a/molecule/hashistack_ca_default/converge.yml b/molecule/hashistack_ca_default/converge.yml new file mode 100644 index 0000000..980542b --- /dev/null +++ b/molecule/hashistack_ca_default/converge.yml @@ -0,0 +1,10 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.hashistack_ca" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.hashistack_ca" + apply: + delegate_to: localhost diff --git a/molecule/hashistack_ca_default/etc/hashistack/.gitkeep b/molecule/hashistack_ca_default/etc/hashistack/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/molecule/hashistack_ca_default/group_vars/all.yml b/molecule/hashistack_ca_default/group_vars/all.yml new file mode 100644 index 0000000..1ed4e3d --- /dev/null +++ b/molecule/hashistack_ca_default/group_vars/all.yml @@ -0,0 +1,111 @@ +--- +hashistack_ca_directory: "{{ lookup('env', 'PWD') }}/etc/hashistack/certificates" +hashistack_ca_use_cryptography: false +hashistack_ca_action: "noop" +hashistack_ca_domain: ednz.fr +hashistack_ca_directory_owner: "{{ lookup('env', 'USER') }}" + +############################## +# Root Certificate Authority # +############################## +hashistack_ca_root_org_name: EDNZ Cloud +hashistack_ca_root_country: FR +hashistack_ca_root_locality: Paris +hashistack_ca_root_common_name: "{{ hashistack_ca_domain }} Root CA" +hashistack_ca_root_email: +hashistack_ca_root_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_root_key_usage_critical: true +hashistack_ca_root_basic_constraints: + - CA:TRUE +hashistack_ca_root_basic_constraints_critical: true + +# Optional fields +hashistack_ca_root_state_or_province_name: +hashistack_ca_root_email_address: + +# Validity +hashistack_ca_root_valid_for: 1825d +hashistack_ca_root_renew_threshold: 180d + +###################################### +# Intermediate Certificate Authority # +###################################### +hashistack_ca_intermediate_org_name: EDNZ Cloud Intermediate +hashistack_ca_intermediate_country: FR +hashistack_ca_intermediate_locality: Paris +hashistack_ca_intermediate_common_name: "{{ hashistack_ca_domain }} Intermediate CA" +hashistack_ca_intermediate_email: +hashistack_ca_intermediate_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_intermediate_key_usage_critical: true +hashistack_ca_intermediate_basic_constraints: + - CA:TRUE + - pathlen:0 +hashistack_ca_intermediate_basic_constraints_critical: true + +# Optional fields +hashistack_ca_intermediate_state_or_province_name: +hashistack_ca_intermediate_email_address: + +# Validity +hashistack_ca_intermediate_valid_for: 365d +hashistack_ca_intermediate_renew_threshold: 90d + +# Name Constraints +hashistack_ca_intermediate_name_constraints_permitted: + - "DNS:.{{ hashistack_ca_domain }}" + - DNS:.nomad + - DNS:.consul + - DNS:localhost + - IP:192.168.0.0/16 + - IP:172.16.0.0/16 + - IP:10.0.0.0/8 + - IP:127.0.0.0/8 +hashistack_ca_intermediate_name_constraints_critical: "{{ (hashistack_ca_intermediate_name_constraints_permitted is defined and hashistack_ca_intermediate_name_constraints_permitted | length > 0) }}" + +##################### +# Leaf certificates # +##################### + +hashistack_ca_leaf_valid_for: 90d +hashistack_ca_leaf_renew_threshold: 30d + +############################ +# Consul Leaf Certificates # +############################ +hashistack_ca_consul_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_consul_common_name: "{{ inventory_hostname }}" +hashistack_ca_consul_csr_sans: + - "DNS:{{ inventory_hostname~'.'~hashistack_ca_domain }}" + - "DNS:consul.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" + +########################### +# Nomad Leaf Certificates # +########################### +hashistack_ca_nomad_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_nomad_common_name: "{{ inventory_hostname }}" +hashistack_ca_nomad_csr_sans: + - "DNS:{{ inventory_hostname~'.'~hashistack_ca_domain }}" + - DNS:server.global.nomad + - DNS:client.global.nomad + - "DNS:nomad.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" + +########################### +# Vault Leaf Certificates # +########################### +hashistack_ca_vault_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_vault_common_name: "{{ inventory_hostname }}" +hashistack_ca_vault_csr_sans: + - "DNS:{{ inventory_hostname~'.'~hashistack_ca_domain }}" + - "DNS:vault.service.consul" + - "DNS:active.vault.service.consul" + - "DNS:standby.vault.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" diff --git a/molecule/hashistack_ca_default/molecule.yml b/molecule/hashistack_ca_default/molecule.yml new file mode 100644 index 0000000..1f90d9e --- /dev/null +++ b/molecule/hashistack_ca_default/molecule.yml @@ -0,0 +1,65 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: docker +platforms: + - name: consul-vault + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - common + - consul_servers + - vault_servers + - name: vault-nomad + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - common + - nomad_clients + - vault_servers + - name: nomad-consul + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true + groups: + - common + - nomad_clients + - consul_agents +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: hashistack_ca_default + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/hashistack_ca_default/prepare.yml b/molecule/hashistack_ca_default/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/hashistack_ca_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/hashistack_ca_default/requirements.yml b/molecule/hashistack_ca_default/requirements.yml new file mode 100644 index 0000000..329e789 --- /dev/null +++ b/molecule/hashistack_ca_default/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/hashistack_ca_default/verify.yml b/molecule/hashistack_ca_default/verify.yml new file mode 100644 index 0000000..5f1bb76 --- /dev/null +++ b/molecule/hashistack_ca_default/verify.yml @@ -0,0 +1,6 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: [] diff --git a/molecule/no_tls_multi_node/etc/hashistack/globals.yml b/molecule/no_tls_multi_node/etc/hashistack/globals.yml index 11dc568..66f4777 100644 --- a/molecule/no_tls_multi_node/etc/hashistack/globals.yml +++ b/molecule/no_tls_multi_node/etc/hashistack/globals.yml @@ -1,4 +1,12 @@ --- +# Molecule specific variables + +hashistack_ca_action: "root_ca,int_ca,leaf_cert,renew_root," +hashistack_ca_directory: "{{ hashistack_sub_configuration_directories['certificates'] }}" +hashistack_ca_directory_owner: "{{ lookup('env', 'USER') }}" +hashistack_ca_domain: ednz.lab +hashistack_ca_intermediate_name_constraints_critical: false + ########################## # General options ######## ########################## @@ -9,7 +17,7 @@ # enable_nomad: "yes" # haproxy_version: "2.8" -# nomad_version: "1.8.1" +nomad_version: "1.8.2" # consul_version: "1.18.1" # vault_version: "1.16.2" @@ -32,6 +40,13 @@ api_interface: "eth1" enable_tls_external: true # external_tls_externally_managed_certs: false +######################## +# internal tls options # +######################## + +enable_tls_internal: true +# internal_tls_externally_managed_certs: false + ##################################################### # # # Consul # @@ -95,7 +110,7 @@ enable_tls_external: true # consul tls configuration # ############################ -consul_enable_tls: true +# consul_enable_tls: "{{ enable_tls_internal }}" # consul_tls_configuration: # defaults: # ca_file: "/etc/ssl/certs/ca-certificates.crt" @@ -152,7 +167,7 @@ consul_enable_tls: true # vault listener # ################## -vault_enable_tls: true +# vault_enable_tls: "{{ enable_tls_internal }}" # vault_tls_verify: false # vault_listener_configuration: # tcp: @@ -263,17 +278,17 @@ vault_enable_tls: true # nomad internal tls # ###################### -nomad_enable_tls: true -nomad_tls_configuration: - http: true - rpc: true - ca_file: "/etc/ssl/certs/ca-certificates.crt" - cert_file: "{{ nomad_certificates_directory }}/cert.pem" - key_file: "{{ nomad_certificates_directory }}/key.pem" - verify_server_hostname: true +# nomad_enable_tls: "{{ enable_tls_internal }}" +# nomad_tls_configuration: +# http: true +# rpc: true +# ca_file: "/etc/ssl/certs/ca-certificates.crt" +# cert_file: "{{ nomad_certificates_directory }}/cert.pem" +# key_file: "{{ nomad_certificates_directory }}/key.pem" +# verify_server_hostname: true # nomad_certificates_directory: "{{ hashicorp_nomad_config_dir }}/tls" # nomad_certificates_extra_files_dir: -# - src: "{{ sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}" +# - src: "{{ hashistack_sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}" # dest: "{{ nomad_certificates_directory }}" ############################# diff --git a/molecule/no_tls_multi_node/etc/hashistack/haproxy_servers/globals.yml b/molecule/no_tls_multi_node/etc/hashistack/haproxy_servers/globals.yml new file mode 100644 index 0000000..5d6ecdc --- /dev/null +++ b/molecule/no_tls_multi_node/etc/hashistack/haproxy_servers/globals.yml @@ -0,0 +1,8 @@ +--- +nomad_client_configuration: + enabled: "{{ nomad_enable_client }}" + state_dir: "{{ nomad_data_dir }}/client" + cni_path: "{{ cni_plugins_install_path | default('/opt/cni/bin') }}" + bridge_network_name: nomad + bridge_network_subnet: "172.26.64.0/20" + node_pool: ingress diff --git a/molecule/no_tls_multi_node/etc/hashistack/vault_servers/config/plugin/vault-plugin-auth-openstack b/molecule/no_tls_multi_node/etc/hashistack/vault_servers/config/plugin/vault-plugin-auth-openstack deleted file mode 100755 index 1fd6777..0000000 Binary files a/molecule/no_tls_multi_node/etc/hashistack/vault_servers/config/plugin/vault-plugin-auth-openstack and /dev/null differ diff --git a/molecule/no_tls_multi_node/molecule.yml b/molecule/no_tls_multi_node/molecule.yml index d645f94..c517f69 100644 --- a/molecule/no_tls_multi_node/molecule.yml +++ b/molecule/no_tls_multi_node/molecule.yml @@ -8,7 +8,7 @@ driver: provider: name: libvirt platforms: - - name: proxy01 + - name: proxy01.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 2 memory: 2048 @@ -20,8 +20,9 @@ platforms: groups: - common - haproxy_servers + - nomad_clients - consul_agents - - name: proxy02 + - name: proxy02.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 2 memory: 2048 @@ -33,8 +34,9 @@ platforms: groups: - common - haproxy_servers + - nomad_clients - consul_agents - - name: hashistack01 + - name: hashistack01.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 4 memory: 2048 @@ -48,7 +50,7 @@ platforms: - vault_servers - consul_servers - nomad_servers - - name: hashistack02 + - name: hashistack02.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 4 memory: 2048 @@ -62,7 +64,7 @@ platforms: - vault_servers - consul_servers - nomad_servers - - name: hashistack03 + - name: hashistack03.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 4 memory: 2048 @@ -76,7 +78,7 @@ platforms: - vault_servers - consul_servers - nomad_servers - - name: hashistack04 + - name: hashistack04.ednz.lab box: generic/${MOLECULE_TEST_OS} cpus: 4 memory: 2048 diff --git a/molecule/no_tls_multi_node/prepare.yml b/molecule/no_tls_multi_node/prepare.yml index f5a76e2..9973f22 100644 --- a/molecule/no_tls_multi_node/prepare.yml +++ b/molecule/no_tls_multi_node/prepare.yml @@ -2,9 +2,6 @@ - name: Include certificate generation playbook ansible.builtin.import_playbook: ednz_cloud.hashistack.generate_certs.yml -# - name: Include credentials generation playbook -# ansible.builtin.import_playbook: ednz_cloud.hashistack.generate_credentials.yml - - name: Include bootstrap playbook ansible.builtin.import_playbook: ednz_cloud.hashistack.bootstrap.yml diff --git a/molecule/no_tls_multi_node/requirements.yml b/molecule/no_tls_multi_node/requirements.yml index ae8019d..3dd371e 100644 --- a/molecule/no_tls_multi_node/requirements.yml +++ b/molecule/no_tls_multi_node/requirements.yml @@ -5,9 +5,6 @@ roles: - name: ednz_cloud.manage_apt_packages - name: ednz_cloud.manage_pip_packages - name: ednz_cloud.install_docker - - name: ednz_cloud.docker_systemd_service -# - name: ednz_cloud.deploy_haproxy -# - name: ednz_cloud.deploy_keepalived collections: - name: ednz_cloud.hashistack diff --git a/molecule/nomad_default/converge.yml b/molecule/nomad_default/converge.yml new file mode 100644 index 0000000..ea6946e --- /dev/null +++ b/molecule/nomad_default/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.nomad" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.nomad" diff --git a/molecule/nomad_default/molecule.yml b/molecule/nomad_default/molecule.yml new file mode 100644 index 0000000..d22a13c --- /dev/null +++ b/molecule/nomad_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: nomad_default + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/nomad_default/prepare.yml b/molecule/nomad_default/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/nomad_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/nomad_default/requirements.yml b/molecule/nomad_default/requirements.yml new file mode 100644 index 0000000..d3761e2 --- /dev/null +++ b/molecule/nomad_default/requirements.yml @@ -0,0 +1,5 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_repositories + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/nomad_default/verify.yml b/molecule/nomad_default/verify.yml new file mode 100644 index 0000000..765cff5 --- /dev/null +++ b/molecule/nomad_default/verify.yml @@ -0,0 +1,170 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: nomad user and group" + block: + - name: "Getent user nomad" + ansible.builtin.getent: + database: passwd + key: nomad + register: nomad_user + + - name: "Getent group nomad" + ansible.builtin.getent: + database: group + key: nomad + register: nomad_group + + - name: "Verify nomad user and group" + ansible.builtin.assert: + that: + - not nomad_user.failed + - not nomad_group.failed + - "'nomad' in nomad_user.ansible_facts.getent_passwd.keys()" + - "'/home/nomad' in nomad_user.ansible_facts.getent_passwd['nomad']" + - "'/bin/false' in nomad_user.ansible_facts.getent_passwd['nomad']" + - "'nomad' in nomad_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/nomad" + block: + - name: "Stat binary /usr/local/bin/nomad" + ansible.builtin.stat: + path: "/usr/local/bin/nomad" + register: stat_usr_local_bin_nomad + + - name: "Verify binary /usr/local/bin/nomad" + ansible.builtin.assert: + that: + - stat_usr_local_bin_nomad.stat.exists + - stat_usr_local_bin_nomad.stat.isreg + - stat_usr_local_bin_nomad.stat.pw_name == 'root' + - stat_usr_local_bin_nomad.stat.gr_name == 'root' + - stat_usr_local_bin_nomad.stat.mode == '0755' + + - name: "Test: directory /etc/nomad.d" + block: + - name: "Stat directory /etc/nomad.d" + ansible.builtin.stat: + path: "/etc/nomad.d" + register: stat_etc_nomad_d + + - name: "Stat file /etc/nomad.d/nomad.env" + ansible.builtin.stat: + path: "/etc/nomad.d/nomad.env" + register: stat_etc_nomad_d_nomad_env + + - name: "Stat file /etc/nomad.d/nomad.json" + ansible.builtin.stat: + path: "/etc/nomad.d/nomad.json" + register: stat_etc_nomad_d_nomad_json + + - name: "Slurp file /etc/nomad.d/nomad.json" + ansible.builtin.slurp: + src: "/etc/nomad.d/nomad.json" + register: slurp_etc_nomad_d_nomad_json + + - name: "Verify directory /etc/nomad.d" + ansible.builtin.assert: + that: + - stat_etc_nomad_d.stat.exists + - stat_etc_nomad_d.stat.isdir + - stat_etc_nomad_d.stat.pw_name == 'nomad' + - stat_etc_nomad_d.stat.gr_name == 'nomad' + - stat_etc_nomad_d.stat.mode == '0755' + - stat_etc_nomad_d_nomad_env.stat.exists + - stat_etc_nomad_d_nomad_env.stat.isreg + - stat_etc_nomad_d_nomad_env.stat.pw_name == 'nomad' + - stat_etc_nomad_d_nomad_env.stat.gr_name == 'nomad' + - stat_etc_nomad_d_nomad_env.stat.mode == '0600' + - stat_etc_nomad_d_nomad_json.stat.exists + - stat_etc_nomad_d_nomad_json.stat.isreg + - stat_etc_nomad_d_nomad_json.stat.pw_name == 'nomad' + - stat_etc_nomad_d_nomad_json.stat.gr_name == 'nomad' + - stat_etc_nomad_d_nomad_json.stat.mode == '0600' + - slurp_etc_nomad_d_nomad_json.content != '' + + - name: "Test: directory /opt/nomad" + block: + - name: "Stat directory /opt/nomad" + ansible.builtin.stat: + path: "/opt/nomad" + register: stat_opt_nomad + + - name: "Verify directory /opt/nomad" + ansible.builtin.assert: + that: + - stat_opt_nomad.stat.exists + - stat_opt_nomad.stat.isdir + - stat_opt_nomad.stat.pw_name == 'nomad' + - stat_opt_nomad.stat.gr_name == 'nomad' + - stat_opt_nomad.stat.mode == '0755' + + - name: "Test: service nomad" + block: + - name: "Get service nomad" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/nomad.service" + ansible.builtin.stat: + path: "/etc/systemd/system/nomad.service" + register: stat_etc_systemd_system_nomad_service + + - name: "Slurp file /etc/systemd/system/nomad.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/nomad.service" + register: slurp_etc_systemd_system_nomad_service + + - name: "Verify service nomad" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_nomad_service.stat.exists + - stat_etc_systemd_system_nomad_service.stat.isreg + - stat_etc_systemd_system_nomad_service.stat.pw_name == 'root' + - stat_etc_systemd_system_nomad_service.stat.gr_name == 'root' + - stat_etc_systemd_system_nomad_service.stat.mode == '0644' + - slurp_etc_systemd_system_nomad_service.content != '' + - ansible_facts.services['nomad.service'] is defined + - ansible_facts.services['nomad.service']['source'] == 'systemd' + - ansible_facts.services['nomad.service']['state'] == 'running' + - ansible_facts.services['nomad.service']['status'] == 'enabled' + + - name: "Test: interaction nomad" + block: + - name: "Command nomad var put" + ansible.builtin.command: "nomad var put secret/foobar foo=bar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + changed_when: false + register: nomad_var_put + + - name: "Command nomad var get" + ansible.builtin.command: "nomad var get secret/foobar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + changed_when: false + register: nomad_var_get + + - name: "Command nomad var purge" + ansible.builtin.command: "nomad var purge secret/foobar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + changed_when: false + register: nomad_var_purge + + - name: "Command nomad server members" + ansible.builtin.command: "nomad server members" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + changed_when: false + register: nomad_server_members + + - name: "Verify nomad interaction" + ansible.builtin.assert: + that: + - "'instance.global' in nomad_server_members.stdout" + - "'\"Items\": {\n \"foo\": \"bar\"\n }' in nomad_var_put.stdout" + - "'\"Items\": {\n \"foo\": \"bar\"\n }' in nomad_var_get.stdout" + - nomad_var_purge.stdout == 'Successfully purged variable \"secret/foobar\"!' diff --git a/molecule/nomad_with_acl_enabled/converge.yml b/molecule/nomad_with_acl_enabled/converge.yml new file mode 100644 index 0000000..ea6946e --- /dev/null +++ b/molecule/nomad_with_acl_enabled/converge.yml @@ -0,0 +1,8 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Include ednz_cloud.hashistack.nomad" + ansible.builtin.include_role: + name: "ednz_cloud.hashistack.nomad" diff --git a/molecule/nomad_with_acl_enabled/group_vars/all.yml b/molecule/nomad_with_acl_enabled/group_vars/all.yml new file mode 100644 index 0000000..627fe25 --- /dev/null +++ b/molecule/nomad_with_acl_enabled/group_vars/all.yml @@ -0,0 +1,12 @@ +--- +# defaults file for hashicorp_nomad + +##################### +# ACL configuration # +##################### + +nomad_acl_configuration: + enabled: true + token_ttl: 30s + policy_ttl: 60s + role_ttl: 60s diff --git a/molecule/nomad_with_acl_enabled/molecule.yml b/molecule/nomad_with_acl_enabled/molecule.yml new file mode 100644 index 0000000..e0b7ba7 --- /dev/null +++ b/molecule/nomad_with_acl_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: nomad_with_acl_enabled + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/nomad_with_acl_enabled/prepare.yml b/molecule/nomad_with_acl_enabled/prepare.yml new file mode 100644 index 0000000..c216743 --- /dev/null +++ b/molecule/nomad_with_acl_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/nomad_with_acl_enabled/requirements.yml b/molecule/nomad_with_acl_enabled/requirements.yml new file mode 100644 index 0000000..d3761e2 --- /dev/null +++ b/molecule/nomad_with_acl_enabled/requirements.yml @@ -0,0 +1,5 @@ +--- +# requirements file for molecule +roles: + - name: ednz_cloud.manage_repositories + - name: ednz_cloud.manage_apt_packages diff --git a/molecule/nomad_with_acl_enabled/verify.yml b/molecule/nomad_with_acl_enabled/verify.yml new file mode 100644 index 0000000..acce698 --- /dev/null +++ b/molecule/nomad_with_acl_enabled/verify.yml @@ -0,0 +1,185 @@ +--- +- name: Verify + hosts: all + gather_facts: true + become: true + tasks: + - name: "Test: nomad user and group" + block: + - name: "Getent user nomad" + ansible.builtin.getent: + database: passwd + key: nomad + register: nomad_user + + - name: "Getent group nomad" + ansible.builtin.getent: + database: group + key: nomad + register: nomad_group + + - name: "Verify nomad user and group" + ansible.builtin.assert: + that: + - not nomad_user.failed + - not nomad_group.failed + - "'nomad' in nomad_user.ansible_facts.getent_passwd.keys()" + - "'/home/nomad' in nomad_user.ansible_facts.getent_passwd['nomad']" + - "'/bin/false' in nomad_user.ansible_facts.getent_passwd['nomad']" + - "'nomad' in nomad_group.ansible_facts.getent_group.keys()" + + - name: "Test: binary /usr/local/bin/nomad" + block: + - name: "Stat binary /usr/local/bin/nomad" + ansible.builtin.stat: + path: "/usr/local/bin/nomad" + register: stat_usr_local_bin_nomad + + - name: "Verify binary /usr/local/bin/nomad" + ansible.builtin.assert: + that: + - stat_usr_local_bin_nomad.stat.exists + - stat_usr_local_bin_nomad.stat.isreg + - stat_usr_local_bin_nomad.stat.pw_name == 'root' + - stat_usr_local_bin_nomad.stat.gr_name == 'root' + - stat_usr_local_bin_nomad.stat.mode == '0755' + + - name: "Test: directory /etc/nomad.d" + block: + - name: "Stat directory /etc/nomad.d" + ansible.builtin.stat: + path: "/etc/nomad.d" + register: stat_etc_nomad_d + + - name: "Stat file /etc/nomad.d/nomad.env" + ansible.builtin.stat: + path: "/etc/nomad.d/nomad.env" + register: stat_etc_nomad_d_nomad_env + + - name: "Stat file /etc/nomad.d/nomad.json" + ansible.builtin.stat: + path: "/etc/nomad.d/nomad.json" + register: stat_etc_nomad_d_nomad_json + + - name: "Slurp file /etc/nomad.d/nomad.json" + ansible.builtin.slurp: + src: "/etc/nomad.d/nomad.json" + register: slurp_etc_nomad_d_nomad_json + + - name: "Verify directory /etc/nomad.d" + ansible.builtin.assert: + that: + - stat_etc_nomad_d.stat.exists + - stat_etc_nomad_d.stat.isdir + - stat_etc_nomad_d.stat.pw_name == 'nomad' + - stat_etc_nomad_d.stat.gr_name == 'nomad' + - stat_etc_nomad_d.stat.mode == '0755' + - stat_etc_nomad_d_nomad_env.stat.exists + - stat_etc_nomad_d_nomad_env.stat.isreg + - stat_etc_nomad_d_nomad_env.stat.pw_name == 'nomad' + - stat_etc_nomad_d_nomad_env.stat.gr_name == 'nomad' + - stat_etc_nomad_d_nomad_env.stat.mode == '0600' + - stat_etc_nomad_d_nomad_json.stat.exists + - stat_etc_nomad_d_nomad_json.stat.isreg + - stat_etc_nomad_d_nomad_json.stat.pw_name == 'nomad' + - stat_etc_nomad_d_nomad_json.stat.gr_name == 'nomad' + - stat_etc_nomad_d_nomad_json.stat.mode == '0600' + - slurp_etc_nomad_d_nomad_json.content != '' + + - name: "Test: directory /opt/nomad" + block: + - name: "Stat directory /opt/nomad" + ansible.builtin.stat: + path: "/opt/nomad" + register: stat_opt_nomad + + - name: "Verify directory /opt/nomad" + ansible.builtin.assert: + that: + - stat_opt_nomad.stat.exists + - stat_opt_nomad.stat.isdir + - stat_opt_nomad.stat.pw_name == 'nomad' + - stat_opt_nomad.stat.gr_name == 'nomad' + - stat_opt_nomad.stat.mode == '0755' + + - name: "Test: service nomad" + block: + - name: "Get service nomad" + ansible.builtin.service_facts: + + - name: "Stat file /etc/systemd/system/nomad.service" + ansible.builtin.stat: + path: "/etc/systemd/system/nomad.service" + register: stat_etc_systemd_system_nomad_service + + - name: "Slurp file /etc/systemd/system/nomad.service" + ansible.builtin.slurp: + src: "/etc/systemd/system/nomad.service" + register: slurp_etc_systemd_system_nomad_service + + - name: "Verify service nomad" + ansible.builtin.assert: + that: + - stat_etc_systemd_system_nomad_service.stat.exists + - stat_etc_systemd_system_nomad_service.stat.isreg + - stat_etc_systemd_system_nomad_service.stat.pw_name == 'root' + - stat_etc_systemd_system_nomad_service.stat.gr_name == 'root' + - stat_etc_systemd_system_nomad_service.stat.mode == '0644' + - slurp_etc_systemd_system_nomad_service.content != '' + - ansible_facts.services['nomad.service'] is defined + - ansible_facts.services['nomad.service']['source'] == 'systemd' + - ansible_facts.services['nomad.service']['state'] == 'running' + - ansible_facts.services['nomad.service']['status'] == 'enabled' + + - name: "Test: bootstrap acl nomad" + block: + - name: "Command nomad acl bootstrap" + ansible.builtin.command: "nomad acl bootstrap -json" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + changed_when: false + register: nomad_acl_bootstrap + + - name: "Test: interaction nomad" + vars: + acl_token: "{{ nomad_acl_bootstrap.stdout|from_json|json_query('SecretID') }}" + block: + - name: "Command nomad var put" + ansible.builtin.command: "nomad var put secret/foobar foo=bar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + NOMAD_TOKEN: "{{ acl_token }}" + changed_when: false + register: nomad_var_put + + - name: "Command nomad var get" + ansible.builtin.command: "nomad var get secret/foobar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + NOMAD_TOKEN: "{{ acl_token }}" + changed_when: false + register: nomad_var_get + + - name: "Command nomad var purge" + ansible.builtin.command: "nomad var purge secret/foobar" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + NOMAD_TOKEN: "{{ acl_token }}" + changed_when: false + register: nomad_var_purge + + - name: "Command nomad server members" + ansible.builtin.command: "nomad server members" + environment: + NOMAD_ADDR: "http://{{ ansible_default_ipv4.address }}:4646" + NOMAD_TOKEN: "{{ acl_token }}" + changed_when: false + register: nomad_server_members + + - name: "Verify nomad interaction" + ansible.builtin.assert: + that: + - "'instance.global' in nomad_server_members.stdout" + - "'\"Items\": {\n \"foo\": \"bar\"\n }' in nomad_var_put.stdout" + - "'\"Items\": {\n \"foo\": \"bar\"\n }' in nomad_var_get.stdout" + - nomad_var_purge.stdout == 'Successfully purged variable \"secret/foobar\"!' 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/playbooks/bootstrap.yml b/playbooks/bootstrap.yml index b190b9c..1379e86 100644 --- a/playbooks/bootstrap.yml +++ b/playbooks/bootstrap.yml @@ -1,10 +1,19 @@ --- # hashistack prepare playbook - name: "Bootstrap" - hosts: all + hosts: all, !deployment gather_facts: true become: true tasks: + - name: "Isntall unzip with package manager" + ansible.builtin.include_role: + name: ednz_cloud.manage_apt_packages + vars: + manage_apt_packages_list: + - name: unzip + version: latest + state: present + - name: "Install hvac library with pip" ansible.builtin.include_role: name: ednz_cloud.manage_pip_packages @@ -27,20 +36,6 @@ state: present when: "'haproxy_servers' in group_names" - - name: "Include ednz_cloud.install_docker" - ansible.builtin.include_role: - name: ednz_cloud.install_docker - vars: - install_docker_edition: ce - install_docker_auto_update: false - install_docker_start_service: true - install_docker_compose: false - install_docker_python_packages: false - install_docker_users: - - "{{ ansible_user }}" - install_docker_daemon_options: {} - #! when: "'nomad_agents' in group_names" - - name: "Ensure /etc/localtime exists" ansible.builtin.file: src: /etc/timezone diff --git a/playbooks/deploy.yml b/playbooks/deploy.yml index 6e45910..4e2f613 100644 --- a/playbooks/deploy.yml +++ b/playbooks/deploy.yml @@ -1,68 +1,55 @@ --- # hashistack deployment playbook - name: "Deploy" - hosts: all + hosts: "{{ target | default('all, !deployment') }}" strategy: linear gather_facts: true + any_errors_fatal: true become: true tasks: - name: "Import variables" - ansible.builtin.import_tasks: - file: tasks/load_vars.yml + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack tags: - always + # Consul nodes deployment - name: "Deploy Consul" tags: - consul when: - enable_consul | bool - block: - - name: "Deploy Consul Control Plane" - ansible.builtin.import_tasks: - file: tasks/consul/consul_deploy.yml - when: - - "'consul_servers' in group_names" - - - name: "Deploy Consul Agents" - ansible.builtin.include_role: - name: ednz_cloud.hashicorp_consul - when: - - "'consul_agents' in group_names" - - - name: "Deploy Haproxy & Keepalived" - ansible.builtin.import_tasks: - file: tasks/haproxy/haproxy_deploy.yml - when: - - enable_haproxy | bool - - "'haproxy_servers' in group_names" - tags: - - haproxy + ansible.builtin.include_tasks: + file: tasks/consul/consul_deploy.yml + # Vault nodes deployment - name: "Deploy Vault" - ansible.builtin.import_tasks: - file: tasks/vault/vault_deploy.yml - when: - - enable_vault | bool - - "'vault_servers' in group_names" tags: - vault + when: + - enable_vault | bool + ansible.builtin.include_tasks: + file: tasks/vault/vault_deploy.yml + # Nomad nodes deployment - name: "Deploy Nomad" tags: - nomad when: - enable_nomad | bool - block: - - name: "Deploy Nomad Control Plane" - ansible.builtin.import_tasks: - file: tasks/nomad/nomad_deploy.yml - when: - - "('nomad_servers' in group_names)" + ansible.builtin.include_tasks: + file: tasks/nomad/nomad_deploy.yml - - name: "Deploy Nomad Clients" - ansible.builtin.include_role: - name: ednz_cloud.hashicorp_nomad - when: - - "('nomad_clients' in group_names)" - - "('nomad_servers' not in group_names)" + # - fail: + # Haproxy nodes deployment + # - name: "Deploy Proxies" + # tags: + # - haproxy + # when: + # - enable_haproxy | bool + # block: + # - name: "Deploy Haproxy & Keepalived" + # ansible.builtin.import_tasks: + # file: tasks/haproxy/haproxy_deploy.yml + # when: + # - "'haproxy_servers' in group_names" diff --git a/playbooks/generate_certs.yml b/playbooks/generate_certs.yml index 2f27eac..5da90dc 100644 --- a/playbooks/generate_certs.yml +++ b/playbooks/generate_certs.yml @@ -1,365 +1,21 @@ --- # hashistack generate certificates playbook - name: "Generate certificates" - hosts: all + hosts: all, !deployment strategy: linear gather_facts: true become: true tasks: - name: "Import variables" - ansible.builtin.import_tasks: - file: tasks/load_vars.yml + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack tags: - always - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/external" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - delegate_to: localhost - run_once: true - - - name: "Generate external certificates" # noqa: run-once[task] - delegate_to: localhost - run_once: true - block: - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/external" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - - - name: "Create private keys" - community.crypto.openssl_privatekey: - path: "{{ sub_configuration_directories['certificates'] }}/external/{{ item.fqdn }}.pem.key" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - loop: - - name: nomad - fqdn: "{{ nomad_fqdn }}" - - name: vault - fqdn: "{{ vault_fqdn }}" - - name: consul - fqdn: "{{ consul_fqdn }}" - - - name: "Create certificate signing request" - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ sub_configuration_directories['certificates'] }}/external/{{ item.fqdn }}.pem.key" - common_name: "{{ item.fqdn }}" - organization_name: EDNZ Cloud - register: csr - loop: - - name: nomad - fqdn: "{{ nomad_fqdn }}" - - name: vault - fqdn: "{{ vault_fqdn }}" - - name: consul - fqdn: "{{ consul_fqdn }}" - - - name: "Create self-signed certificate from CSR" - community.crypto.x509_certificate: - path: "{{ sub_configuration_directories['certificates'] }}/external/{{ item.item.fqdn }}.pem" - csr_content: "{{ item.csr }}" - privatekey_path: "{{ sub_configuration_directories['certificates'] }}/external/{{ item.item.fqdn }}.pem.key" - provider: selfsigned - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - loop: "{{ csr.results }}" - - - name: "Generate internal certificates" + - name: "Create Certificate Authority" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack_ca + apply: + delegate_to: localhost tags: - - never - - internal - delegate_to: localhost - vars: - hashistack_ca_key_path: "{{ sub_configuration_directories['certificates'] }}/ca/ca.key" - hashistack_ca_cert_path: "{{ sub_configuration_directories['certificates'] }}/ca/ca.crt" - block: - - name: "Create internal CA" # noqa: run-once[task] - run_once: true - block: - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/ca" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - - - name: "Create CA private key" - community.crypto.openssl_privatekey: - path: "{{ hashistack_ca_key_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - - - name: "Create CA signing request" - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ hashistack_ca_key_path }}" - common_name: "CA" - organization_name: EDNZ Cloud - use_common_name_for_san: false - basic_constraints: - - CA:TRUE - basic_constraints_critical: true - key_usage: - - keyCertSign - key_usage_critical: true - register: ca_csr - - - name: "Create self-signed CA certificate from CSR" - community.crypto.x509_certificate: - path: "{{ hashistack_ca_cert_path }}" - csr_content: "{{ ca_csr.csr }}" - privatekey_path: "{{ hashistack_ca_key_path }}" - provider: selfsigned - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - - - name: "Create Vault certificates" - when: - - "'vault_servers' in group_names" - vars: - vault_private_key_path: "{{ sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}/key.pem" - vault_certificate_path: "{{ sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}/cert.pem" - block: - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - - - name: "Create Vault certificate keys" - community.crypto.openssl_privatekey: - path: "{{ vault_private_key_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - - - name: "Create CSRs for Vault servers" - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ vault_private_key_path }}" - common_name: "{{ inventory_hostname }}" - subject_alt_name: - - "DNS:{{ inventory_hostname }}" - - "DNS:active.vault.service.consul" - - "DNS:standby.vault.service.consul" - - "DNS:vault.service.consul" - - "DNS:localhost" - - "IP:{{ api_interface_address }}" - - "IP:127.0.0.1" - key_usage_critical: true - key_usage: - - Digital Signature - - Key Encipherment - - Key Agreement - extended_key_usage: - - TLS Web Server Authentication - - TLS Web Client Authentication - organization_name: EDNZ Cloud - use_common_name_for_san: false - register: vault_csr - - - name: "Sign certificates with internal CA" - community.crypto.x509_certificate: - path: "{{ vault_certificate_path }}" - csr_content: "{{ vault_csr.csr }}" - provider: ownca - ownca_path: "{{ hashistack_ca_cert_path }}" - ownca_privatekey_path: "{{ hashistack_ca_key_path }}" - ownca_not_after: "+365d" - ownca_not_before: "-1d" - - - name: "Concatenate CA and Child certificates" - block: - - name: "Read content of ca.crt" - ansible.builtin.slurp: - src: "{{ hashistack_ca_cert_path }}" - register: ca_crt_content - - - name: "Read content of cert.pem" - ansible.builtin.slurp: - src: "{{ vault_certificate_path }}" - register: cert_pem_content - - - name: "Concatenate certificates" - ansible.builtin.copy: - content: | - {{ cert_pem_content['content'] | b64decode }}{{ ca_crt_content['content'] | b64decode }} - dest: "{{ vault_certificate_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0644" - - - name: "Create Consul certificates" - when: - - "('consul_servers' in group_names) or ('consul_agents' in group_names)" - vars: - consul_private_key_path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}/key.pem" - consul_certificate_path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}/cert.pem" - block: - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - - - name: "Create Consul certificate keys" - community.crypto.openssl_privatekey: - path: "{{ consul_private_key_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - - - name: "Create CSRs for Consul servers" - vars: - consul_csr_sans: >- - {%- set sans_list = [ - 'DNS:' + inventory_hostname, - 'DNS:consul.service.consul', - 'DNS:localhost', - 'IP:' + api_interface_address, - 'IP:127.0.0.1' - ] -%} - {%- if hashicorp_consul_configuration.server -%} - {%- set _ = sans_list.append('DNS:server.' ~ hashicorp_consul_configuration.datacenter ~ '.' ~ hashicorp_consul_configuration.domain) -%} - {%- endif -%} - {{ sans_list }} - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ consul_private_key_path }}" - common_name: "{{ inventory_hostname }}" - subject_alt_name: "{{ consul_csr_sans }}" - key_usage_critical: true - key_usage: - - Digital Signature - - Key Encipherment - - Key Agreement - extended_key_usage: - - TLS Web Server Authentication - - TLS Web Client Authentication - organization_name: EDNZ Cloud - use_common_name_for_san: false - register: consul_csr - - - name: "Sign certificates with internal CA" - community.crypto.x509_certificate: - path: "{{ consul_certificate_path }}" - csr_content: "{{ consul_csr.csr }}" - provider: ownca - ownca_path: "{{ hashistack_ca_cert_path }}" - ownca_privatekey_path: "{{ hashistack_ca_key_path }}" - ownca_not_after: "+365d" - ownca_not_before: "-1d" - - - name: "Concatenate CA and Child certificates" - block: - - name: "Read content of ca.crt" - ansible.builtin.slurp: - src: "{{ hashistack_ca_cert_path }}" - register: ca_crt_content - - - name: "Read content of cert.pem" - ansible.builtin.slurp: - src: "{{ consul_certificate_path }}" - register: cert_pem_content - - - name: "Concatenate certificates" - ansible.builtin.copy: - content: | - {{ cert_pem_content['content'] | b64decode }}{{ ca_crt_content['content'] | b64decode }} - dest: "{{ consul_certificate_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0644" - - - name: "Create Nomad certificates" - when: - - "('nomad_servers' in group_names) or ('nomad_clients' in group_names)" - vars: - nomad_private_key_path: "{{ sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}/key.pem" - nomad_certificate_path: "{{ sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}/cert.pem" - block: - - name: "Create temporary cert directory in {{ sub_configuration_directories['certificates'] }}" # noqa: run-once[task] - ansible.builtin.file: - path: "{{ sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}" - state: directory - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0755" - - - name: "Create Nomad certificate keys" - community.crypto.openssl_privatekey: - path: "{{ nomad_private_key_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - - - name: "Create CSRs for Nomad servers" - vars: - nomad_csr_sans: >- - {%- set sans_list = [ - 'DNS:' + inventory_hostname, - 'DNS:localhost', - 'IP:' + api_interface_address, - 'IP:127.0.0.1' - ] -%} - {%- if hashicorp_nomad_configuration.server.enabled -%} - {%- set _ = sans_list.append('DNS:server.' ~ hashicorp_nomad_configuration.region ~ '.nomad') -%} - {%- if (enable_consul | bool) -%} - {%- set _ = sans_list.append('DNS:nomad.service.consul') -%} - {%- endif -%} - {%- endif -%} - {%- if hashicorp_nomad_configuration.client.enabled -%} - {%- set _ = sans_list.append('DNS:client.' ~ hashicorp_nomad_configuration.region ~ '.nomad') -%} - {%- endif -%} - {{ sans_list }} - community.crypto.openssl_csr_pipe: - privatekey_path: "{{ nomad_private_key_path }}" - common_name: "{{ inventory_hostname }}" - subject_alt_name: "{{ nomad_csr_sans }}" - key_usage_critical: true - key_usage: - - Digital Signature - - Key Encipherment - extended_key_usage: - - TLS Web Server Authentication - - TLS Web Client Authentication - organization_name: EDNZ Cloud - use_common_name_for_san: false - register: nomad_csr - - - name: "Sign certificates with internal CA" - community.crypto.x509_certificate: - path: "{{ nomad_certificate_path }}" - csr_content: "{{ nomad_csr.csr }}" - provider: ownca - ownca_path: "{{ hashistack_ca_cert_path }}" - ownca_privatekey_path: "{{ hashistack_ca_key_path }}" - ownca_not_after: "+365d" - ownca_not_before: "-1d" - - - name: "Concatenate CA and Child certificates" - block: - - name: "Read content of ca.crt" - ansible.builtin.slurp: - src: "{{ hashistack_ca_cert_path }}" - register: ca_crt_content - - - name: "Read content of cert.pem" - ansible.builtin.slurp: - src: "{{ nomad_certificate_path }}" - register: cert_pem_content - - - name: "Concatenate certificates" - ansible.builtin.copy: - content: | - {{ cert_pem_content['content'] | b64decode }}{{ ca_crt_content['content'] | b64decode }} - dest: "{{ nomad_certificate_path }}" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0644" + - always diff --git a/playbooks/generate_credentials.yml b/playbooks/generate_credentials.yml index c6133ff..a705d11 100644 --- a/playbooks/generate_credentials.yml +++ b/playbooks/generate_credentials.yml @@ -1,7 +1,7 @@ --- # hashistack generate certificates playbook - name: "Generate credentials" - hosts: localhost + hosts: deployment strategy: linear gather_facts: true become: true @@ -9,8 +9,19 @@ - name: "Generate consul credentials" block: - name: "Generate consul gossip encryption key" - ansible.builtin.set_fact: - _consul_gossip_encryption_key: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | b64encode }}" + block: + - name: "Generate 24 random bytes and base64 encode" + ansible.builtin.shell: + cmd: | + set -o pipefail + dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 + executable: /bin/bash + changed_when: false + register: _consul_random_base64_string + + - name: "Generate consul gossip encryption key" + ansible.builtin.set_fact: + _consul_gossip_encryption_key: "{{ _consul_random_base64_string.stdout }}" - name: "Generate consul root credentials" ansible.builtin.set_fact: @@ -39,8 +50,19 @@ - name: "Generate nomad credentials" block: - name: "Generate nomad gossip encryption key" - ansible.builtin.set_fact: - _nomad_gossip_encryption_key: "{{ lookup('ansible.builtin.password', '/dev/null', chars=['ascii_letters','digits']) | b64encode }}" + block: + - name: "Generate 24 random bytes and base64 encode" + ansible.builtin.shell: + cmd: | + set -o pipefail + dd if=/dev/urandom bs=24 count=1 2>/dev/null | base64 + executable: /bin/bash + changed_when: false + register: _nomad_random_base64_string + + - name: "Generate nomad gossip encryption key" + ansible.builtin.set_fact: + _nomad_gossip_encryption_key: "{{ _nomad_random_base64_string.stdout }}" - name: "Generate nomad root credentials" ansible.builtin.set_fact: @@ -48,7 +70,7 @@ - name: "Ensure secrets directory is created" ansible.builtin.file: - path: "{{ sub_configuration_directories['secrets'] }}" + path: "{{ hashistack_sub_configuration_directories['secrets'] }}" state: directory owner: "{{ lookup('env', 'USER') }}" group: "{{ lookup('env', 'USER') }}" @@ -57,7 +79,7 @@ - name: "Write credentials file" ansible.builtin.template: src: templates/credentials.yml.j2 - dest: "{{ sub_configuration_directories['secrets'] }}/{{ configuration_credentials_vars_file }}" + dest: "{{ hashistack_sub_configuration_directories['secrets'] }}/{{ hashistack_configuration_credentials_vars_file }}" owner: "{{ lookup('env', 'USER') }}" group: "{{ lookup('env', 'USER') }}" mode: '0644' diff --git a/playbooks/group_vars/all/all.yml b/playbooks/group_vars/all/all.yml index 8e95eb1..0303894 100644 --- a/playbooks/group_vars/all/all.yml +++ b/playbooks/group_vars/all/all.yml @@ -5,24 +5,19 @@ manage_pip_packages_allow_break_system_packages: "{{ ansible_distribution == 'Debian' and ansible_distribution_version == '12' }}" -configuration_directory: "{{ lookup('env', 'PWD') }}/etc/hashistack" -sub_configuration_directories: - secrets: "{{ configuration_directory }}/secrets" - certificates: "{{ configuration_directory }}/certificates" - nomad_servers: "{{ configuration_directory }}/nomad_servers" - vault_servers: "{{ configuration_directory }}/vault_servers" - consul_servers: "{{ configuration_directory }}/consul_servers" +hashistack_configuration_directory: "{{ lookup('env', 'PWD') }}/etc/hashistack" +hashistack_sub_configuration_directories: + secrets: "{{ hashistack_configuration_directory }}/secrets" + certificates: "{{ hashistack_configuration_directory }}/certificates" + nomad_servers: "{{ hashistack_configuration_directory }}/nomad_servers" + vault_servers: "{{ hashistack_configuration_directory }}/vault_servers" + consul_servers: "{{ hashistack_configuration_directory }}/consul_servers" -configuration_global_vars_file: "globals.yml" -configuration_credentials_vars_file: "credentials.yml" +hashistack_configuration_global_vars_file: "globals.yml" +hashistack_configuration_credentials_vars_file: "credentials.yml" hashistack_remote_config_dir: "/etc/hashistack" -hashistack_remote_data_dir: "/opt/hashistack" - -default_container_extra_volumes: - - "/etc/timezone:/etc/timezone" - - "/etc/localtime:/etc/localtime" - - "/etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro" +hashistack_remote_log_dir: "/var/log/hashistack" ################### # support options # @@ -44,4 +39,6 @@ preflight_enable_host_ntp_checks: true haproxy_required_ports: [80, 443] vault_required_ports: [8200, 8201] consul_required_ports: [8300, 8301, 8302, 8500, 8501, 8502, 8503, 8600] -nomad_required_ports: [] +nomad_required_ports: [4646, 4647, 4648] + +target: all, !deployment diff --git a/playbooks/group_vars/all/cni.yml b/playbooks/group_vars/all/cni.yml new file mode 100644 index 0000000..5536b6d --- /dev/null +++ b/playbooks/group_vars/all/cni.yml @@ -0,0 +1,4 @@ +--- +cni_plugins_version: "v1.5.1" +cni_plugins_install_path: /opt/cni/bin +cni_plugins_install_consul_cni: true diff --git a/playbooks/group_vars/all/consul.yml b/playbooks/group_vars/all/consul.yml index b633496..918204c 100644 --- a/playbooks/group_vars/all/consul.yml +++ b/playbooks/group_vars/all/consul.yml @@ -1,9 +1,5 @@ --- -##################################################### -# # -# Non-Editable # -# # -##################################################### +consul_init_server: "{{ (inventory_hostname == groups['consul_servers'][0]) | bool }}" ##################### # consul api config # @@ -14,36 +10,106 @@ consul_api_scheme: "{{ 'https' if consul_enable_tls else 'http' }}" consul_api_port: http: 8500 https: 8501 +consul_grpc_port: + http: 8502 + https: 8503 -########################## -# consul haproxy backend # -########################## +########## +# Consul # +########## -consul_haproxy_frontend_options: - - acl is_consul hdr(host) -i {{ consul_fqdn }} - - use_backend consul_external if is_consul +consul_config_dir: "{{ hashistack_remote_config_dir }}/consul.d" +consul_data_dir: "/opt/consul" +consul_certs_dir: "{{ consul_config_dir }}/tls" +consul_logs_dir: "{{ hashistack_remote_log_dir }}/consul" -consul_haproxy_backends: - - name: consul_external - options: "{{ consul_external_backend_options + consul_external_backend_servers }}" +consul_envoy_install: false +consul_envoy_version: latest -consul_external_backend_options: - - description consul external http backend - - option forwardfor - - option httpchk - - http-check send meth GET uri / - - default-server inter 2s fastinter 1s downinter 1s +consul_extra_files: true +# consul_extra_files_list: [] -consul_external_backend_servers: | - [ - {% for host in groups['consul_servers'] %} - 'server consul-{{ hostvars[host].api_interface_address }} {{ hostvars[host].api_interface_address }}:{{ hostvars[host].consul_api_port[consul_api_scheme] }} check {{ 'ssl verify none ' if consul_enable_tls }}inter 5s'{% if not loop.last %},{% endif %} - {% endfor %} - ] +consul_env_variables: {} -############################ -# consul ACL configuration # -############################ +####################### +# 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. + +# consul_extra_configuration: {} + +########### +# general # +########### + +# consul_domain: consul +# consul_datacenter: dc1 +# consul_primary_datacenter: "{{ consul_datacenter }}" +# consul_gossip_encryption_key: "{{ _credentials.consul.gossip_encryption_key }}" +# consul_enable_script_checks: false + +####################### +# leave configuration # +####################### + +consul_leave_on_terminate: true +consul_rejoin_after_leave: true + +###################### +# join configuration # +###################### + +consul_join_configuration: + retry_join: | + {{ + groups['consul_servers'] | + map('extract', hostvars, ['consul_address_configuration', 'bind_addr']) | + list | + to_json | + from_json + }} + retry_interval: 30s + retry_max: 0 + +######################## +# server configuration # +######################## + +consul_enable_server: "{{ 'consul_servers' in group_names }}" +consul_bootstrap_expect: "{{ (groups['consul_servers'] | length) }}" + +#################### +# ui configuration # +#################### + +consul_ui_configuration: + enabled: "{{ consul_enable_server }}" + +######################### +# address configuration # +######################### + +consul_bind_addr: "0.0.0.0" +consul_advertise_addr: "{{ api_interface_address }}" +consul_address_configuration: + client_addr: "{{ consul_bind_addr }}" + bind_addr: "{{ consul_advertise_addr }}" + advertise_addr: "{{ consul_advertise_addr }}" + +##################### +# ACL configuration # +##################### + +consul_acl_configuration: + enabled: true + default_policy: "deny" + enable_token_persistence: true + tokens: + agent: "{{ _credentials.consul.tokens.agent.secret_id }}" consul_default_agent_policy: | node_prefix "" { @@ -53,73 +119,61 @@ consul_default_agent_policy: | policy = "read" } -####################### -# consul internal tls # -####################### +############################## +# service mesh configuration # +############################## -consul_certificates_directory: "{{ hashicorp_consul_config_dir }}/tls" -consul_certificates_extra_files_dir: - - src: "{{ sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}" - dest: "{{ consul_certificates_directory }}" +consul_mesh_configuration: + enabled: true -######################### -# consul role variables # -######################### +##################### +# DNS configuration # +##################### -hashicorp_consul_start_service: true -hashicorp_consul_service_name: "consul" -hashicorp_consul_version: "{{ consul_version }}" -hashicorp_consul_env_variables: {} -hashicorp_consul_config_dir: "/etc/consul.d" -hashicorp_consul_data_dir: "/opt/consul" -hashicorp_consul_extra_files: true -hashicorp_consul_extra_files_list: "{{ ([] + - (consul_certificates_extra_files_dir if consul_enable_tls else []) + - consul_extra_files_list) - | unique - | sort - }}" -hashicorp_consul_envoy_install: false -hashicorp_consul_envoy_version: v1.27.2 -hashicorp_consul_configuration: - domain: "{{ consul_domain }}" - datacenter: "{{ consul_datacenter }}" - primary_datacenter: "{{ consul_primary_datacenter }}" - data_dir: "{{ hashicorp_consul_data_dir }}" - encrypt: "{{ _credentials.consul.gossip_encryption_key }}" - server: "{{ 'consul_servers' in group_names }}" - retry_join: "{{ - groups['consul_servers'] | - map('extract', hostvars, ['consul_address_configuration', 'bind_addr']) | - list | - to_json | - from_json - }}" - ui_config: "{{ consul_ui_configuration }}" - connect: "{{ consul_mesh_configuration }}" - leave_on_terminate: "{{ consul_leave_on_terminate }}" - rejoin_after_leave: "{{ consul_rejoin_after_leave }}" - enable_script_checks: "{{ consul_enable_script_checks }}" - enable_syslog: true - acl: "{{ consul_acl_configuration }}" - dns_config: "{{ consul_dns_configuration }}" - ports: - dns: 8600 - server: 8300 - serf_lan: 8301 - serf_wan: 8302 - sidecar_min_port: 21000 - sidecar_max_port: 21255 - expose_min_port: 21500 - expose_max_port: 21755 +consul_dns_configuration: + allow_stale: true + enable_truncate: true + only_passing: true -# this is used to circumvent jinja limitation to convert string to integer -hashicorp_consul_configuration_string: | - ports: - http: {{ (consul_api_port.http|int) if not consul_enable_tls else ('-1' | int) }} - https: {{ (consul_api_port.https|int) if consul_enable_tls else ('-1' | int) }} - grpc: {{ ('8502'|int) if not consul_enable_tls else ('-1' | int) }} - grpc_tls: {{ ('8503'|int) if consul_enable_tls else ('-1' | int) }} +################ +# internal tls # +################ -hashicorp_consul_servers_configuration_string: | - bootstrap_expect: {{ (groups['consul_servers'] | length) }} +# consul_enable_tls: false +consul_tls_configuration: + defaults: + ca_file: "/etc/ssl/certs/ca-certificates.crt" + cert_file: "{{ consul_certs_dir }}/fullchain.crt" + key_file: "{{ consul_certs_dir }}/cert.key" + verify_incoming: false + verify_outgoing: true + internal_rpc: + verify_server_hostname: true + +consul_certificates_extra_files_dir: > + {{ + [] if external_tls_externally_managed_certs | bool else + [{ + 'src': "{{ hashistack_sub_configuration_directories['certificates'] }}/consul/{{ inventory_hostname }}", + 'dest': "{{ consul_certs_dir }}" + }] + }} + +########################### +# telemetry configuration # +########################### + +consul_enable_prometheus_metrics: false +consul_prometheus_retention_time: 60s +consul_telemetry_configuration: {} + +########### +# logging # +########### + +# consul_log_level: info +consul_enable_log_to_file: "{{ enable_log_to_file | bool }}" +consul_log_to_file_configuration: + log_file: "{{ consul_logs_dir }}/consul.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 diff --git a/playbooks/group_vars/all/globals.yml b/playbooks/group_vars/all/globals.yml index ddfae4f..359a20b 100644 --- a/playbooks/group_vars/all/globals.yml +++ b/playbooks/group_vars/all/globals.yml @@ -1,14 +1,13 @@ --- -########################## -# General options ######## -########################## +################### +# General options # +################### -enable_haproxy: "yes" +enable_ingress: "yes" enable_vault: "yes" enable_consul: "yes" enable_nomad: "yes" -haproxy_version: "2.8" nomad_version: "1.8.1" consul_version: "1.18.1" vault_version: "1.16.2" @@ -17,14 +16,20 @@ consul_fqdn: consul.ednz.lab vault_fqdn: vault.ednz.lab nomad_fqdn: nomad.ednz.lab -hashistack_external_vip_interface: "eth0" -hashistack_external_vip_addr: "192.168.121.100" -hashistack_internal_vip_interface: "{{ hashistack_external_vip_interface }}" -hashistack_internal_vip_addr: "{{ hashistack_external_vip_addr }}" +# hashistack_external_vip_interface: "eth0" +# hashistack_external_vip_addr: "192.168.121.100" +# hashistack_internal_vip_interface: "{{ hashistack_external_vip_interface }}" +# hashistack_internal_vip_addr: "{{ hashistack_external_vip_addr }}" api_interface: "eth0" api_interface_address: "{{ ansible_facts[api_interface]['ipv4']['address'] }}" +################### +# logging options # +################### + +enable_log_to_file: true + ######################## # external tls options # ######################## @@ -32,248 +37,72 @@ api_interface_address: "{{ ansible_facts[api_interface]['ipv4']['address'] }}" enable_tls_external: false external_tls_externally_managed_certs: false -##################################################### -# # -# Consul # -# # -##################################################### +######################## +# internal tls options # +######################## + +enable_tls_internal: false +internal_tls_externally_managed_certs: false + +########## +# Consul # +########## consul_domain: consul consul_datacenter: dc1 -consul_primary_datacenter: dc1 -consul_leave_on_terminate: true -consul_rejoin_after_leave: true -consul_enable_script_checks: true +consul_primary_datacenter: "{{ consul_datacenter }}" +consul_gossip_encryption_key: "{{ _credentials.consul.gossip_encryption_key }}" +consul_enable_script_checks: false -################################ -# consul address configuration # -################################ - -consul_address_configuration: - # The address to which Consul will bind client interfaces, - # including the HTTP and DNS servers. - client_addr: "0.0.0.0" - # The address that should be bound to for internal cluster communications. - bind_addr: "{{ api_interface_address }}" - # The advertise address is used to change the address that we advertise to other nodes in the cluster. - advertise_addr: "{{ api_interface_address }}" - -############################ -# consul ACL configuration # -############################ - -consul_acl_configuration: - enabled: true - default_policy: "deny" # can be allow or deny - enable_token_persistence: true - -############################ -# consul DNS configuration # -############################ - -consul_dns_configuration: - allow_stale: true - enable_truncate: true - only_passing: true - -########################### -# consul ui configuration # -########################### - -consul_ui_configuration: - enabled: "{{ 'consul_servers' in group_names }}" - -##################################### -# consul service mesh configuration # -##################################### - -consul_mesh_configuration: - enabled: true - -############################ -# consul tls configuration # -############################ - -consul_enable_tls: false -consul_tls_configuration: - defaults: - ca_file: "/etc/ssl/certs/ca-certificates.crt" - cert_file: "{{ consul_certificates_directory }}/cert.pem" - key_file: "{{ consul_certificates_directory }}/key.pem" - verify_incoming: false - verify_outgoing: true - internal_rpc: - verify_server_hostname: true - -############################ -# consul container volumes # -############################ - -extra_consul_container_volumes: [] - -############################## -# consul extra configuration # -############################## - -consul_extra_configuration: {} consul_extra_files_list: [] +consul_extra_configuration: {} -##################################################### -# # -# Vault # -# # -##################################################### +consul_enable_tls: "{{ enable_tls_internal }}" + +consul_log_level: info + +######### +# Vault # +######### vault_cluster_name: vault +vault_bind_addr: "0.0.0.0" +vault_cluster_addr: "{{ api_interface_address }}" vault_enable_ui: true -vault_seal_configuration: - key_shares: 3 - key_threshold: 2 +vault_disable_mlock: false +vault_disable_cache: false -################# -# vault storage # -################# +vault_extra_files_list: [] +vault_extra_configuration: {} -vault_storage_configuration: - raft: - path: "{{ hashicorp_vault_data_dir }}" - node_id: "{{ ansible_hostname }}" - retry_join: | - [ - {% for host in groups['vault_servers'] %} - { - 'leader_api_addr': '{{ "https" if vault_enable_tls else "http"}}://{{ hostvars[host].api_interface_address }}:8200' - }{% if not loop.last %},{% endif %} - {% endfor %} - ] - -################## -# vault listener # -################## - -vault_enable_tls: false -vault_tls_verify: false -vault_listener_configuration: - tcp: - address: "0.0.0.0:8200" - tls_disable: true - -vault_tls_listener_configuration: - tcp: - tls_disable: false - tls_cert_file: "{{ vault_certificates_directory }}/cert.pem" - tls_key_file: "{{ vault_certificates_directory }}/key.pem" - tls_disable_client_certs: true - -vault_extra_listener_configuration: {} - -######################## -# service registration # -######################## +vault_enable_tls: "{{ enable_tls_internal }}" vault_enable_service_registration: "{{ enable_consul | bool }}" -vault_service_registration_configuration: - consul: - address: "127.0.0.1:{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }}" - scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" - token: "{{ _credentials.consul.tokens.vault.secret_id }}" - -################# -# vault plugins # -################# vault_enable_plugins: false -########### -# logging # -########### +vault_log_level: info -vault_enable_log_to_file: false -vault_logging_configuration: - log_level: info - log_format: standard - log_rotate_duration: 24h - log_rotate_max_files: 30 +######### +# Nomad # +######### -########################### -# vault container volumes # -########################### - -extra_vault_container_volumes: [] - -############################# -# vault extra configuration # -############################# - -vault_extra_configuration: {} -vault_extra_files_list: [] - -##################################################### -# # -# Nomad # -# # -##################################################### - -nomad_datacenter: dc1 nomad_region: global +nomad_datacenter: dc1 -########################### -# nomad ACL configuration # -########################### +nomad_extra_files_list: [] +nomad_extra_configuration: {} -nomad_acl_configuration: - enabled: true - token_ttl: 30s - policy_ttl: 60s - role_ttl: 60s +nomad_autopilot_configuration: {} -############################ -# nomad consul integration # -############################ - -nomad_enable_consul_integration: "{{ enable_consul | bool }}" -nomad_consul_integration_configuration: - address: "127.0.0.1:{{ hashicorp_consul_configuration.ports.https if consul_enable_tls else hashicorp_consul_configuration.ports.http }}" - auto_advertise: true - ssl: "{{ consul_enable_tls | bool }}" - token: "{{ _credentials.consul.tokens.nomad.server.secret_id if nomad_enable_server else _credentials.consul.tokens.nomad.client.secret_id}}" - tags: [] - -############################ -# nomad vault integration # -############################ - -nomad_enable_vault_integration: false -nomad_vault_integration_configuration: {} - -############################### -# nomad drivers configuration # -############################### - -nomad_driver_enable_docker: yes -nomad_driver_enable_podman: no -nomad_driver_enable_raw_exec: no -nomad_driver_enable_java: no -nomad_driver_enable_qemu: no +nomad_driver_enable_docker: true +nomad_driver_enable_podman: false +nomad_driver_enable_raw_exec: false +nomad_driver_enable_java: false +nomad_driver_enable_qemu: false nomad_driver_extra_configuration: {} -###################### -# nomad internal tls # -###################### +nomad_log_level: info -nomad_enable_tls: false -nomad_tls_configuration: - http: true - rpc: true - ca_file: "/etc/ssl/certs/ca-certificates.crt" - cert_file: "{{ nomad_certificates_directory }}/cert.pem" - key_file: "{{ nomad_certificates_directory }}/key.pem" - verify_server_hostname: true - -############################# -# nomad extra configuration # -############################# - -nomad_extra_configuration: {} -nomad_extra_files_list: [] +nomad_enable_tls: "{{ enable_tls_internal }}" diff --git a/playbooks/group_vars/all/haproxy.yml b/playbooks/group_vars/all/haproxy.yml index a5ffef2..2986cfa 100644 --- a/playbooks/group_vars/all/haproxy.yml +++ b/playbooks/group_vars/all/haproxy.yml @@ -10,7 +10,7 @@ deploy_haproxy_version: "{{ haproxy_version }}" deploy_haproxy_env_variables: {} deploy_haproxy_start_service: true -deploy_haproxy_cert_dir: "{{ sub_configuration_directories['certificates']~'/external' if (enable_tls_external and not external_tls_externally_managed_certs) }}" +deploy_haproxy_cert_dir: "{{ hashistack_sub_configuration_directories['certificates']~'/external' if (enable_tls_external and not external_tls_externally_managed_certs) }}" deploy_haproxy_extra_container_volumes: [] deploy_haproxy_global: - log /dev/log local0 diff --git a/playbooks/group_vars/all/hashistack_ca.yml b/playbooks/group_vars/all/hashistack_ca.yml new file mode 100644 index 0000000..6321ac7 --- /dev/null +++ b/playbooks/group_vars/all/hashistack_ca.yml @@ -0,0 +1,130 @@ +--- +# defaults +hashistack_ca_directory: "/etc/hashistack/certificates" +hashistack_ca_use_cryptography: false +hashistack_ca_action: "noop" +hashistack_ca_domain: example.com +hashistack_ca_directory_owner: root + +############################## +# Root Certificate Authority # +############################## +hashistack_ca_root_org_name: EDNZ Cloud +hashistack_ca_root_country: FR +hashistack_ca_root_locality: Paris +hashistack_ca_root_common_name: "{{ hashistack_ca_domain }} Root CA" +hashistack_ca_root_email: +hashistack_ca_root_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_root_key_usage_critical: true +hashistack_ca_root_basic_constraints: + - CA:TRUE +hashistack_ca_root_basic_constraints_critical: true + +# Optional fields +hashistack_ca_root_state_or_province_name: +hashistack_ca_root_email_address: + +# Validity +hashistack_ca_root_valid_for: 1825d +hashistack_ca_root_renew_threshold: 180d + +###################################### +# Intermediate Certificate Authority # +###################################### +hashistack_ca_intermediate_org_name: EDNZ Cloud Intermediate +hashistack_ca_intermediate_country: FR +hashistack_ca_intermediate_locality: Paris +hashistack_ca_intermediate_common_name: "{{ hashistack_ca_domain }} Intermediate CA" +hashistack_ca_intermediate_email: +hashistack_ca_intermediate_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_intermediate_key_usage_critical: true +hashistack_ca_intermediate_basic_constraints: + - CA:TRUE + - pathlen:0 +hashistack_ca_intermediate_basic_constraints_critical: true + +# Optional fields +hashistack_ca_intermediate_state_or_province_name: +hashistack_ca_intermediate_email_address: + +# Validity +hashistack_ca_intermediate_valid_for: 365d +hashistack_ca_intermediate_renew_threshold: 90d + +# Name Constraints +hashistack_ca_intermediate_name_constraints_permitted: + - "DNS:.{{ hashistack_ca_domain }}" + - DNS:.nomad + - DNS:.consul + - DNS:localhost + - IP:192.168.0.0/16 + - IP:172.16.0.0/16 + - IP:10.0.0.0/8 + - IP:127.0.0.0/8 +hashistack_ca_intermediate_name_constraints_critical: "{{ (hashistack_ca_intermediate_name_constraints_permitted is defined and hashistack_ca_intermediate_name_constraints_permitted | length > 0) }}" + +##################### +# Leaf certificates # +##################### + +hashistack_ca_leaf_valid_for: 90d +hashistack_ca_leaf_renew_threshold: 30d + +############################ +# Consul Leaf Certificates # +############################ +hashistack_ca_consul_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_consul_common_name: "{{ inventory_hostname }}" +hashistack_ca_consul_csr_sans: >- + {%- set sans_list = [ + 'DNS:' + inventory_hostname, + 'DNS:consul.service.consul', + 'DNS:localhost', + 'IP:' + api_interface_address, + 'IP:127.0.0.1' + ] -%} + {%- if consul_enable_server -%} + {%- set _ = sans_list.append('DNS:server.' ~ consul_datacenter ~ '.' ~ consul_domain) -%} + {%- endif -%} + {{ sans_list }} + +########################### +# Nomad Leaf Certificates # +########################### +hashistack_ca_nomad_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_nomad_common_name: "{{ inventory_hostname }}" +hashistack_ca_nomad_csr_sans: >- + {%- set sans_list = [ + 'DNS:' + inventory_hostname, + 'DNS:localhost', + 'IP:' + api_interface_address, + 'IP:127.0.0.1' + ] -%} + {%- if nomad_enable_server -%} + {%- set _ = sans_list.append('DNS:server.' ~ nomad_region ~ '.nomad') -%} + {%- if (enable_consul | bool) -%} + {%- set _ = sans_list.append('DNS:nomad.service.consul') -%} + {%- endif -%} + {%- endif -%} + {%- if nomad_enable_client -%} + {%- set _ = sans_list.append('DNS:client.' ~ nomad_region ~ '.nomad') -%} + {%- endif -%} + {{ sans_list }} + +########################### +# Vault Leaf Certificates # +########################### +hashistack_ca_vault_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_vault_common_name: "{{ inventory_hostname }}" +hashistack_ca_vault_csr_sans: + - "DNS:{{ inventory_hostname }}" + - "DNS:active.vault.service.consul" + - "DNS:standby.vault.service.consul" + - "DNS:vault.service.consul" + - "DNS:localhost" + - "IP:{{ api_interface_address }}" + - "IP:127.0.0.1" diff --git a/playbooks/group_vars/all/nomad.yml b/playbooks/group_vars/all/nomad.yml index 2a4500e..642228f 100644 --- a/playbooks/group_vars/all/nomad.yml +++ b/playbooks/group_vars/all/nomad.yml @@ -1,9 +1,5 @@ --- -##################################################### -# # -# Non-Editable # -# # -##################################################### +nomad_init_server: "{{ (inventory_hostname == groups['nomad_servers'][0]) | bool }}" #################### # nomad api config # @@ -15,60 +11,188 @@ nomad_api_port: http: "{{ nomad_address_configuration.ports.http }}" https: "{{ nomad_address_configuration.ports.http }}" +######### +# Nomad # +######### + +nomad_config_dir: "{{ hashistack_remote_config_dir }}/nomad.d" +nomad_data_dir: "/opt/nomad" +nomad_certs_dir: "{{ nomad_config_dir }}/tls" +nomad_logs_dir: "{{ hashistack_remote_log_dir }}/nomad" + +nomad_extra_files: true +# nomad_extra_files_list: [] + +nomad_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. + +# nomad_extra_configuration: {} + +########### +# general # +########### + +# nomad_region: global +# nomad_datacenter: dc1 + ######################### -# nomad haproxy backend # +# address configuration # ######################### -nomad_haproxy_frontend_options: - - acl is_nomad hdr(host) -i {{ nomad_fqdn }} - - use_backend nomad_external if is_nomad - -nomad_haproxy_backends: - - name: nomad_external - options: "{{ nomad_external_backend_options + nomad_external_backend_servers }}" - -nomad_external_backend_options: - - description nomad external http backend - - option forwardfor - - option httpchk - - http-check send meth GET uri / - - default-server inter 2s fastinter 1s downinter 1s - -nomad_external_backend_servers: | - [ - {% for host in groups['nomad_servers'] %} - 'server nomad-{{ hostvars[host].api_interface_address }} {{ hostvars[host].api_interface_address }}:{{ hostvars[host].nomad_api_port[nomad_api_scheme] }} check {{ 'ssl verify none ' if nomad_enable_tls }}inter 5s'{% if not loop.last %},{% endif %} - {% endfor %} - ] - -############################### -# nomad address configuration # -############################### - +nomad_bind_addr: "0.0.0.0" +nomad_advertise_addr: "{{ api_interface_address }}" nomad_address_configuration: - bind_addr: "{{ api_interface_address }}" + bind_addr: "{{ nomad_bind_addr }}" addresses: - http: "{{ api_interface_address }}" - rpc: "{{ api_interface_address }}" - serf: "{{ api_interface_address }}" + http: "{{ nomad_advertise_addr }}" + rpc: "{{ nomad_advertise_addr }}" + serf: "{{ nomad_advertise_addr }}" advertise: - http: "{{ api_interface_address }}" - rpc: "{{ api_interface_address }}" - serf: "{{ api_interface_address }}" + http: "{{ nomad_advertise_addr }}" + rpc: "{{ nomad_advertise_addr }}" + serf: "{{ nomad_advertise_addr }}" ports: http: 4646 rpc: 4647 serf: 4648 -################################# -# nomad autopilot configuration # -################################# +########################### +# autopilot configuration # +########################### -nomad_autopilot_configuration: {} +# nomad_autopilot_configuration: {} -############################ -# nomad consul integration # -############################ +####################### +# leave configuration # +####################### + +nomad_leave_on_interrupt: false +nomad_leave_on_terminate: false + +######################## +# server configuration # +######################## + +nomad_enable_server: "{{ ('nomad_servers' in group_names) | bool }}" +nomad_server_bootstrap_expect: "{{ (groups['nomad_servers'] | length) }}" +nomad_server_configuration: + enabled: "{{ nomad_enable_server }}" + data_dir: "{{ nomad_data_dir }}/server" + encrypt: "{{ _credentials.nomad.gossip_encryption_key }}" + +############################## +# client configuration # +############################## + +nomad_enable_client: "{{ ('nomad_clients' in group_names) | bool }}" +nomad_client_configuration: + enabled: "{{ nomad_enable_client }}" + state_dir: "{{ nomad_data_dir }}/client" + cni_path: "{{ cni_plugins_install_path | default('/opt/cni/bin') }}" + bridge_network_name: nomad + bridge_network_subnet: "172.26.64.0/20" + +#################### +# ui configuration # +#################### + +nomad_ui_configuration: + enabled: "{{ nomad_enable_server }}" + +######################### +# drivers configuration # +######################### + +nomad_driver_enable_docker: true +nomad_driver_enable_podman: false +nomad_driver_enable_raw_exec: false +nomad_driver_enable_java: false +nomad_driver_enable_qemu: false + +nomad_driver_configuration: + raw_exec: + enabled: false + +nomad_driver_extra_configuration: {} + +########### +# logging # +########### + +nomad_log_level: info +nomad_enable_log_to_file: "{{ enable_log_to_file | bool }}" +nomad_log_to_file_configuration: + log_file: "{{ nomad_logs_dir }}/nomad.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 + +##################### +# ACL configuration # +##################### + +nomad_acl_configuration: + enabled: true + token_ttl: 30s + policy_ttl: 60s + role_ttl: 60s + +################ +# internal tls # +################ + +nomad_enable_tls: false +nomad_tls_configuration: + http: true + rpc: true + ca_file: "/etc/ssl/certs/ca-certificates.crt" + cert_file: "{{ nomad_certs_dir }}/fullchain.crt" + key_file: "{{ nomad_certs_dir }}/cert.key" + verify_server_hostname: true + +nomad_certificates_extra_files_dir: > + {{ + [] if external_tls_externally_managed_certs | bool else + [{ + 'src': "{{ hashistack_sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}", + 'dest': "{{ nomad_certs_dir }}" + }] + }} + +########################### +# telemetry configuration # +########################### + +nomad_telemetry_configuration: + collection_interval: 10s + disable_hostname: false + use_node_name: false + publish_allocation_metrics: false + publish_node_metrics: false + prefix_filter: [] + disable_dispatched_job_summary_metrics: false + prometheus_metrics: false + +###################### +# consul integration # +###################### + +nomad_enable_consul_integration: "{{ enable_consul | bool }}" +nomad_consul_integration_configuration: + address: >- + 127.0.0.1:{{ consul_api_port[consul_api_scheme] }} + auto_advertise: true + ssl: "{{ consul_enable_tls | bool }}" + token: >- + {{ _credentials.consul.tokens.nomad.server.secret_id if nomad_enable_server else _credentials.consul.tokens.nomad.client.secret_id }} + tags: [] nomad_consul_integration_tls_configuration: ca_file: "/etc/ssl/certs/ca-certificates.crt" @@ -78,7 +202,8 @@ nomad_consul_integration_server_configuration: nomad_consul_integration_client_configuration: client_auto_join: true - grpc_address: "127.0.0.1:{{ hashicorp_consul_configuration.ports.grpc_tls if consul_enable_tls else hashicorp_consul_configuration.ports.grpc }}" + grpc_address: >- + 127.0.0.1:{{ consul_grpc_port[consul_api_scheme] }} nomad_consul_integration_client_tls_configuration: grpc_ca_file: "/etc/ssl/certs/ca-certificates.crt" @@ -107,93 +232,9 @@ nomad_consul_integration_client_policy: | policy = "write" } -############################# -# nomad leave configuration # -############################# +############################ +# nomad vault integration # +############################ -# node will leave the cluster if the process is stopped -# and if it is only a client -nomad_leave_on_interrupt: "{{ (('nomad_clients' in group_names) and not ('nomad_servers' in group_names)) | bool }}" -nomad_leave_on_terminate: "{{ (('nomad_clients' in group_names) and not ('nomad_servers' in group_names)) | bool }}" - -########################## -# nomad ui configuration # -########################## - -nomad_ui_configuration: - enabled: "{{ ('nomad_servers' in group_names) | bool }}" - -############################## -# nomad server configuration # -############################## - -nomad_enable_server: "{{ ('nomad_servers' in group_names) | bool }}" -nomad_server_configuration: - enabled: "{{ nomad_enable_server }}" - data_dir: "{{ hashicorp_nomad_data_dir }}/server" - encrypt: "{{ _credentials.nomad.gossip_encryption_key }}" - -############################## -# nomad client configuration # -############################## - -nomad_enable_client: "{{ ('nomad_clients' in group_names) | bool }}" -nomad_client_configuration: - enabled: "{{ nomad_enable_client }}" - state_dir: "{{ hashicorp_nomad_data_dir }}/client" - bridge_network_name: nomad - bridge_network_subnet: "172.26.64.0/20" - -############################### -# nomad drivers configuration # -############################### - -nomad_driver_configuration: - raw_exec: - enabled: "{{ nomad_driver_enable_raw_exec | bool }}" - -###################### -# nomad internal tls # -###################### - -nomad_certificates_directory: "{{ hashicorp_nomad_config_dir }}/tls" -nomad_certificates_extra_files_dir: - - src: "{{ sub_configuration_directories['certificates'] }}/nomad/{{ inventory_hostname }}" - dest: "{{ nomad_certificates_directory }}" - -######################## -# nomad role variables # -######################## - -hashicorp_nomad_start_service: true -hashicorp_nomad_service_name: "nomad" -hashicorp_nomad_cni_plugins_install: true -hashicorp_nomad_cni_plugins_version: latest -hashicorp_nomad_cni_plugins_install_path: /opt/cni/bin -hashicorp_nomad_version: "{{ nomad_version }}" -hashicorp_nomad_env_variables: {} -hashicorp_nomad_config_dir: "/etc/nomad.d" -hashicorp_nomad_data_dir: /opt/nomad -hashicorp_nomad_extra_files: true -hashicorp_nomad_extra_files_list: "{{ ([] + - (nomad_certificates_extra_files_dir if nomad_enable_tls else []) + - nomad_extra_files_list) - | unique - | sort - }}" -hashicorp_nomad_configuration: - datacenter: "{{ nomad_datacenter }}" - region: "{{ nomad_region }}" - bind_addr: "0.0.0.0" - data_dir: "{{ hashicorp_nomad_data_dir }}" - leave_on_interrupt: "{{ nomad_leave_on_interrupt }}" - leave_on_terminate: "{{ nomad_leave_on_terminate }}" - acl: "{{ nomad_acl_configuration }}" - server: "{{ nomad_server_configuration }}" - client: "{{ nomad_client_configuration }}" - ui: "{{ nomad_ui_configuration }}" - -# this is used to circumvent jinja limitation to convert string to integer -hashicorp_nomad_configuration_string: | - server: - bootstrap_expect: {{ (groups['nomad_servers'] | length) }} +nomad_enable_vault_integration: false +nomad_vault_integration_configuration: {} diff --git a/playbooks/group_vars/all/vault.yml b/playbooks/group_vars/all/vault.yml index 02c0e57..f447156 100644 --- a/playbooks/group_vars/all/vault.yml +++ b/playbooks/group_vars/all/vault.yml @@ -1,90 +1,126 @@ --- -##################################################### -# # -# Non-Editable # -# # -##################################################### +vault_init_server: "{{ (inventory_hostname == groups['vault_servers'][0]) | bool }}" -######################### -# vault haproxy backend # -######################### +######### +# Vault # +######### -vault_haproxy_frontend_options: - - acl is_vault hdr(host) -i {{ vault_fqdn }} - - use_backend vault_external if is_vault +vault_config_dir: "{{ hashistack_remote_config_dir }}/vault.d" +vault_data_dir: "/opt/vault" +vault_certs_dir: "{{ vault_config_dir }}/tls" +vault_logs_dir: "{{ hashistack_remote_log_dir }}/vault" -vault_haproxy_backends: - - name: vault_external - options: "{{ vault_external_backend_options + vault_external_backend_servers }}" +vault_extra_files: true +# vault_extra_files_list: [] -vault_external_backend_options: - - description vault external http backend - - option forwardfor - - option httpchk GET /v1/sys/health?standbyok=true&sealedcode=200&standbycode=200&uninitcode=200 - - http-check expect status 200 - - default-server inter 2s fastinter 1s downinter 1s +vault_env_variables: {} -vault_external_backend_servers: | - [ - {% for host in groups['vault_servers'] %} - 'server vault-{{ hostvars[host].api_interface_address }} {{ hostvars[host].api_interface_address }}:8200 check {{ 'ssl verify none ' if vault_enable_tls }}inter 5s'{% if not loop.last %},{% endif %} - {% endfor %} - ] +####################### +# 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: "{{ api_interface_address }}" +# vault_enable_ui: true +# vault_disable_mlock: false +# vault_disable_cache: false ###################### -# vault internal tls # +# seal configuration # ###################### -vault_certificates_directory: "{{ hashicorp_vault_config_dir }}/tls" -vault_certificates_extra_files_dir: - - src: "{{ sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}" - dest: "{{ vault_certificates_directory }}" +vault_seal_configuration: + key_shares: 3 + key_threshold: 2 -################# -# vault plugins # -################# +######################### +# storage configuration # +######################### -vault_plugin_directory: "{{ hashicorp_vault_config_dir }}/plugin" -vault_plugin_extra_files_dir: - - src: "{{ sub_configuration_directories['vault_servers'] }}/plugin" - dest: "{{ vault_plugin_directory }}" +vault_storage_configuration: + raft: + path: "{{ vault_data_dir }}" + node_id: "{{ ansible_hostname }}" + retry_join: >- + [ + {% for host in groups['vault_servers'] %} + { + 'leader_api_addr': '{{ "https" if vault_enable_tls else "http"}}://{{ hostvars[host].api_interface_address }}:8200' + }{% if not loop.last %},{% endif %} + {% endfor %} + ] -############################## -# vault service registration # -############################## +########################## +# 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 }}/fullchain.crt" + tls_key_file: "{{ vault_certs_dir }}/cert.key" + tls_disable_client_certs: true + +vault_certificates_extra_files_dir: > + {{ + [] if external_tls_externally_managed_certs | bool else + [{ + 'src': "{{ hashistack_sub_configuration_directories['certificates'] }}/vault/{{ inventory_hostname }}", + 'dest': "{{ vault_certs_dir }}" + }] + }} + +vault_extra_listener_configuration: [] + +######################## +# service registration # +######################## + +# vault_enable_service_registration: "{{ enable_consul | bool }}" +vault_service_registration_configuration: + consul: + address: >- + 127.0.0.1:{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }} + scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" + token: "{{ _credentials.consul.tokens.vault.secret_id }}" vault_service_registration_policy: | service "vault" { policy = "write" } -######################## -# vault role variables # -######################## +######################### +# plugins configuration # +######################### -hashicorp_vault_start_service: true -hashicorp_vault_service_name: "vault" -hashicorp_vault_version: "{{ vault_version }}" -hashicorp_vault_env_variables: {} -hashicorp_vault_config_dir: "/etc/vault.d" -hashicorp_vault_data_dir: "/opt/vault" -hashicorp_vault_extra_files: true -hashicorp_vault_extra_files_list: "{{ ([] + - (vault_certificates_extra_files_dir if vault_enable_tls else []) + - (vault_plugin_extra_files_dir if vault_enable_plugins else []) + - vault_extra_files_list) - | unique - | sort - }}" -hashicorp_vault_extra_files_src: "{{ sub_configuration_directories.vault_servers }}/config" -hashicorp_vault_extra_files_dst: "{{ hashicorp_vault_config_dir }}/config" -hashicorp_vault_extra_container_volumes: "{{ default_container_extra_volumes | union(extra_vault_container_volumes) | unique | sort }}" -hashicorp_vault_configuration: - cluster_name: "{{ vault_cluster_name }}" - cluster_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ api_interface_address }}:8201" - api_addr: "{{ 'https' if vault_enable_tls else 'http'}}://{{ api_interface_address }}:8200" - ui: "{{ vault_enable_ui }}" - disable_mlock: false - disable_cache: false - listener: "{{ vault_listener_configuration }}" - storage: "{{ vault_storage_configuration }}" +# vault_enable_plugins: false +vault_plugins_directory: "{{ vault_config_dir }}/plugins" + +################# +# vault logging # +################# + +# vault_log_level: info +vault_enable_log_to_file: "{{ enable_log_to_file | bool }}" +vault_log_to_file_configuration: + log_file: "{{ vault_logs_dir }}/vault.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 diff --git a/playbooks/inventory/multinode.ini b/playbooks/inventory/multinode.ini index 373198d..bf01735 100644 --- a/playbooks/inventory/multinode.ini +++ b/playbooks/inventory/multinode.ini @@ -22,6 +22,8 @@ nomad-client01 nomad-client02 nomad-client03 +[consul_agents] + [consul_agents:children] haproxy_servers vault_servers @@ -31,8 +33,20 @@ nomad_clients [deployment] localhost ansible_connection=local +[consul:children] +consul_servers +consul_agents + +[nomad:children] +nomad_servers +nomad_clients + +[vault:children] +vault_servers + [common:children] haproxy_servers vault_servers consul_servers nomad_servers +nomad_clients diff --git a/playbooks/preflight.yml b/playbooks/preflight.yml index b49cd73..83d076c 100644 --- a/playbooks/preflight.yml +++ b/playbooks/preflight.yml @@ -1,14 +1,16 @@ --- # hashistack deployment playbook - name: "Preflight" - hosts: all + hosts: all, !deployment strategy: linear gather_facts: true become: true tasks: - name: "Import variables" - ansible.builtin.import_tasks: - file: tasks/load_vars.yml + ansible.builtin.include_role: + name: ednz_cloud.hashistack.hashistack + tags: + - always - name: "Checking vault inventory" ansible.builtin.assert: @@ -46,44 +48,44 @@ when: - enable_nomad | bool - - name: "Checking directory {{ configuration_directory }}" # noqa: run-once[task] + - name: "Checking directory {{ hashistack_configuration_directory }}" # noqa: run-once[task] delegate_to: localhost run_once: true block: - - name: "Stat directory {{ configuration_directory }}" + - name: "Stat directory {{ hashistack_configuration_directory }}" ansible.builtin.stat: - path: "{{ configuration_directory }}" + path: "{{ hashistack_configuration_directory }}" register: _stat_config_dir - name: "Stat nomad_servers config directory" ansible.builtin.stat: - path: "{{ sub_configuration_directories.nomad_servers }}" + path: "{{ hashistack_sub_configuration_directories.nomad_servers }}" register: _stat_config_dir_nomad_servers when: - enable_nomad | bool - name: "Stat consul_servers config directory" ansible.builtin.stat: - path: "{{ sub_configuration_directories.consul_servers }}" + path: "{{ hashistack_sub_configuration_directories.consul_servers }}" register: _stat_config_dir_consul_servers when: - enable_consul | bool - name: "Stat vault_servers config directory" ansible.builtin.stat: - path: "{{ sub_configuration_directories.vault_servers }}" + path: "{{ hashistack_sub_configuration_directories.vault_servers }}" register: _stat_config_dir_vault_servers when: - enable_vault | bool - - name: "Make sure directory exists: {{ configuration_directory }}" + - name: "Make sure directory exists: {{ hashistack_configuration_directory }}" ansible.builtin.assert: that: - _stat_config_dir.stat.exists - _stat_config_dir.stat.isdir - _stat_config_dir.stat.writeable - - name: "Make sure directory exists: {{ sub_configuration_directories.nomad_servers }}" + - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.nomad_servers }}" ansible.builtin.assert: that: - _stat_config_dir_nomad_servers.stat.exists @@ -92,7 +94,7 @@ when: - enable_nomad | bool - - name: "Make sure directory exists: {{ sub_configuration_directories.consul_servers }}" + - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.consul_servers }}" ansible.builtin.assert: that: - _stat_config_dir_consul_servers.stat.exists @@ -101,7 +103,7 @@ when: - enable_consul | bool - - name: "Make sure directory exists: {{ sub_configuration_directories.vault_servers }}" + - name: "Make sure directory exists: {{ hashistack_sub_configuration_directories.vault_servers }}" ansible.builtin.assert: that: - _stat_config_dir_vault_servers.stat.exists @@ -111,8 +113,8 @@ - enable_vault | bool - name: "Checking host OS distribution" - #TODO: This needs to work with debian and ubuntu, major version works for debian but not ubuntu, simple version works the other way around... - #? seems to work + # TODO: This needs to work with debian and ubuntu, major version works for debian but not ubuntu, simple version works the other way around... + # ? seems to work ansible.builtin.assert: that: - "(ansible_facts.distribution | lower) in hashistack_supported_distributions" @@ -259,7 +261,7 @@ when: vault_port_results.results | length > 0 - name: "Checking if consul ports are available" - when: inventory_hostname in groups['consul_servers'] + when: inventory_hostname in groups['consul_servers'] or inventory_hostname in groups['consul_agents'] block: - name: "Checking if consul ports are available" ansible.builtin.wait_for: @@ -278,6 +280,26 @@ with_items: "{{ consul_port_results.results }}" when: consul_port_results.results | length > 0 + - name: "Checking if nomad ports are available" + when: inventory_hostname in groups['nomad_servers'] or inventory_hostname in groups['nomad_clients'] + block: + - name: "Checking if nomad ports are available" + ansible.builtin.wait_for: + host: "{{ inventory_hostname }}" + port: "{{ item }}" + state: "stopped" + timeout: 5 + loop: "{{ nomad_required_ports }}" + ignore_errors: true + register: nomad_port_results + + - name: "Assert that nomad ports are not currently in use" + ansible.builtin.assert: + that: + - item.failed == false + with_items: "{{ nomad_port_results.results }}" + when: nomad_port_results.results | length > 0 + - name: "Checking if system uses systemd" become: true ansible.builtin.assert: diff --git a/playbooks/tasks/consul/consul_agents.yml b/playbooks/tasks/consul/consul_agents.yml new file mode 100644 index 0000000..ba7e5fa --- /dev/null +++ b/playbooks/tasks/consul/consul_agents.yml @@ -0,0 +1,6 @@ +--- +- name: "Consul agents" + block: + - name: "Deploy Consul Agents" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.consul diff --git a/playbooks/tasks/consul/consul_control_plane.yml b/playbooks/tasks/consul/consul_control_plane.yml new file mode 100644 index 0000000..f309133 --- /dev/null +++ b/playbooks/tasks/consul/consul_control_plane.yml @@ -0,0 +1,71 @@ +--- +- name: "Consul control plane" + block: + - name: "Include ednz_cloud.hashistack.consul" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.consul + + - name: "Consul | Wait for consul cluster to initialize" # noqa: run-once[task] + block: + - name: "Consul | Wait for consul nodes to stabilize" + ansible.builtin.wait_for: + host: "{{ api_interface_address }}" + port: "{{ consul_api_port[consul_api_scheme] }}" + delay: 10 + + - name: "Consul | Waiting for consul api to respond" + ansible.builtin.uri: + url: "{{ consul_api_addr }}" + validate_certs: no + return_content: yes + status_code: + - 200 + until: uri_output.status == 200 + retries: 24 + delay: 5 + register: uri_output + + - name: "Consul | Initialize consul cluster" # noqa: run-once[task] + community.general.consul_acl_bootstrap: + bootstrap_secret: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ api_interface_address }}" + port: "{{ consul_api_port[consul_api_scheme] }}" + scheme: "{{ consul_api_scheme }}" + state: present + register: _consul_init_secret + when: + - consul_init_server + - consul_configuration.acl.enabled + + - name: "Consul | Create consul agents token" + when: + - consul_init_server + - consul_configuration.acl.enabled + block: + - name: "Consul | Create consul agents token" # noqa: run-once[task] no-handler + block: + - name: "Consul | Create consul agent policy" + community.general.consul_policy: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ api_interface_address }}" + port: "{{ consul_api_port[consul_api_scheme] }}" + scheme: "{{ consul_api_scheme }}" + validate_certs: false + state: present + name: agents-policy + rules: "{{ consul_default_agent_policy }}" + register: _consul_agent_policy + + - name: "Consul | Create consul agents token" + community.general.consul_token: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ api_interface_address }}" + port: "{{ consul_api_port[consul_api_scheme] }}" + scheme: "{{ consul_api_scheme }}" + validate_certs: false + accessor_id: "{{ _credentials.consul.tokens.agent.accessor_id }}" + secret_id: "{{ _credentials.consul.tokens.agent.secret_id }}" + policies: + - id: "{{ _consul_agent_policy.policy.ID }}" + state: present + register: _consul_agent_token diff --git a/playbooks/tasks/consul/consul_deploy.yml b/playbooks/tasks/consul/consul_deploy.yml index 14a0872..4271b3f 100644 --- a/playbooks/tasks/consul/consul_deploy.yml +++ b/playbooks/tasks/consul/consul_deploy.yml @@ -1,78 +1,19 @@ --- - name: "Consul" block: - - name: "Include ednz_cloud.hashicorp_consul" - ansible.builtin.include_role: - name: ednz_cloud.hashicorp_consul - - - name: "Wait for consul cluster to initialize" # noqa: run-once[task] - block: - - name: "Wait for consul nodes to stabilize" - ansible.builtin.wait_for: - host: "{{ api_interface_address }}" - port: "{{ consul_api_port[consul_api_scheme] }}" - delay: 10 - - - name: "Waiting for consul api to respond" - ansible.builtin.uri: - url: "{{ consul_api_addr }}" - validate_certs: no - return_content: yes - status_code: - - 200 - until: uri_output.status == 200 - retries: 24 - delay: 5 - register: uri_output - - - name: "Initialize consul cluster" # noqa: run-once[task] - community.general.consul_acl_bootstrap: - bootstrap_secret: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ api_interface_address }}" - port: "{{ consul_api_port[consul_api_scheme] }}" - scheme: "{{ consul_api_scheme }}" - state: present - run_once: true - delegate_to: "{{ groups['consul_servers'] | first }}" - register: _consul_init_secret - when: hashicorp_consul_configuration.acl.enabled - - - name: "Create consul agents token" + - name: "Deploy Consul Control Plane" + ansible.builtin.import_tasks: + file: consul_control_plane.yml when: - - consul_acl_configuration.enabled - block: - - name: "Create consul agents token" # noqa: run-once[task] no-handler - run_once: true - block: - - name: "Create consul agent policy" - community.general.consul_policy: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ api_interface_address }}" - port: "{{ consul_api_port[consul_api_scheme] }}" - scheme: "{{ consul_api_scheme }}" - validate_certs: false - state: present - name: agents-policy - rules: "{{ consul_default_agent_policy }}" - register: _consul_agent_policy + - "'consul_servers' in group_names" + tags: + - consul_servers - - name: "Create consul agents token" - community.general.consul_token: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ api_interface_address }}" - port: "{{ consul_api_port[consul_api_scheme] }}" - scheme: "{{ consul_api_scheme }}" - validate_certs: false - accessor_id: "{{ _credentials.consul.tokens.agent.accessor_id }}" - secret_id: "{{ _credentials.consul.tokens.agent.secret_id }}" - policies: - - id: "{{ _consul_agent_policy.policy.ID }}" - state: present - register: _consul_agent_token - - - name: "Restart consul service" # noqa: no-handler - ansible.builtin.service: - name: "{{ hashicorp_consul_service_name }}" - state: restarted - throttle: 1 - when: _consul_agent_token.changed + - name: "Deploy Consul Agents" + ansible.builtin.import_tasks: + file: consul_agents.yml + when: + - "'consul_agents' in group_names" + - "'consul_servers' not in group_names" + tags: + - consul_agents diff --git a/playbooks/tasks/consul/consul_vars.yml b/playbooks/tasks/consul/consul_vars.yml deleted file mode 100644 index fb593df..0000000 --- a/playbooks/tasks/consul/consul_vars.yml +++ /dev/null @@ -1,67 +0,0 @@ ---- -# hashistack configuration merging for consul -- name: "Consul | Merge stringified configuration" - vars: - _config_to_merge: "{{ hashicorp_consul_configuration_string }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ - hashicorp_consul_configuration | - combine(_config_to_merge|from_yaml, recursive=true) - }}" - when: - - hashicorp_consul_configuration_string is defined - -- name: "Consul | Merge servers specific stringified configuration" - vars: - _config_to_merge: "{{ hashicorp_consul_servers_configuration_string }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ - hashicorp_consul_configuration | - combine(_config_to_merge|from_yaml, recursive=true) - }}" - when: - - hashicorp_consul_configuration_string is defined - - "'consul_servers' in group_names" - -- name: "Consul | Merge addresses configuration" - vars: - _config_to_merge: "{{ consul_address_configuration }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ - hashicorp_consul_configuration | - combine(_config_to_merge, recursive=true) - }}" - when: consul_address_configuration is defined - -- name: "Consul | Merge TLS configuration" - vars: - _config_to_merge: - tls: "{{ consul_tls_configuration }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ - hashicorp_consul_configuration | - combine(_config_to_merge, recursive=true) - }}" - when: consul_enable_tls - -- name: "Consul | Merge token configuration" - delegate_to: localhost - block: - - name: "Consul | Merge token configuration" - vars: - _config_to_merge: - acl: - tokens: - agent: "{{ _credentials.consul.tokens.agent.secret_id }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ hashicorp_consul_configuration | default({}) | combine(_config_to_merge, recursive=true) }}" - -- name: "Consul | Merge extra configuration settings" - vars: - _config_to_merge: "{{ consul_extra_configuration }}" - ansible.builtin.set_fact: - hashicorp_consul_configuration: "{{ - hashicorp_consul_configuration | - combine(_config_to_merge, recursive=true) - }}" - when: consul_extra_configuration is defined diff --git a/playbooks/tasks/load_vars.yml b/playbooks/tasks/load_vars.yml index ec8785f..ea642c3 100644 --- a/playbooks/tasks/load_vars.yml +++ b/playbooks/tasks/load_vars.yml @@ -23,7 +23,6 @@ owner: root group: root mode: 0755 - recurse: yes loop: - "{{ hashistack_remote_config_dir }}" - "{{ hashistack_remote_data_dir }}" @@ -52,3 +51,8 @@ when: - enable_nomad | bool - "('nomad_servers' in group_names) or ('nomad_clients' in group_names)" +# - name: "Print all config" +# ansible.builtin.debug: +# msg: "{{ hostvars[inventory_hostname] }}" +# +# - fail: diff --git a/playbooks/tasks/misc/load_all_vars.yml b/playbooks/tasks/misc/load_all_vars.yml deleted file mode 100644 index 2e5eea5..0000000 --- a/playbooks/tasks/misc/load_all_vars.yml +++ /dev/null @@ -1,206 +0,0 @@ ---- -# hashistack variable injection playbook -- name: "Load global variables" - block: - - name: "Stat global configuration file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ configuration_global_vars_file }}" - register: _global_config_file - delegate_to: localhost - - - name: "Make sure global configuration file exists" - ansible.builtin.assert: - that: - - _global_config_file.stat.exists - fail_msg: >- - Main configuration file {{ _global_config_file.stat.path }} was not found, cannot continue without it. - delegate_to: localhost - - - name: "Load global variables" - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}" - files_matching: "{{ configuration_global_vars_file }}" - depth: 1 - delegate_to: localhost - -- name: "Load credentials variables" - block: - - name: "Stat credentials file" - ansible.builtin.stat: - path: "{{ sub_configuration_directories['secrets'] }}/{{ configuration_credentials_vars_file }}" - register: _credentials_file - delegate_to: localhost - - - name: "Stat vault credentials file" - ansible.builtin.stat: - path: "{{ sub_configuration_directories['secrets'] }}/vault.yml" - register: _vault_credentials_file - delegate_to: localhost - - - name: "Make sure credentials file exists" - ansible.builtin.assert: - that: - - _credentials_file.stat.exists - fail_msg: >- - Credentials file {{ _credentials_file.stat.path }} was not found, cannot continue without it. - delegate_to: localhost - - - name: "Load credentials variables" - ansible.builtin.include_vars: - dir: "{{ sub_configuration_directories['secrets'] }}" - files_matching: "{{ configuration_credentials_vars_file }}" - depth: 1 - name: _credentials - delegate_to: localhost - - - name: "Load vault credentials if vault.yml exists" - ansible.builtin.include_vars: - dir: "{{ sub_configuration_directories['secrets'] }}" - files_matching: "vault.yml" - depth: 1 - name: _vault_credentials - when: _vault_credentials_file.stat.exists - delegate_to: localhost - - - name: "Merge vault credentials into _credentials" - vars: - _config_to_merge: - vault: "{{ _vault_credentials }}" - ansible.builtin.set_fact: - _credentials: "{{ _credentials | combine(_vault_credentials, recursive=true) }}" - when: _vault_credentials_file.stat.exists - delegate_to: localhost - -- name: "Load group specific variables" - block: - - name: "Stat group specific config file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ group_name }}/{{ configuration_global_vars_file }}" - register: _group_config_file - loop: "{{ group_names }}" - loop_control: - loop_var: group_name - - - name: Load group specific variables - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}/{{ item.group_name }}" - files_matching: "{{ configuration_global_vars_file }}" - depth: 1 - loop: "{{ _group_config_file.results }}" - when: item.stat.exists - and item.group_name in group_names - loop_control: - loop_var: item - delegate_to: localhost - -- name: "Load host specific variables" - block: - - name: "Stat host specific config file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ group_name }}/{{ inventory_hostname }}/{{ configuration_global_vars_file }}" - register: _host_config_file - loop: "{{ group_names }}" - loop_control: - loop_var: group_name - delegate_to: localhost - - - name: Load host specific variables - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}/{{ item.group_name }}/{{ inventory_hostname }}" - files_matching: "{{ configuration_global_vars_file }}" - loop: "{{ _host_config_file.results }}" - when: item.stat.exists - loop_control: - loop_var: item - delegate_to: localhost - -- name: "Ensure remote directories exists" - ansible.builtin.file: - path: "{{ item }}" - state: directory - owner: root - group: root - mode: 0755 - recurse: yes - loop: - - "{{ hashistack_remote_config_dir }}" - - "{{ hashistack_remote_data_dir }}" - -- name: "Load custom CA certificates" - block: - - name: "Check if CA directory exists" - ansible.builtin.stat: - path: "{{ sub_configuration_directories['certificates'] }}/ca" - register: _hashistack_ca_directory - delegate_to: localhost - - - name: "Find custom ca certificates to copy" - ansible.builtin.find: - paths: "{{ sub_configuration_directories['certificates'] }}/ca" - patterns: "*.crt" - register: _hashistack_cacert_files - delegate_to: localhost - when: _hashistack_ca_directory.stat.exists and _hashistack_ca_directory.stat.isdir - - - name: "Ensure remote ca directory exists" - ansible.builtin.file: - path: "{{ hashistack_remote_config_dir }}/ca" - state: directory - owner: root - group: root - mode: 0755 - - - name: "Copy custom ca certificates" - ansible.builtin.copy: - src: "{{ item.path }}" - dest: "{{ hashistack_remote_config_dir }}/ca/{{ item.path | basename }}" - owner: root - group: root - mode: 0644 - loop: "{{ _hashistack_cacert_files.files }}" - register: _hashistack_copied_ca - - - name: "Copy and update trust store" - block: - - name: "Copy ca certificates to /usr/loca/share/ca-certificates" - ansible.builtin.file: - state: link - src: "{{ item.dest }}" - dest: "/usr/local/share/ca-certificates/hashistack-customca-{{ item.dest | basename }}" - owner: root - group: root - loop: "{{ _hashistack_copied_ca.results }}" - register: _hashistack_usr_local_share_ca_certificates - - - name: "Update the trust store" - ansible.builtin.command: update-ca-certificates - changed_when: false - when: _hashistack_usr_local_share_ca_certificates.changed - - # - name: "Initialize list of CA certificates" - # ansible.builtin.set_fact: - # hashistack_cacert_extra_files: [] - # delegate_to: localhost - - # - name: "Add custom CA to list of extra certificates" - # ansible.builtin.set_fact: - # hashistack_cacert_extra_files: "{{ - # hashistack_cacert_extra_files | default([]) - # + [{'src': item.path, 'dest': '/etc/ssl/certs/hashistack-custom-' + item.path | basename}] }}" - # loop: "{{ _hashistack_cacert_files.files }}" - # delegate_to: localhost - # when: _hashistack_cacert_files.matched > 0 - -- name: "Merge consul configurations" - ansible.builtin.import_tasks: - file: "consul/consul_vars.yml" - when: - - enable_consul | bool - - "('consul_servers' in group_names) or ('consul_agents' in group_names)" - -- name: "Merge vault configurations" - ansible.builtin.import_tasks: - file: "vault/vault_vars.yml" - when: - - enable_vault | bool - - "'vault_servers' in group_names" diff --git a/playbooks/tasks/misc/load_global_vars.yml b/playbooks/tasks/misc/load_global_vars.yml deleted file mode 100644 index e162ee9..0000000 --- a/playbooks/tasks/misc/load_global_vars.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -- name: "Stat global configuration file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ configuration_global_vars_file }}" - register: _global_config_file - delegate_to: localhost - -- name: "Make sure global configuration file exists" - ansible.builtin.assert: - that: - - _global_config_file.stat.exists - fail_msg: >- - Main configuration file {{ _global_config_file.stat.path }} was not found, cannot continue without it. - delegate_to: localhost - -- name: "Load global variables" - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}" - files_matching: "{{ configuration_global_vars_file }}" - depth: 1 - delegate_to: localhost diff --git a/playbooks/tasks/misc/load_group_vars.yml b/playbooks/tasks/misc/load_group_vars.yml deleted file mode 100644 index d21a475..0000000 --- a/playbooks/tasks/misc/load_group_vars.yml +++ /dev/null @@ -1,20 +0,0 @@ ---- -- name: "Stat group specific config file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ group_name }}/{{ configuration_global_vars_file }}" - register: _group_config_file - loop: "{{ group_names }}" - loop_control: - loop_var: group_name - -- name: Load group specific variables - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}/{{ item.group_name }}" - files_matching: "{{ configuration_global_vars_file }}" - depth: 1 - loop: "{{ _group_config_file.results }}" - when: item.stat.exists - and item.group_name in group_names - loop_control: - loop_var: item - delegate_to: localhost diff --git a/playbooks/tasks/misc/load_host_vars.yml b/playbooks/tasks/misc/load_host_vars.yml deleted file mode 100644 index c95c2b7..0000000 --- a/playbooks/tasks/misc/load_host_vars.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -- name: "Stat host specific config file" - ansible.builtin.stat: - path: "{{ configuration_directory }}/{{ group_name }}/{{ inventory_hostname }}/{{ configuration_global_vars_file }}" - register: _host_config_file - loop: "{{ group_names }}" - loop_control: - loop_var: group_name - delegate_to: localhost - -- name: Load host specific variables - ansible.builtin.include_vars: - dir: "{{ configuration_directory }}/{{ item.group_name }}/{{ inventory_hostname }}" - files_matching: "{{ configuration_global_vars_file }}" - loop: "{{ _host_config_file.results }}" - when: item.stat.exists - loop_control: - loop_var: item - delegate_to: localhost diff --git a/playbooks/tasks/nomad/nomad_clients.yml b/playbooks/tasks/nomad/nomad_clients.yml new file mode 100644 index 0000000..7cb8618 --- /dev/null +++ b/playbooks/tasks/nomad/nomad_clients.yml @@ -0,0 +1,15 @@ +--- +- name: "Nomad clients" + block: + - name: "Nomad | Install docker driver" + ansible.builtin.include_role: + name: ednz_cloud.install_docker + when: nomad_driver_enable_docker + + - name: "Include ednz_cloud.hashistack.cni" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.cni + + - name: "Nomad | Deploy Clients" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.nomad diff --git a/playbooks/tasks/nomad/nomad_control_plane.yml b/playbooks/tasks/nomad/nomad_control_plane.yml new file mode 100644 index 0000000..da92dfd --- /dev/null +++ b/playbooks/tasks/nomad/nomad_control_plane.yml @@ -0,0 +1,87 @@ +--- +- name: "Nomad control plane" + block: + - name: "Nomad | Create consul tokens for service registration" + when: + - nomad_init_server + - enable_consul + - nomad_enable_consul_integration + vars: + _consul_host: "{{ hostvars[groups['consul_servers'][0]].api_interface_address }}" + _consul_port: "{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }}" + _consul_scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" + block: + - name: "Nomad | Create server credentials" + block: + - name: "Nomad | Create consul server policy" + community.general.consul_policy: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_host }}" + port: "{{ _consul_port }}" + scheme: "{{ _consul_scheme }}" + validate_certs: false + state: present + name: nomad-server-policy + rules: "{{ nomad_consul_integration_server_policy }}" + register: _consul_nomad_server_policy + + - name: "Nomad | Create consul server token" # noqa: no-handler + community.general.consul_token: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_host }}" + port: "{{ _consul_port }}" + scheme: "{{ _consul_scheme }}" + validate_certs: false + accessor_id: "{{ _credentials.consul.tokens.nomad.server.accessor_id }}" + secret_id: "{{ _credentials.consul.tokens.nomad.server.secret_id }}" + policies: + - id: "{{ _consul_nomad_server_policy.policy.ID }}" + state: present + when: _consul_nomad_server_policy.changed + + - name: "Nomad | Create client credentials" + block: + - name: "Nomad | Create consul client policy" + community.general.consul_policy: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_host }}" + port: "{{ _consul_port }}" + scheme: "{{ _consul_scheme }}" + validate_certs: false + state: present + name: nomad-client-policy + rules: "{{ nomad_consul_integration_client_policy }}" + register: _consul_nomad_client_policy + + - name: "Nomad | Create consul client token" # noqa: no-handler + community.general.consul_token: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_host }}" + port: "{{ _consul_port }}" + scheme: "{{ _consul_scheme }}" + validate_certs: false + accessor_id: "{{ _credentials.consul.tokens.nomad.client.accessor_id }}" + secret_id: "{{ _credentials.consul.tokens.nomad.client.secret_id }}" + policies: + - id: "{{ _consul_nomad_client_policy.policy.ID }}" + state: present + when: _consul_nomad_client_policy.changed + + - name: "Include ednz_cloud.hashistack.cni" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.cni + when: nomad_enable_client + + - name: "Include ednz_cloud.hashistack.nomad" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.nomad + + - name: "Nomad | Initialize nomad cluster" # noqa: run-once[task] + ednz_cloud.hashistack.nomad_acl_bootstrap: + bootstrap_secret: "{{ _credentials.nomad.root_token.secret_id }}" + api_url: "{{ nomad_api_addr }}" + tls_verify: false + register: _nomad_init_secret + when: + - nomad_init_server + - nomad_configuration.acl.enabled diff --git a/playbooks/tasks/nomad/nomad_deploy.yml b/playbooks/tasks/nomad/nomad_deploy.yml index 0249ced..7bf78ba 100644 --- a/playbooks/tasks/nomad/nomad_deploy.yml +++ b/playbooks/tasks/nomad/nomad_deploy.yml @@ -1,83 +1,19 @@ --- - name: "Nomad" block: - - name: "Create consul tokens for service registration" + - name: "Deploy Nomad Control Plane" + ansible.builtin.import_tasks: + file: nomad_control_plane.yml when: - - enable_consul - - nomad_enable_consul_integration - delegate_to: "{{ groups['consul_servers'] | first }}" - vars: - _consul_host: "{{ hostvars[groups['consul_servers'][0]].api_interface_address }}" - _consul_port: "{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }}" - _consul_scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" - run_once: true - block: - - name: "Create server credentials" - block: - - name: "Create consul server policy" - community.general.consul_policy: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_host }}" - port: "{{ _consul_port }}" - scheme: "{{ _consul_scheme }}" - validate_certs: false - state: present - name: nomad-server-policy - rules: "{{ nomad_consul_integration_server_policy }}" - register: _consul_nomad_server_policy + - nomad_enable_server + tags: + - nomad_servers - - name: "Create consul server token" - community.general.consul_token: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_host }}" - port: "{{ _consul_port }}" - scheme: "{{ _consul_scheme }}" - validate_certs: false - accessor_id: "{{ _credentials.consul.tokens.nomad.server.accessor_id }}" - secret_id: "{{ _credentials.consul.tokens.nomad.server.secret_id }}" - policies: - - id: "{{ _consul_nomad_server_policy.policy.ID }}" - state: present - when: _consul_nomad_server_policy.changed - - - name: "Create client credentials" - block: - - name: "Create consul client policy" - community.general.consul_policy: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_host }}" - port: "{{ _consul_port }}" - scheme: "{{ _consul_scheme }}" - validate_certs: false - state: present - name: nomad-client-policy - rules: "{{ nomad_consul_integration_client_policy }}" - register: _consul_nomad_client_policy - - - name: "Create consul client token" - community.general.consul_token: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_host }}" - port: "{{ _consul_port }}" - scheme: "{{ _consul_scheme }}" - validate_certs: false - accessor_id: "{{ _credentials.consul.tokens.nomad.client.accessor_id }}" - secret_id: "{{ _credentials.consul.tokens.nomad.client.secret_id }}" - policies: - - id: "{{ _consul_nomad_client_policy.policy.ID }}" - state: present - when: _consul_nomad_client_policy.changed - - - name: "Include ednz_cloud.hashicorp_nomad" - ansible.builtin.include_role: - name: ednz_cloud.hashicorp_nomad - - - name: "Initialize nomad cluster" # noqa: run-once[task] - ednz_cloud.hashistack.nomad_acl_bootstrap: - bootstrap_secret: "{{ _credentials.nomad.root_token.secret_id }}" - api_url: "{{ nomad_api_addr }}" - tls_verify: false - run_once: true - delegate_to: "{{ groups['nomad_servers'] | first }}" - register: _nomad_init_secret - when: hashicorp_nomad_configuration.acl.enabled + - name: "Deploy Nomad Clients" + ansible.builtin.import_tasks: + file: nomad_clients.yml + when: + - nomad_enable_client + - not nomad_enable_server + tags: + - nomad_clients diff --git a/playbooks/tasks/vault/vault_control_plane.yml b/playbooks/tasks/vault/vault_control_plane.yml new file mode 100644 index 0000000..c0ace90 --- /dev/null +++ b/playbooks/tasks/vault/vault_control_plane.yml @@ -0,0 +1,97 @@ +--- +- name: "Vault control plane" + block: + - name: "Vault | Create consul token for service registration" + when: + - vault_init_server + - enable_consul + - vault_enable_service_registration + vars: + _consul_vault_sr_host: "{{ hostvars[groups['consul_servers'][0]].api_interface_address }}" + _consul_vault_sr_port: "{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }}" + _consul_vault_sr_scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" + block: + - name: "Vault | Create consul vault policy" + community.general.consul_policy: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_vault_sr_host }}" + port: "{{ _consul_vault_sr_port }}" + scheme: "{{ _consul_vault_sr_scheme }}" + validate_certs: false + state: present + name: vault-policy + rules: "{{ vault_service_registration_policy }}" + register: _consul_vault_policy + + - name: "Vault | Create consul vault token" # noqa: no-handler + community.general.consul_token: + token: "{{ _credentials.consul.root_token.secret_id }}" + host: "{{ _consul_vault_sr_host }}" + port: "{{ _consul_vault_sr_port }}" + scheme: "{{ _consul_vault_sr_scheme }}" + validate_certs: false + accessor_id: "{{ _credentials.consul.tokens.vault.accessor_id }}" + secret_id: "{{ _credentials.consul.tokens.vault.secret_id }}" + policies: + - id: "{{ _consul_vault_policy.policy.ID }}" + state: present + when: _consul_vault_policy.changed + + - name: "Vault | Stat vault secret file" + ansible.builtin.stat: + path: "{{ hashistack_sub_configuration_directories.secrets }}/vault.yml" + register: _vault_needs_early_unseal + + - name: "Include ednz_cloud.hashistack.vault" + ansible.builtin.include_role: + name: ednz_cloud.hashistack.vault + + - name: "Vault | Initialize vault cluster" # noqa: run-once[task] + ednz_cloud.hashistack.vault_init: + api_url: "{{ vault_configuration['api_addr'] }}" + tls_verify: false + key_shares: "{{ vault_seal_configuration['key_shares'] }}" + key_threshold: "{{ vault_seal_configuration['key_threshold'] }}" + retries: 5 + delay: 5 + register: _vault_init_secret + until: not _vault_init_secret.failed + when: vault_init_server + + - name: "Vault | Write vault configuration to file" # noqa: run-once[task] no-handler + ansible.builtin.copy: + content: "{{ _vault_init_secret.state | to_nice_yaml(indent=2) }}" + dest: "{{ hashistack_sub_configuration_directories.secrets }}/vault.yml" + owner: "{{ lookup('env', 'USER') }}" + group: "{{ lookup('env', 'USER') }}" + mode: "0644" + when: + - vault_init_server + - _vault_init_secret.changed + delegate_to: localhost + + - name: "Load vault cluster variables necessary for unseal operation" + ansible.builtin.import_role: + name: ednz_cloud.hashistack.hashistack + vars: + hashistack_only_load_credentials: true + + - name: "Vault | Unseal the bootstrap node" # noqa: run-once[task] no-handler + ednz_cloud.hashistack.vault_unseal: + api_url: "{{ vault_configuration['api_addr'] }}" + tls_verify: false + key_shares: "{{ _credentials.vault['keys'] }}" + when: + - vault_init_server + - _vault_init_secret.changed + register: _vault_unseal_secret + + - name: "Vault | Unseal all vault nodes" + ednz_cloud.hashistack.vault_unseal: + api_url: "{{ vault_configuration['api_addr'] }}" + tls_verify: false + key_shares: "{{ _credentials.vault['keys'] }}" + retries: 5 + delay: 5 + until: _unseal_status.changed or not _unseal_status.failed + register: _unseal_status diff --git a/playbooks/tasks/vault/vault_deploy.yml b/playbooks/tasks/vault/vault_deploy.yml index 844118a..21c1016 100644 --- a/playbooks/tasks/vault/vault_deploy.yml +++ b/playbooks/tasks/vault/vault_deploy.yml @@ -1,96 +1,10 @@ --- - name: "Vault" block: - - name: "Create consul token for service registration" - when: - - enable_consul - - vault_enable_service_registration - delegate_to: "{{ groups['consul_servers'] | first }}" - vars: - _consul_vault_sr_host: "{{ hostvars[groups['consul_servers'][0]].api_interface_address }}" - _consul_vault_sr_port: "{{ hostvars[groups['consul_servers'][0]].consul_api_port[hostvars[groups['consul_servers'][0]].consul_api_scheme] }}" - _consul_vault_sr_scheme: "{{ hostvars[groups['consul_servers'][0]].consul_api_scheme }}" - run_once: true - block: - - name: "Create consul vault policy" - community.general.consul_policy: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_vault_sr_host }}" - port: "{{ _consul_vault_sr_port }}" - scheme: "{{ _consul_vault_sr_scheme }}" - validate_certs: false - state: present - name: vault-policy - rules: "{{ vault_service_registration_policy }}" - register: _consul_vault_policy - - - name: "Create consul vault token" - community.general.consul_token: - token: "{{ _credentials.consul.root_token.secret_id }}" - host: "{{ _consul_vault_sr_host }}" - port: "{{ _consul_vault_sr_port }}" - scheme: "{{ _consul_vault_sr_scheme }}" - validate_certs: false - accessor_id: "{{ _credentials.consul.tokens.vault.accessor_id }}" - secret_id: "{{ _credentials.consul.tokens.vault.secret_id }}" - policies: - - id: "{{ _consul_vault_policy.policy.ID }}" - state: present - when: _consul_vault_policy.changed - - - name: "Include ednz_cloud.hashicorp_consul" - ansible.builtin.include_role: - name: ednz_cloud.hashicorp_vault - - - name: "Initialize vault cluster" # noqa: run-once[task] - ednz_cloud.hashistack.vault_init: - api_url: "{{ hashicorp_vault_configuration['api_addr'] }}" - tls_verify: "{{ vault_tls_verify }}" - key_shares: "{{ vault_seal_configuration['key_shares'] }}" - key_threshold: "{{ vault_seal_configuration['key_threshold'] }}" - run_once: true - retries: 5 - delay: 5 - delegate_to: "{{ groups['vault_servers'] | first }}" - register: _vault_init_secret - until: not _vault_init_secret.failed - - - name: "Write vault configuration to file" # noqa: run-once[task] no-handler - ansible.builtin.copy: - content: "{{ _vault_init_secret.state | to_nice_yaml(indent=2) }}" - dest: "{{ sub_configuration_directories.secrets }}/vault.yml" - owner: "{{ lookup('env', 'USER') }}" - group: "{{ lookup('env', 'USER') }}" - mode: "0644" - when: _vault_init_secret.changed - run_once: true - delegate_to: localhost - - # - name: "Load vault cluster variables necessary for unseal operation" - # ansible.builtin.include_vars: - # file: "{{ sub_configuration_directories.vault_servers }}/vault_config.yml" - # name: _vault_cluster_config - - - name: "Load vault cluster variables necessary for unseal operation" + - name: "Deploy Vault Control Plane" ansible.builtin.import_tasks: - file: ../misc/load_credentials_vars.yml - - - name: "Unseal the bootstrap node" # noqa: run-once[task] no-handler - ednz_cloud.hashistack.vault_unseal: - api_url: "{{ hashicorp_vault_configuration['api_addr'] }}" - tls_verify: "{{ vault_tls_verify }}" - key_shares: "{{ _credentials.vault['keys'] }}" - run_once: true - delegate_to: "{{ groups['vault_servers'] | first }}" - when: _vault_init_secret.changed - register: _vault_unseal_secret - - - name: "Unseal all vault nodes" - ednz_cloud.hashistack.vault_unseal: - api_url: "{{ hashicorp_vault_configuration['api_addr'] }}" - tls_verify: "{{ vault_tls_verify }}" - key_shares: "{{ _credentials.vault['keys'] }}" - retries: 5 - delay: 5 - until: _unseal_status.changed or not _unseal_status.failed - register: _unseal_status + file: vault_control_plane.yml + when: + - "'vault_servers' in group_names" + tags: + - vault_servers diff --git a/playbooks/tasks/vault/vault_vars.yml b/playbooks/tasks/vault/vault_vars.yml deleted file mode 100644 index 6a5758b..0000000 --- a/playbooks/tasks/vault/vault_vars.yml +++ /dev/null @@ -1,51 +0,0 @@ ---- -# hashistack configuration merging for vault -- name: "Vault | Merge listener configuration" - ansible.builtin.set_fact: - vault_listener_configuration: "{{ - vault_listener_configuration | - combine((vault_enable_tls | bool) | ternary(vault_tls_listener_configuration, {}), recursive=True) | - combine(vault_extra_listener_configuration | default({}), recursive=True) - }}" - -- name: "Vault | Merge service registration configuration" - vars: - _config_to_merge: - service_registration: "{{ vault_service_registration_configuration }}" - ansible.builtin.set_fact: - hashicorp_vault_configuration: "{{ - hashicorp_vault_configuration | - combine(_config_to_merge) - }}" - when: vault_enable_service_registration - -- name: "Vault | Merge plugins configuration" - vars: - _config_to_merge: - plugin_directory: "{{ vault_plugin_directory }}" - ansible.builtin.set_fact: - hashicorp_vault_configuration: "{{ - hashicorp_vault_configuration | - combine(_config_to_merge) - }}" - when: vault_enable_plugins - -- name: "Vault | Merge logging configuration" - vars: - _config_to_merge: "{{ vault_logging_configuration }}" - ansible.builtin.set_fact: - hashicorp_vault_configuration: "{{ - hashicorp_vault_configuration | - combine(_config_to_merge) - }}" - when: vault_enable_log_to_file - -- name: "Vault | Merge extra configuration settings" - vars: - _config_to_merge: "{{ vault_extra_configuration }}" - ansible.builtin.set_fact: - hashicorp_vault_configuration: "{{ - hashicorp_vault_configuration | - combine(_config_to_merge) - }}" - when: vault_extra_configuration is defined diff --git a/playbooks/templates/credentials.yml.j2 b/playbooks/templates/credentials.yml.j2 index 47dfb36..2382a40 100644 --- a/playbooks/templates/credentials.yml.j2 +++ b/playbooks/templates/credentials.yml.j2 @@ -5,7 +5,7 @@ consul: secret_id: "{{ _consul_root_token }}" tokens: agent: - accessor_id: "{{ _consul_agents_accesor }}" + accessor_id: "{{ _consul_agents_accessor }}" secret_id: "{{ _consul_agents_token }}" vault: accessor_id: "{{ _consul_vault_accessor }}" diff --git a/roles/cni/defaults/main.yml b/roles/cni/defaults/main.yml new file mode 100644 index 0000000..5605796 --- /dev/null +++ b/roles/cni/defaults/main.yml @@ -0,0 +1,7 @@ +--- +# defaults file for cni +cni_plugins_version: "latest" +cni_plugins_install_path: /opt/cni/bin +cni_plugins_install_consul_cni: false +cni_user: nomad +cni_group: nomad diff --git a/roles/cni/handlers/main.yml b/roles/cni/handlers/main.yml new file mode 100644 index 0000000..c921b72 --- /dev/null +++ b/roles/cni/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for cni diff --git a/roles/cni/meta/main.yml b/roles/cni/meta/main.yml new file mode 100644 index 0000000..3ca8af7 --- /dev/null +++ b/roles/cni/meta/main.yml @@ -0,0 +1,28 @@ +--- +# meta file for cni +galaxy_info: + namespace: "ednz_cloud" + role_name: "cni" + author: "Bertrand Lanson" + description: "Install the default cni plugins and the consul-cni plugin" + 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" + - "cni" + - "networking" + - "consul" + +dependencies: [] diff --git a/roles/cni/tasks/cni_install.yml b/roles/cni/tasks/cni_install.yml new file mode 100644 index 0000000..e897d6a --- /dev/null +++ b/roles/cni/tasks/cni_install.yml @@ -0,0 +1,70 @@ +--- +# task/cni_install file for cni +- name: "CNI Plugins | Get release for cni_plugins:{{ cni_plugins_version }}" + vars: + _cni_plugins_url_ext: "{% if cni_plugins_version == 'latest'%}releases{% else %}releases/tags{% endif %}" + ansible.builtin.uri: + url: "{{ cni_github_api }}/{{ cni_github_project }}/{{ _cni_plugins_url_ext }}/{{ cni_plugins_version }}" + return_content: true + register: _cni_plugins_latest_release + +- name: "CNI Plugins | Check if cni plugin is already installed" + ansible.builtin.stat: + path: "{{ cni_plugins_install_path }}/.version" + changed_when: false + check_mode: false + register: _cni_plugins_is_installed + +- name: "CNI Plugins | Check current cni plugin version" + ansible.builtin.command: "cat {{ cni_plugins_install_path }}/.version" + changed_when: false + check_mode: false + register: _cni_plugins_old_release + when: _cni_plugins_is_installed.stat.exists + +- name: "CNI Plugins | Set facts for wanted cni plugins release" + ansible.builtin.set_fact: + cni_plugins_wanted_version: "{{ _cni_plugins_latest_release.json['tag_name']|regex_replace('v', '') }}" + when: _cni_plugins_latest_release.json is defined + and (_cni_plugins_latest_release.json | length > 0) + +- name: "CNI Plugins | Set facts for current cni plugins release" + ansible.builtin.set_fact: + cni_plugins_current_version: "{{ _cni_plugins_old_release.stdout | regex_replace('v', '') }}" + when: _cni_plugins_old_release.stdout is defined + and (_cni_plugins_old_release.stdout | length > 0) + +- name: "CNI Plugins | Install cni plugins" + when: cni_plugins_current_version is not defined + or cni_plugins_wanted_version not in cni_plugins_current_version + block: + - name: "CNI Plugins | Install cni plugins version:{{ cni_plugins_version }}" + ansible.builtin.get_url: + url: "{{ cni_github_url }}/{{ cni_github_project }}/releases/download/v{{ cni_plugins_wanted_version }}/cni-plugins-linux-{{ cni_architecture }}-v{{ cni_plugins_wanted_version }}.tgz" + dest: "/tmp/cni_plugin.tgz" + mode: "0644" + register: _cni_plugins_download_archive + until: _cni_plugins_download_archive is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "CNI Plugins | Unpack cni plugins" + ansible.builtin.unarchive: + src: "/tmp/cni_plugin.tgz" + dest: "{{ cni_plugins_install_path }}" + owner: "{{ cni_user }}" + group: "{{ cni_group }}" + mode: "0755" + remote_src: true + + - name: "CNI Plugins | Remove temporary archive" + ansible.builtin.file: + path: "/tmp/cni_plugin.tgz" + state: absent + + - name: "CNI Plugins | Update version file" + ansible.builtin.copy: + content: "{{ cni_plugins_wanted_version }}" + dest: "{{ cni_plugins_install_path }}/.version" + mode: "0600" diff --git a/roles/cni/tasks/consul_cni_install.yml b/roles/cni/tasks/consul_cni_install.yml new file mode 100644 index 0000000..a04d9ac --- /dev/null +++ b/roles/cni/tasks/consul_cni_install.yml @@ -0,0 +1,114 @@ +--- +# task/consul_cni_install file for cni +- name: "CNI Plugins | Set wanted consul-cni version to latest tag" + ansible.builtin.set_fact: + _cni_consul_cni_wanted_version: "{{ _cni_plugins_latest_release.json['tag_name']|regex_replace('v', '') }}" + when: cni_plugins_version == 'latest' + +- name: "CNI Plugins | Set wanted consul-cni version to {{ cni_plugins_version }}" + ansible.builtin.set_fact: + _cni_consul_cni_wanted_version: "{{ cni_plugins_version|regex_replace('v', '') }}" + when: cni_plugins_version != 'latest' + +- name: "CNI Plugins | Get current consul-cni version" + block: + - name: "CNI Plugins | Stat consul-cni version file" + ansible.builtin.stat: + path: "{{ cni_plugins_install_path }}/.consul-cni-version" + changed_when: false + check_mode: false + register: _cni_consul_cni_version_file + + - name: "CNI Plugins | Get current consul-cni version" + ansible.builtin.slurp: + src: "{{ _cni_consul_cni_version_file.stat.path }}" + when: + - _cni_consul_cni_version_file.stat.exists + - _cni_consul_cni_version_file.stat.isreg + register: _cni_consul_cni_current_version + +- name: "CNI Plugins | Download and install consul-cni binary" + when: _cni_consul_cni_current_version is not defined + or _cni_consul_cni_wanted_version != (_cni_consul_cni_current_version.content|default('')|b64decode) + block: + - name: "CNI Plugins | Set consul-cni package name to download" + ansible.builtin.set_fact: + _cni_consul_cni_package_name: >- + consul-cni_{{ _cni_consul_cni_wanted_version }}_linux_{{ cni_deb_architecture_map[ansible_architecture] }}.zip + _cni_consul_cni_shasum_file_name: >- + consul-cni_{{ _cni_consul_cni_wanted_version }}_SHA256SUMS + + - name: "CNI Plugins | Download checksum file for consul-cni archive" + ansible.builtin.get_url: + url: "{{ cni_consul_cni_repository_url }}/{{ _cni_consul_cni_wanted_version }}/{{ _cni_consul_cni_shasum_file_name }}" + dest: "/tmp/{{ _cni_consul_cni_shasum_file_name }}" + mode: "0644" + register: _cni_consul_cni_checksum_file + until: _cni_consul_cni_checksum_file is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "CNI Plugins | Extract correct checksum from checksum file" + ansible.builtin.command: + cmd: 'grep "{{ _cni_consul_cni_package_name }}" /tmp/{{ _cni_consul_cni_shasum_file_name }}' + changed_when: false + register: _cni_consul_cni_expected_checksum_line + + - name: "CNI Plugins | Parse the expected checksum" + ansible.builtin.set_fact: + _cni_consul_cni_expected_checksum: "{{ _cni_consul_cni_expected_checksum_line.stdout.split()[0] }}" + + - name: "CNI Plugins | Download consul-cni binary archive" + ansible.builtin.get_url: + url: "{{ cni_consul_cni_repository_url }}/{{ _cni_consul_cni_wanted_version }}/{{ _cni_consul_cni_package_name }}" + dest: "/tmp/{{ _cni_consul_cni_package_name }}" + mode: "0644" + checksum: "sha256:{{ _cni_consul_cni_expected_checksum }}" + register: _cni_consul_cni_binary_archive + until: _cni_consul_cni_binary_archive is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "CNI Plugins | Create temporary directory for archive decompression" + ansible.builtin.file: + path: /tmp/consul-cni + state: directory + mode: "0755" + + - name: "CNI Plugins | Unpack consul-cni archive" + ansible.builtin.unarchive: + src: "/tmp/{{ _cni_consul_cni_package_name }}" + dest: "/tmp/consul-cni" + owner: "{{ cni_user }}" + group: "{{ cni_group }}" + mode: "0755" + remote_src: true + + - name: "CNI Plugins | Copy consul-cni binary to {{ cni_plugins_install_path }}" + ansible.builtin.copy: + src: /tmp/consul-cni/consul-cni + dest: "{{ cni_plugins_install_path }}" + owner: "{{ cni_user }}" + group: "{{ cni_user }}" + mode: "0755" + remote_src: true + force: true + + - name: "CNI Plugins | Update consul-cni version file" + ansible.builtin.copy: + content: "{{ _cni_consul_cni_wanted_version }}" + dest: "{{ cni_plugins_install_path }}/.consul-cni-version" + owner: "{{ cni_user }}" + group: "{{ cni_group }}" + mode: "0600" + + - name: "CNI Plugins | Cleanup temporary directory" + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/consul-cni + - /tmp/{{ _cni_consul_cni_package_name }} + - /tmp/{{ _cni_consul_cni_shasum_file_name }} diff --git a/roles/cni/tasks/main.yml b/roles/cni/tasks/main.yml new file mode 100644 index 0000000..c85867f --- /dev/null +++ b/roles/cni/tasks/main.yml @@ -0,0 +1,10 @@ +--- +# task/main file for cni +- name: "CNI Plugins | Import prerequisites.yml" + ansible.builtin.include_tasks: prerequisites.yml + +- name: "CNI Plugins | Import cni_install.yml" + ansible.builtin.include_tasks: cni_install.yml + +- name: "CNI Plugins | Import consul_cni_install.yml" + ansible.builtin.include_tasks: consul_cni_install.yml diff --git a/roles/cni/tasks/prerequisites.yml b/roles/cni/tasks/prerequisites.yml new file mode 100644 index 0000000..24e6367 --- /dev/null +++ b/roles/cni/tasks/prerequisites.yml @@ -0,0 +1,21 @@ +--- +# task/prerequisites file for cni +- name: "CNI Plugins | Create group {{ cni_group }}" + ansible.builtin.group: + name: "{{ cni_user }}" + state: present + +- name: "CNI Plugins | Create user {{ cni_user }}" + ansible.builtin.user: + name: "{{ cni_user }}" + group: "{{ cni_group }}" + shell: /bin/false + state: present + +- name: "CNI Plugins | Create directory {{ cni_plugins_install_path }}" + ansible.builtin.file: + path: "{{ cni_plugins_install_path }}" + state: directory + owner: "{{ cni_user }}" + group: "{{ cni_group }}" + mode: "0755" diff --git a/roles/cni/vars/main.yml b/roles/cni/vars/main.yml new file mode 100644 index 0000000..e6949d8 --- /dev/null +++ b/roles/cni/vars/main.yml @@ -0,0 +1,11 @@ +--- +cni_github_api: https://api.github.com/repos +cni_github_project: containernetworking/plugins +cni_github_url: https://github.com +cni_deb_architecture_map: + x86_64: "amd64" + aarch64: "arm64" + armv7l: "arm" + armv6l: "arm" +cni_architecture: "{{ cni_deb_architecture_map[ansible_architecture] | default(ansible_architecture) }}" +cni_consul_cni_repository_url: https://releases.hashicorp.com/consul-cni diff --git a/roles/consul/defaults/main.yml b/roles/consul/defaults/main.yml new file mode 100644 index 0000000..9f43d7b --- /dev/null +++ b/roles/consul/defaults/main.yml @@ -0,0 +1,146 @@ +--- +# defaults file for hashicorp_consul + +consul_version: "latest" +consul_start_service: true +consul_config_dir: "/etc/consul.d" +consul_data_dir: "/opt/consul" +consul_certs_dir: "{{ consul_config_dir }}/tls" +consul_logs_dir: "/var/log/consul" + +consul_envoy_install: false +consul_envoy_version: latest + +consul_extra_files: false +consul_extra_files_list: [] + +consul_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. + +consul_extra_configuration: {} + +########### +# general # +########### + +consul_domain: consul +consul_datacenter: dc1 +consul_primary_datacenter: "{{ consul_datacenter }}" +consul_gossip_encryption_key: "{{ 'mysupersecretgossipencryptionkey'|b64encode }}" +consul_enable_script_checks: false + +####################### +# leave configuration # +####################### + +consul_leave_on_terminate: true +consul_rejoin_after_leave: true + +###################### +# join configuration # +###################### + +consul_join_configuration: + retry_join: + - "{{ ansible_default_ipv4.address }}" + retry_interval: 30s + retry_max: 0 + +######################## +# server configuration # +######################## + +consul_enable_server: true +consul_bootstrap_expect: 1 + +#################### +# ui configuration # +#################### + +consul_ui_configuration: + enabled: "{{ consul_enable_server }}" + +######################### +# address configuration # +######################### + +consul_bind_addr: "0.0.0.0" +consul_advertise_addr: "{{ ansible_default_ipv4.address }}" +consul_address_configuration: + client_addr: "{{ consul_bind_addr }}" + bind_addr: "{{ consul_advertise_addr }}" + advertise_addr: "{{ consul_advertise_addr }}" + +##################### +# ACL configuration # +##################### + +consul_acl_configuration: + enabled: false + default_policy: "deny" + enable_token_persistence: true + # tokens: + # agent: "" + +############################## +# service mesh configuration # +############################## + +consul_mesh_configuration: + enabled: false + +##################### +# DNS configuration # +##################### + +consul_dns_configuration: + allow_stale: true + enable_truncate: true + only_passing: true + +################ +# internal tls # +################ + +consul_enable_tls: false +consul_tls_configuration: + defaults: + ca_file: "/etc/ssl/certs/ca-certificates.crt" + cert_file: "{{ consul_certs_dir }}/cert.pem" + key_file: "{{ consul_certs_dir }}/key.pem" + verify_incoming: false + verify_outgoing: true + internal_rpc: + verify_server_hostname: true + +consul_certificates_extra_files_dir: + [] + # - src: "" + # dest: "{{ consul_certs_dir }}" + +########################### +# telemetry configuration # +########################### + +consul_enable_prometheus_metrics: false +consul_prometheus_retention_time: 60s +consul_telemetry_configuration: {} + +########### +# logging # +########### + +consul_log_level: info +consul_enable_log_to_file: false +consul_log_to_file_configuration: + log_file: "{{ consul_logs_dir }}/consul.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 diff --git a/roles/consul/handlers/main.yml b/roles/consul/handlers/main.yml new file mode 100644 index 0000000..7296134 --- /dev/null +++ b/roles/consul/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashicorp_consul diff --git a/roles/consul/meta/main.yml b/roles/consul/meta/main.yml new file mode 100644 index 0000000..965aeef --- /dev/null +++ b/roles/consul/meta/main.yml @@ -0,0 +1,26 @@ +--- +# meta file for hashicorp_consul +galaxy_info: + namespace: "ednz_cloud" + role_name: "hashicorp_consul" + author: "Bertrand Lanson" + description: "Install and configure hashicorp consul 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" + - "consul" + +dependencies: [] diff --git a/roles/consul/tasks/configure.yml b/roles/consul/tasks/configure.yml new file mode 100644 index 0000000..22bdcb1 --- /dev/null +++ b/roles/consul/tasks/configure.yml @@ -0,0 +1,74 @@ +--- +# task/configure file for hashicorp_consul +- name: "Consul | Create consul.env" + ansible.builtin.template: + src: consul.env.j2 + dest: "{{ consul_config_dir }}/consul.env" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0600" + register: _consul_env_file + +- name: "Consul | Copy consul.json template" + ansible.builtin.template: + src: consul.json.j2 + dest: "{{ consul_config_dir }}/consul.json" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0600" + register: _consul_config_file + +- name: "Consul | Set restart-check variable" + ansible.builtin.set_fact: + _consul_service_need_restart: true + when: _consul_env_file.changed or + _consul_config_file.changed + +- name: "Consul | Copy extra configuration files" + when: consul_extra_files + block: + - name: "Consul | Get extra file types" + ansible.builtin.stat: + path: "{{ item.src }}" + loop: "{{ consul_extra_files_list }}" + register: consul_extra_file_stat + delegate_to: localhost + + - name: "Consul | Set list for file sources" + vars: + _consul_file_sources: [] + ansible.builtin.set_fact: + _consul_file_sources: "{{ _consul_file_sources + [item.item] }}" + when: item.stat.isreg + loop: "{{ consul_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Consul | Set list for directory sources" + vars: + _consul_dir_sources: [] + ansible.builtin.set_fact: + _consul_dir_sources: "{{ _consul_dir_sources + [item.item] }}" + when: item.stat.isdir + loop: "{{ consul_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Consul | Template extra file sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest | regex_replace('\\.j2$', '') }}" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0700" + loop: "{{ _consul_file_sources }}" + when: _consul_file_sources is defined + + - name: "Consul | Template extra directory sources" + ansible.builtin.include_tasks: recursive_copy_extra_dirs.yml + loop: "{{ _consul_dir_sources }}" + loop_control: + loop_var: dir_source_item + when: _consul_dir_sources is defined diff --git a/roles/consul/tasks/install.yml b/roles/consul/tasks/install.yml new file mode 100644 index 0000000..dab00b7 --- /dev/null +++ b/roles/consul/tasks/install.yml @@ -0,0 +1,141 @@ +--- +# task/install file for hashicorp_consul +- name: "Consul | Get latest release of consul" + when: consul_version == 'latest' + block: + - name: "Consul | Get latest consul release from github api" + ansible.builtin.uri: + url: "{{ consul_github_api }}/{{ consul_github_project }}/releases/latest" + return_content: true + register: _consul_latest_release + + - name: "Consul | Set wanted consul version to latest tag" + ansible.builtin.set_fact: + _consul_wanted_version: "{{ _consul_latest_release.json['tag_name']|regex_replace('v', '') }}" + +- name: "Consul | Set wanted consul version to {{ consul_version }}" + ansible.builtin.set_fact: + _consul_wanted_version: "{{ consul_version|regex_replace('v', '') }}" + when: consul_version != 'latest' + +- name: "Consul | Get current consul version" + block: + - name: "Consul | Stat consul version file" + ansible.builtin.stat: + path: "{{ consul_config_dir }}/.version" + changed_when: false + check_mode: false + register: _consul_version_file + + - name: "Consul | Get current consul version" + ansible.builtin.slurp: + src: "{{ _consul_version_file.stat.path }}" + when: + - _consul_version_file.stat.exists + - _consul_version_file.stat.isreg + register: _consul_current_version + +- name: "Consul | Download and install consul binary" + when: _consul_current_version is not defined + or _consul_wanted_version != (_consul_current_version.content|default('')|b64decode) + block: + - name: "Consul | Set consul package name to download" + ansible.builtin.set_fact: + _consul_package_name: >- + consul_{{ _consul_wanted_version }}_linux_{{ consul_deb_architecture_map[ansible_architecture] }}.zip + _consul_shasum_file_name: >- + consul_{{ _consul_wanted_version }}_SHA256SUMS + + - name: "Consul | Download checksum file for consul archive" + ansible.builtin.get_url: + url: "{{ consul_repository_url }}/{{ _consul_wanted_version }}/{{ _consul_shasum_file_name }}" + dest: "/tmp/{{ _consul_shasum_file_name }}" + mode: "0644" + register: _consul_checksum_file + until: _consul_checksum_file is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Consul | Extract correct checksum from checksum file" + ansible.builtin.command: + cmd: 'grep "{{ _consul_package_name }}" /tmp/{{ _consul_shasum_file_name }}' + changed_when: false + register: _consul_expected_checksum_line + + - name: "Consul | Parse the expected checksum" + ansible.builtin.set_fact: + _consul_expected_checksum: "{{ _consul_expected_checksum_line.stdout.split()[0] }}" + + - name: "Consul | Download consul binary archive" + ansible.builtin.get_url: + url: "{{ consul_repository_url }}/{{ _consul_wanted_version }}/{{ _consul_package_name }}" + dest: "/tmp/{{ _consul_package_name }}" + mode: "0644" + checksum: "sha256:{{ _consul_expected_checksum }}" + register: _consul_binary_archive + until: _consul_binary_archive is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Consul | Create temporary directory for archive decompression" + ansible.builtin.file: + path: /tmp/consul + state: directory + mode: "0755" + + - name: "Consul | Unpack consul archive" + ansible.builtin.unarchive: + src: "/tmp/{{ _consul_package_name }}" + dest: "/tmp/consul" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0755" + remote_src: true + + - name: "Consul | Copy consul binary to {{ consul_binary_path }}" + ansible.builtin.copy: + src: /tmp/consul/consul + dest: "{{ consul_binary_path }}" + owner: root + group: root + mode: "0755" + remote_src: true + force: true + + - name: "Consul | Update consul version file" + ansible.builtin.copy: + content: "{{ _consul_wanted_version }}" + dest: "{{ consul_config_dir }}/.version" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0600" + + - name: "Consul | Set restart-check variable" + ansible.builtin.set_fact: + _consul_service_need_restart: true + + - name: "Consul | Cleanup temporary directory" + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/consul + - /tmp/{{ _consul_package_name }} + - /tmp/{{ _consul_shasum_file_name }} + +- name: "Consul | Copy systemd service file for consul" + ansible.builtin.template: + src: "consul.service.j2" + dest: "/etc/systemd/system/{{ consul_service_name }}.service" + owner: root + group: root + mode: "0644" + register: _consul_unit_file + +- name: "Consul | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _consul_service_need_reload: true + _consul_service_need_restart: true + when: _consul_unit_file.changed # noqa: no-handler diff --git a/roles/consul/tasks/install_envoy.yml b/roles/consul/tasks/install_envoy.yml new file mode 100644 index 0000000..98ea19d --- /dev/null +++ b/roles/consul/tasks/install_envoy.yml @@ -0,0 +1,65 @@ +--- +# task/install_envoy file for hashicorp_consul +- name: "Consul | Get release for envoy:{{ consul_envoy_version }}" + vars: + _envoy_url_ext: "{% if consul_envoy_version == 'latest'%}releases{% else %}releases/tags{% endif %}" + ansible.builtin.uri: + url: "{{ consul_github_api }}/{{ consul_envoy_github_project }}/{{ _envoy_url_ext }}/{{ consul_envoy_version }}" + return_content: true + register: _envoy_new_release + +- name: "Consul | Check if envoy is already installed" + ansible.builtin.stat: + path: "{{ consul_data_dir }}/envoy/version" + changed_when: false + check_mode: false + register: _envoy_is_installed + +- name: "Consul | Check current envoy version" + ansible.builtin.command: "cat {{ consul_data_dir }}/envoy/version" + changed_when: false + check_mode: false + register: _envoy_old_release + when: _envoy_is_installed.stat.exists + +- name: "Consul | Set facts for wanted envoy release" + ansible.builtin.set_fact: + consul_envoy_wanted_version: "{{ _envoy_new_release.json['tag_name']|regex_replace('v', '') }}" + when: _envoy_new_release.json is defined + and (_envoy_new_release.json | length > 0) + +- name: "Consul | Set facts for current envoy release" + ansible.builtin.set_fact: + consul_envoy_current_version: "{{ _envoy_old_release.stdout | regex_replace('v', '') }}" + when: _envoy_old_release.stdout is defined + and (_envoy_old_release.stdout | length > 0) + +- name: "Consul | Create envoy directory" + ansible.builtin.file: + path: "{{ consul_data_dir }}/envoy" + state: directory + mode: "0775" + +- name: "Consul | Install envoy" + when: consul_envoy_current_version is not defined + or consul_envoy_wanted_version not in consul_envoy_current_version + block: + - name: "Consul | Remove old compose binary if different" + ansible.builtin.file: + path: "{{ consul_envoy_binary_path }}" + state: absent + register: _envoy_binary_removed + + - name: "Consul | Download and install envoy version:{{ consul_envoy_version }}" + ansible.builtin.get_url: + url: "{{ consul_github_url }}/{{ consul_envoy_github_project }}/releases/download/v{{ consul_envoy_wanted_version }}/envoy-{{ consul_envoy_wanted_version }}-linux-{{ consul_envoy_architecture }} " + dest: "{{ consul_envoy_binary_path }}" + owner: root + group: root + mode: "0755" + + - name: "Consul | Update version file" + ansible.builtin.copy: + content: "{{ consul_envoy_wanted_version }}" + dest: "{{ consul_data_dir }}/envoy/version" + mode: "0600" diff --git a/roles/consul/tasks/main.yml b/roles/consul/tasks/main.yml new file mode 100644 index 0000000..cb4fcb0 --- /dev/null +++ b/roles/consul/tasks/main.yml @@ -0,0 +1,47 @@ +--- +# task/main file for hashicorp_consul +- name: "Consul | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _consul_service_need_reload: false + _consul_service_need_restart: false + +- name: "Consul | Import merge_variables.yml" + ansible.builtin.include_tasks: merge_variables.yml + +- name: "Consul | Import prerequisites.yml" + ansible.builtin.include_tasks: prerequisites.yml + +- name: "Consul | Import install_envoy.yml" + ansible.builtin.include_tasks: install_envoy.yml + when: consul_envoy_install + +- name: "Consul | Import install.yml" + ansible.builtin.include_tasks: "install.yml" + +- name: "Consul | Import configure.yml" + ansible.builtin.include_tasks: configure.yml + +- name: "Consul | Populate service facts" + ansible.builtin.service_facts: + +- name: "Consul | Set restart-check variable" + ansible.builtin.set_fact: + _consul_service_need_restart: true + when: ansible_facts.services[consul_service_name~'.service'].state != 'running' + +- name: "Consul | Enable service: {{ consul_service_name }}" + ansible.builtin.service: + name: "{{ consul_service_name }}" + enabled: true + +- name: "Consul | Reload systemd daemon" + ansible.builtin.systemd: + daemon_reload: true + when: _consul_service_need_reload + +- name: "Consul | Start service: {{ consul_service_name }}" + ansible.builtin.service: + name: "{{ consul_service_name }}" + state: restarted + throttle: 1 + when: _consul_service_need_restart diff --git a/roles/consul/tasks/merge_variables.yml b/roles/consul/tasks/merge_variables.yml new file mode 100644 index 0000000..3e2ab80 --- /dev/null +++ b/roles/consul/tasks/merge_variables.yml @@ -0,0 +1,103 @@ +--- +# task/merge_variables file for hashicorp_consul +- name: "Consul | Merge stringified configuration" + vars: + _config_to_merge: "{{ consul_configuration_string }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge|from_yaml, recursive=true) + }}" + +- name: "Consul | Merge server specific stringified configuration" + vars: + _config_to_merge: "{{ consul_server_configuration_string }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge|from_yaml, recursive=true) + }}" + when: + - consul_enable_server + +- name: "Consul | Merge join configuration" + vars: + _config_to_merge: "{{ consul_join_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge|from_yaml, recursive=true) + }}" + +- name: "Consul | Merge addresses configuration" + vars: + _config_to_merge: "{{ consul_address_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge, recursive=true) + }}" + +- name: "Consul | Merge TLS configuration" + when: consul_enable_tls + block: + - name: "Consul | Merge TLS configuration" + vars: + _config_to_merge: + tls: "{{ consul_tls_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge, recursive=true) + }}" + + - name: "Consul | Add certificates directory to extra_files_dir" + ansible.builtin.set_fact: + consul_extra_files_list: "{{ + consul_extra_files_list + + consul_certificates_extra_files_dir + | unique + | sort + }}" + +- name: "Consul | Merge extra configuration settings" + vars: + _config_to_merge: "{{ consul_extra_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge, recursive=true) + }}" + +- name: "Consul | Merge log to file configuration" + vars: + _config_to_merge: "{{ consul_log_to_file_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: consul_enable_log_to_file + +- name: "Consul | Merge telemetry configuration" + block: + - name: "Consul | Merge prometheus metrics configuration" + vars: + _config_to_merge: + prometheus_retention_time: "{{ consul_prometheus_retention_time }}" + ansible.builtin.set_fact: + consul_telemetry_configuration: "{{ + consul_telemetry_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: consul_enable_prometheus_metrics + + - name: "Consul | Merge telemtry configuration" + vars: + _config_to_merge: + telemetry: "{{ consul_telemetry_configuration }}" + ansible.builtin.set_fact: + consul_configuration: "{{ + consul_configuration | + combine(_config_to_merge, recursive=true) + }}" diff --git a/roles/consul/tasks/prerequisites.yml b/roles/consul/tasks/prerequisites.yml new file mode 100644 index 0000000..1e34e83 --- /dev/null +++ b/roles/consul/tasks/prerequisites.yml @@ -0,0 +1,46 @@ +--- +# task/prerequisites file for hashicorp_consul +- name: "Consul | Create group {{ consul_group }}" + ansible.builtin.group: + name: "{{ consul_group }}" + state: present + +- name: "Consul | Create user {{ consul_user }}" + ansible.builtin.user: + name: "{{ consul_user }}" + group: "{{ consul_group }}" + shell: /bin/false + state: present + +- name: "Consul | Create directory {{ consul_config_dir }}" + ansible.builtin.file: + path: "{{ consul_config_dir }}" + state: directory + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0755" + +- name: "Consul | Create directory {{ consul_data_dir}}" + ansible.builtin.file: + path: "{{ consul_data_dir }}" + state: directory + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0755" + +- name: "Consul | Create directory {{ consul_certs_dir }}" + ansible.builtin.file: + path: "{{ consul_certs_dir }}" + state: directory + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0755" + +- name: "Consul | Create directory {{ consul_logs_dir }}" + ansible.builtin.file: + path: "{{ consul_logs_dir }}" + state: directory + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0755" + when: consul_enable_log_to_file diff --git a/roles/consul/tasks/recursive_copy_extra_dirs.yml b/roles/consul/tasks/recursive_copy_extra_dirs.yml new file mode 100644 index 0000000..09f43e4 --- /dev/null +++ b/roles/consul/tasks/recursive_copy_extra_dirs.yml @@ -0,0 +1,27 @@ +--- +# task/recursive_copy_extra_dirs file for hashicorp_consul +- name: "Consul | Ensure destination directory exists" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}" + recurse: true + state: directory + mode: "0775" + +- name: "Consul | Create extra directory sources" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}/{{ item.path }}" + recurse: true + state: directory + mode: "0775" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'directory' + +- name: "Consul | Template extra directory sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ dir_source_item.dest }}/{{ item.path | regex_replace('\\.j2$', '') }}" + owner: "{{ consul_user }}" + group: "{{ consul_group }}" + mode: "0700" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'file' diff --git a/roles/consul/templates/consul.env.j2 b/roles/consul/templates/consul.env.j2 new file mode 100644 index 0000000..f5f982c --- /dev/null +++ b/roles/consul/templates/consul.env.j2 @@ -0,0 +1,4 @@ +# {{ ansible_managed }} +{% for item in consul_env_variables %} +{{ item }}="{{ consul_env_variables[item] }}" +{% endfor %} diff --git a/roles/consul/templates/consul.json.j2 b/roles/consul/templates/consul.json.j2 new file mode 100644 index 0000000..9bc6d5f --- /dev/null +++ b/roles/consul/templates/consul.json.j2 @@ -0,0 +1 @@ +{{ consul_configuration|to_nice_json }} diff --git a/roles/consul/templates/consul.service.j2 b/roles/consul/templates/consul.service.j2 new file mode 100644 index 0000000..5b1cd38 --- /dev/null +++ b/roles/consul/templates/consul.service.j2 @@ -0,0 +1,20 @@ +[Unit] +Description=Consul +Documentation=https://developer.hashicorp.com/consul/docs +Requires=network-online.target +After=network-online.target +ConditionFileNotEmpty={{ consul_config_dir }}/consul.json + +[Service] +EnvironmentFile=-{{ consul_config_dir }}/consul.env +User={{ consul_user }} +Group={{ consul_group }} +ExecStart={{ consul_binary_path }} agent -config-dir={{ consul_config_dir }} +ExecReload=/bin/kill --signal HUP $MAINPID +KillMode=process +KillSignal=SIGTERM +Restart=on-failure +LimitNOFILE=65536 + +[Install] +WantedBy=multi-user.target diff --git a/roles/consul/vars/main.yml b/roles/consul/vars/main.yml new file mode 100644 index 0000000..f2139b1 --- /dev/null +++ b/roles/consul/vars/main.yml @@ -0,0 +1,58 @@ +--- +# vars file for hashicorp_consul +consul_user: consul +consul_group: consul +consul_binary_path: /usr/local/bin/consul +consul_envoy_binary_path: /usr/local/bin/envoy +consul_deb_architecture_map: + x86_64: "amd64" + aarch64: "arm64" + armv7l: "arm" + armv6l: "arm" +consul_envoy_architecture_map: + x86_64: "x86_64" + aarch64: "aarch64" +consul_architecture: "{{ consul_deb_architecture_map[ansible_architecture] | default(ansible_architecture) }}" +consul_envoy_architecture: "{{ consul_envoy_architecture_map[ansible_architecture] | default(ansible_architecture) }}" +consul_service_name: "consul" +consul_github_api: https://api.github.com/repos +consul_envoy_github_project: envoyproxy/envoy +consul_github_project: hashicorp/consul +consul_github_url: https://github.com +consul_repository_url: https://releases.hashicorp.com/consul + +consul_configuration: + domain: "{{ consul_domain }}" + datacenter: "{{ consul_datacenter }}" + primary_datacenter: "{{ consul_primary_datacenter }}" + data_dir: "{{ consul_data_dir }}" + encrypt: "{{ consul_gossip_encryption_key }}" + server: "{{ consul_enable_server }}" + ui_config: "{{ consul_ui_configuration }}" + connect: "{{ consul_mesh_configuration }}" + leave_on_terminate: "{{ consul_leave_on_terminate }}" + rejoin_after_leave: "{{ consul_rejoin_after_leave }}" + enable_script_checks: "{{ consul_enable_script_checks }}" + enable_syslog: true + acl: "{{ consul_acl_configuration }}" + dns_config: "{{ consul_dns_configuration }}" + log_level: "{{ consul_log_level }}" + ports: + dns: 8600 + server: 8300 + serf_lan: 8301 + serf_wan: 8302 + sidecar_min_port: 21000 + sidecar_max_port: 21255 + expose_min_port: 21500 + expose_max_port: 21755 + +consul_configuration_string: | + ports: + http: {{ ('8500'|int) if not consul_enable_tls else ('-1' | int) }} + https: {{ ('8501'|int) if consul_enable_tls else ('-1' | int) }} + grpc: {{ ('8502'|int) if not consul_enable_tls else ('-1' | int) }} + grpc_tls: {{ ('8503'|int) if consul_enable_tls else ('-1' | int) }} + +consul_server_configuration_string: | + bootstrap_expect: {{ consul_bootstrap_expect }} diff --git a/roles/hashistack/defaults/main.yml b/roles/hashistack/defaults/main.yml new file mode 100644 index 0000000..e9554f3 --- /dev/null +++ b/roles/hashistack/defaults/main.yml @@ -0,0 +1,17 @@ +--- +# defaults file for hashistack +hashistack_configuration_directory: "{{ lookup('env', 'PWD') }}/etc/hashistack" +hashistack_sub_configuration_directories: + secrets: "{{ hashistack_configuration_directory }}/secrets" + certificates: "{{ hashistack_configuration_directory }}/certificates" + nomad_servers: "{{ hashistack_configuration_directory }}/nomad_servers" + vault_servers: "{{ hashistack_configuration_directory }}/vault_servers" + consul_servers: "{{ hashistack_configuration_directory }}/consul_servers" + +hashistack_configuration_global_vars_file: "globals.yml" +hashistack_configuration_credentials_vars_file: "credentials.yml" + +hashistack_remote_config_dir: "/etc/hashistack" +hashistack_remote_log_dir: "/var/log/hashistack" + +hashistack_only_load_credentials: false diff --git a/roles/hashistack/handlers/main.yml b/roles/hashistack/handlers/main.yml new file mode 100644 index 0000000..e0911c6 --- /dev/null +++ b/roles/hashistack/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashistack diff --git a/roles/hashistack/meta/main.yml b/roles/hashistack/meta/main.yml new file mode 100644 index 0000000..7db3860 --- /dev/null +++ b/roles/hashistack/meta/main.yml @@ -0,0 +1,25 @@ +--- +# meta file for hashistack +galaxy_info: + namespace: "ednz_cloud" + role_name: "hashistack" + author: "Bertrand Lanson" + description: "Merge variables for the playbooks contained in ednz_cloud.hashistack collection" + 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" + +dependencies: [] diff --git a/playbooks/tasks/misc/load_ca_certificates.yml b/roles/hashistack/tasks/load_ca_certificates.yml similarity index 78% rename from playbooks/tasks/misc/load_ca_certificates.yml rename to roles/hashistack/tasks/load_ca_certificates.yml index 4ab0d47..5636347 100644 --- a/playbooks/tasks/misc/load_ca_certificates.yml +++ b/roles/hashistack/tasks/load_ca_certificates.yml @@ -1,13 +1,14 @@ --- +# task/load_ca_certificates file for hashistack - name: "Check if CA directory exists" ansible.builtin.stat: - path: "{{ sub_configuration_directories['certificates'] }}/ca" + path: "{{ hashistack_sub_configuration_directories['certificates'] }}/ca" register: _hashistack_ca_directory delegate_to: localhost - name: "Find custom ca certificates to copy" ansible.builtin.find: - paths: "{{ sub_configuration_directories['certificates'] }}/ca" + paths: "{{ hashistack_sub_configuration_directories['certificates'] }}/ca" patterns: "*.crt" register: _hashistack_cacert_files delegate_to: localhost @@ -30,10 +31,12 @@ mode: 0644 loop: "{{ _hashistack_cacert_files.files }}" register: _hashistack_copied_ca + when: not _hashistack_cacert_files.skipped | default(False) - name: "Copy and update trust store" + when: not _hashistack_copied_ca.skipped | default(False) block: - - name: "Copy ca certificates to /usr/loca/share/ca-certificates" + - name: "Copy ca certificates to /usr/local/share/ca-certificates" ansible.builtin.file: state: link src: "{{ item.dest }}" diff --git a/playbooks/tasks/misc/load_credentials_vars.yml b/roles/hashistack/tasks/load_credentials_vars.yml similarity index 53% rename from playbooks/tasks/misc/load_credentials_vars.yml rename to roles/hashistack/tasks/load_credentials_vars.yml index 7c8980d..b791254 100644 --- a/playbooks/tasks/misc/load_credentials_vars.yml +++ b/roles/hashistack/tasks/load_credentials_vars.yml @@ -1,17 +1,18 @@ --- -- name: "Stat credentials file" +# task/load_credentials_vars file for hashistack +- name: "Variables | Stat credentials file" ansible.builtin.stat: - path: "{{ sub_configuration_directories['secrets'] }}/{{ configuration_credentials_vars_file }}" + path: "{{ hashistack_sub_configuration_directories['secrets'] }}/{{ hashistack_configuration_credentials_vars_file }}" register: _credentials_file delegate_to: localhost -- name: "Stat vault credentials file" +- name: "Variables | Stat vault credentials file" ansible.builtin.stat: - path: "{{ sub_configuration_directories['secrets'] }}/vault.yml" + path: "{{ hashistack_sub_configuration_directories['secrets'] }}/vault.yml" register: _vault_credentials_file delegate_to: localhost -- name: "Make sure credentials file exists" +- name: "Variables | Make sure credentials file exists" ansible.builtin.assert: that: - _credentials_file.stat.exists @@ -19,24 +20,24 @@ Credentials file {{ _credentials_file.stat.path }} was not found, cannot continue without it. delegate_to: localhost -- name: "Load credentials variables" +- name: "Variables | Load credentials variables" ansible.builtin.include_vars: - dir: "{{ sub_configuration_directories['secrets'] }}" - files_matching: "{{ configuration_credentials_vars_file }}" + dir: "{{ hashistack_sub_configuration_directories['secrets'] }}" + files_matching: "{{ hashistack_configuration_credentials_vars_file }}" depth: 1 name: _credentials delegate_to: localhost -- name: "Load vault credentials if vault.yml exists" +- name: "Variables | Load vault credentials if vault.yml exists" ansible.builtin.include_vars: - dir: "{{ sub_configuration_directories['secrets'] }}" + dir: "{{ hashistack_sub_configuration_directories['secrets'] }}" files_matching: "vault.yml" depth: 1 name: _vault_credentials when: _vault_credentials_file.stat.exists delegate_to: localhost -- name: "Merge vault credentials into _credentials" +- name: "Variables | Merge vault credentials into _credentials" vars: _config_to_merge: vault: "{{ _vault_credentials }}" diff --git a/roles/hashistack/tasks/load_global_vars.yml b/roles/hashistack/tasks/load_global_vars.yml new file mode 100644 index 0000000..7ac8be4 --- /dev/null +++ b/roles/hashistack/tasks/load_global_vars.yml @@ -0,0 +1,27 @@ +--- +# task/load_global_vars file for hashistack +- name: "Variables | Include all default variables" + ansible.builtin.include_vars: + dir: "{{ playbook_dir }}/group_vars/all/" + depth: 1 + extensions: ["yml"] + delegate_to: localhost + +- name: "Variables | Stat global configuration file" + ansible.builtin.stat: + path: "{{ hashistack_configuration_directory }}/{{ hashistack_configuration_global_vars_file }}" + register: _global_config_file + delegate_to: localhost + +- name: "Variables | Make sure global configuration file exists" + ansible.builtin.assert: + that: + - _global_config_file.stat.exists + delegate_to: localhost + +- name: "Variables | Load global variables" + ansible.builtin.include_vars: + dir: "{{ hashistack_configuration_directory }}" + files_matching: "{{ hashistack_configuration_global_vars_file }}" + depth: 1 + delegate_to: localhost diff --git a/roles/hashistack/tasks/load_group_vars.yml b/roles/hashistack/tasks/load_group_vars.yml new file mode 100644 index 0000000..e18360f --- /dev/null +++ b/roles/hashistack/tasks/load_group_vars.yml @@ -0,0 +1,22 @@ +--- +# task/load_group_vars file for hashistack +- name: "Variables | Stat group specific config file" + ansible.builtin.stat: + path: "{{ hashistack_configuration_directory }}/{{ group_name }}/{{ hashistack_configuration_global_vars_file }}" + register: _group_config_file + loop: "{{ group_names }}" + loop_control: + loop_var: group_name + delegate_to: localhost + +- name: "Variables | Load group specific variables" + ansible.builtin.include_vars: + dir: "{{ hashistack_configuration_directory }}/{{ item.group_name }}" + files_matching: "{{ hashistack_configuration_global_vars_file }}" + depth: 1 + loop: "{{ _group_config_file.results }}" + when: item.stat.exists + and item.group_name in group_names + loop_control: + loop_var: item + delegate_to: localhost diff --git a/roles/hashistack/tasks/load_host_vars.yml b/roles/hashistack/tasks/load_host_vars.yml new file mode 100644 index 0000000..76ebf32 --- /dev/null +++ b/roles/hashistack/tasks/load_host_vars.yml @@ -0,0 +1,20 @@ +--- +# task/load_host_vars file for hashistack +- name: "Variables | Stat host specific config file" + ansible.builtin.stat: + path: "{{ hashistack_configuration_directory }}/{{ group_name }}/{{ inventory_hostname }}/{{ hashistack_configuration_global_vars_file }}" + register: _host_config_file + loop: "{{ group_names }}" + loop_control: + loop_var: group_name + delegate_to: localhost + +- name: "Variables | Load host specific variables" + ansible.builtin.include_vars: + dir: "{{ hashistack_configuration_directory }}/{{ item.group_name }}/{{ inventory_hostname }}" + files_matching: "{{ hashistack_configuration_global_vars_file }}" + loop: "{{ _host_config_file.results }}" + when: item.stat.exists + loop_control: + loop_var: item + delegate_to: localhost diff --git a/roles/hashistack/tasks/main.yml b/roles/hashistack/tasks/main.yml new file mode 100644 index 0000000..525e3b1 --- /dev/null +++ b/roles/hashistack/tasks/main.yml @@ -0,0 +1,32 @@ +--- +# task/main file for hashistack +- name: "Variables | Load global variables" + ansible.builtin.include_tasks: load_global_vars.yml + when: not hashistack_only_load_credentials + +- name: "Variables | Load credentials variables" + ansible.builtin.include_tasks: load_credentials_vars.yml + +- name: "Variables | Load group specific variables" + ansible.builtin.include_tasks: load_group_vars.yml + when: not hashistack_only_load_credentials + +- name: "Variables | Load host specific variables" + ansible.builtin.include_tasks: load_host_vars.yml + when: not hashistack_only_load_credentials + +- name: "Ensure remote directories exists" + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: 0755 + loop: + - "{{ hashistack_remote_config_dir }}" + - "{{ hashistack_remote_log_dir }}" + when: not hashistack_only_load_credentials + +- name: "Variables | Load custom CA certificates" + ansible.builtin.include_tasks: load_ca_certificates.yml + when: not hashistack_only_load_credentials diff --git a/roles/hashistack/vars/main.yml b/roles/hashistack/vars/main.yml new file mode 100644 index 0000000..622dbc0 --- /dev/null +++ b/roles/hashistack/vars/main.yml @@ -0,0 +1,2 @@ +--- +# vars file for hashistack diff --git a/roles/hashistack_ca/defaults/main.yml b/roles/hashistack_ca/defaults/main.yml new file mode 100644 index 0000000..9020363 --- /dev/null +++ b/roles/hashistack_ca/defaults/main.yml @@ -0,0 +1,109 @@ +--- +# defaults file for hashistack_ca +hashistack_ca_directory: "/etc/hashistack/certificates" +hashistack_ca_use_cryptography: false +hashistack_ca_action: "noop" +hashistack_ca_domain: example.com +hashistack_ca_directory_owner: root + +############################## +# Root Certificate Authority # +############################## +hashistack_ca_root_org_name: EDNZ Cloud +hashistack_ca_root_country: FR +hashistack_ca_root_locality: Paris +hashistack_ca_root_common_name: "{{ hashistack_ca_domain }} Root CA" +hashistack_ca_root_email: +hashistack_ca_root_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_root_key_usage_critical: true +hashistack_ca_root_basic_constraints: + - CA:TRUE +hashistack_ca_root_basic_constraints_critical: true + +# Optional fields +hashistack_ca_root_state_or_province_name: +hashistack_ca_root_email_address: + +# Validity +hashistack_ca_root_valid_for: 1825d +hashistack_ca_root_renew_threshold: 180d + +###################################### +# Intermediate Certificate Authority # +###################################### +hashistack_ca_intermediate_org_name: EDNZ Cloud Intermediate +hashistack_ca_intermediate_country: FR +hashistack_ca_intermediate_locality: Paris +hashistack_ca_intermediate_common_name: "{{ hashistack_ca_domain }} Intermediate CA" +hashistack_ca_intermediate_email: +hashistack_ca_intermediate_key_usage: + - keyCertSign + - cRLSign +hashistack_ca_intermediate_key_usage_critical: true +hashistack_ca_intermediate_basic_constraints: + - CA:TRUE + - pathlen:0 +hashistack_ca_intermediate_basic_constraints_critical: true + +# Optional fields +hashistack_ca_intermediate_state_or_province_name: +hashistack_ca_intermediate_email_address: + +# Validity +hashistack_ca_intermediate_valid_for: 365d +hashistack_ca_intermediate_renew_threshold: 90d + +# Name Constraints +hashistack_ca_intermediate_name_constraints_permitted: + - "DNS:.{{ hashistack_ca_domain }}" + - DNS:.nomad + - DNS:.consul + - DNS:localhost + - IP:192.168.0.0/16 + - IP:172.16.0.0/16 + - IP:10.0.0.0/8 + - IP:127.0.0.0/8 +hashistack_ca_intermediate_name_constraints_critical: "{{ (hashistack_ca_intermediate_name_constraints_permitted is defined and hashistack_ca_intermediate_name_constraints_permitted | length > 0) }}" + +##################### +# Leaf certificates # +##################### + +hashistack_ca_leaf_valid_for: 90d +hashistack_ca_leaf_renew_threshold: 30d + +############################ +# Consul Leaf Certificates # +############################ +hashistack_ca_consul_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_consul_common_name: "{{ inventory_hostname }}" +hashistack_ca_consul_csr_sans: + - "DNS:consul.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" + +########################### +# Nomad Leaf Certificates # +########################### +hashistack_ca_nomad_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_nomad_common_name: "{{ inventory_hostname }}" +hashistack_ca_nomad_csr_sans: + - DNS:server.global.nomad + - DNS:client.global.nomad + - "DNS:nomad.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" + +########################### +# Vault Leaf Certificates # +########################### +hashistack_ca_vault_org_name: "{{ hashistack_ca_root_org_name }}" +hashistack_ca_vault_common_name: "{{ inventory_hostname }}" +hashistack_ca_vault_csr_sans: + - "DNS:vault.service.consul" + - "DNS:active.vault.service.consul" + - "DNS:standby.vault.service.consul" + - "DNS:localhost" + - "IP:127.0.0.1" diff --git a/roles/hashistack_ca/handlers/main.yml b/roles/hashistack_ca/handlers/main.yml new file mode 100644 index 0000000..d422281 --- /dev/null +++ b/roles/hashistack_ca/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashistack_ca diff --git a/roles/hashistack_ca/meta/main.yml b/roles/hashistack_ca/meta/main.yml new file mode 100644 index 0000000..b1fb702 --- /dev/null +++ b/roles/hashistack_ca/meta/main.yml @@ -0,0 +1,2 @@ +--- +# meta file for hashistack_ca diff --git a/roles/hashistack_ca/tasks/cleanup_backups.yml b/roles/hashistack_ca/tasks/cleanup_backups.yml new file mode 100644 index 0000000..f4bac6b --- /dev/null +++ b/roles/hashistack_ca/tasks/cleanup_backups.yml @@ -0,0 +1,73 @@ +--- +# tasks/cleanup_backups file for hashistack_ca +- name: "Cleanup | Check if root CA backup directory exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_root_backup_dir }}" + register: _hashistack_ca_root_backup_dir_stat + delegate_to: localhost + +- name: "Cleanup | Check if intermediate CA backup directory exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_intermediate_backup_dir }}" + register: _hashistack_ca_intermediate_backup_dir_stat + delegate_to: localhost + +- name: "Cleanup | Root CA backups" + when: + - _hashistack_ca_root_backup_dir_stat.stat.exists + - _hashistack_ca_root_backup_dir_stat.stat.isdir + delegate_to: localhost + block: + - name: "Root CA | Find root CA backup certificates" + ansible.builtin.find: + paths: "{{ hashistack_ca_root_backup_dir }}" + patterns: "*.crt" + register: _root_backup_files + + - name: "Root CA | Check expiration for root CA backup certificates" + when: _root_backup_files.matched > 0 + community.crypto.x509_certificate_info: + path: "{{ item.path }}" + register: _root_cert_info + loop: "{{ _root_backup_files.files }}" + loop_control: + label: "{{ item.path }}" + failed_when: false + ignore_errors: true + + - name: "Root CA | Remove expired root CA backup certificates" + when: item.item.expired | default(false) + ansible.builtin.file: + path: "{{ item.item.path }}" + state: absent + loop: "{{ _root_cert_info.results }}" + +- name: "Cleanup | Intermediate CA backups" + when: + - _hashistack_ca_intermediate_backup_dir_stat.stat.exists + - _hashistack_ca_intermediate_backup_dir_stat.stat.isdir + delegate_to: localhost + block: + - name: "Intermediate CA | Find intermediate CA backup certificates" + ansible.builtin.find: + paths: "{{ hashistack_ca_intermediate_backup_dir }}" + patterns: "*.crt" + register: _intermediate_backup_files + + - name: "Intermediate CA | Check expiration for intermediate CA backup certificates" + when: _intermediate_backup_files.matched > 0 + community.crypto.x509_certificate_info: + path: "{{ item.path }}" + register: _intermediate_cert_info + loop: "{{ _intermediate_backup_files.files }}" + loop_control: + label: "{{ item.path }}" + failed_when: false + ignore_errors: true + + - name: "Intermediate CA | Remove expired intermediate CA backup certificates" + when: item.item.expired | default(false) + ansible.builtin.file: + path: "{{ item.item.path }}" + state: absent + loop: "{{ _intermediate_cert_info.results }}" diff --git a/roles/hashistack_ca/tasks/generate/generate_consul.yml b/roles/hashistack_ca/tasks/generate/generate_consul.yml new file mode 100644 index 0000000..a2600f2 --- /dev/null +++ b/roles/hashistack_ca/tasks/generate/generate_consul.yml @@ -0,0 +1,73 @@ +--- +# task/generate_consul file for hashistack_ca +- name: "Consul leaf certificates | Create certificate directory in for consul servers" + ansible.builtin.file: + path: "{{ hashistack_ca_consul_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + +- name: "Consul leaf certificates | Create Consul certificates" + block: + - name: "Consul leaf certificates | Create Consul certificate keys" + community.crypto.openssl_privatekey: + path: "{{ hashistack_ca_consul_key_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Consul leaf certificates | Create CSRs for Consul servers" + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ hashistack_ca_consul_key_path }}" + common_name: "{{ hashistack_ca_consul_common_name }}" + subject_alt_name: "{{ hashistack_ca_consul_csr_sans }}" + key_usage_critical: true + key_usage: + - Digital Signature + - Key Encipherment + - Key Agreement + extended_key_usage: + - TLS Web Server Authentication + - TLS Web Client Authentication + organization_name: "{{ hashistack_ca_consul_org_name }}" + use_common_name_for_san: false + register: _hashistack_ca_consul_csr + + - name: "Consul leaf certificates | Sign certificates with internal CA" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_consul_cert_path }}" + csr_content: "{{ _hashistack_ca_consul_csr.csr }}" + provider: ownca + ownca_path: "{{ hashistack_ca_intermediate_cert_path }}" + ownca_privatekey_path: "{{ hashistack_ca_intermediate_key_path }}" + ownca_not_after: "+{{ hashistack_ca_leaf_valid_for }}" + ownca_not_before: "-1d" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" + + - name: "Consul leaf certificates | Generate fullchain certificate" + block: + - name: "Consul leaf certificates | Read content of root ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_root_key_path }}" + register: _hashistack_ca_root_crt + + - name: "Consul leaf certificates | Read content of intermediate ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_crt + + - name: "Consul leaf certificates | Read content of leaf certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_consul_cert_path }}" + register: _hashistack_ca_consul_crt + + - name: "Consul leaf certificates | Concatenate certificates" + ansible.builtin.copy: + content: | + {{ _hashistack_ca_consul_crt['content'] | b64decode }}{{ _hashistack_ca_intermediate_crt['content'] | b64decode }}{{ _hashistack_ca_root_crt['content'] | b64decode }} + dest: "{{ hashistack_ca_consul_fullchain_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" diff --git a/roles/hashistack_ca/tasks/generate/generate_intermediate.yml b/roles/hashistack_ca/tasks/generate/generate_intermediate.yml new file mode 100644 index 0000000..da61e00 --- /dev/null +++ b/roles/hashistack_ca/tasks/generate/generate_intermediate.yml @@ -0,0 +1,47 @@ +--- +# task/generate_intermediate file for hashistack_ca +- name: "Intermediate CA | Create temporary cert directory in {{ hashistack_ca_directory }}/intermediate" + ansible.builtin.file: + path: "{{ hashistack_ca_intermediate_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + +- name: "Intermediate CA | Generate internal certificates" + block: + - name: "Intermediate CA | Create intermediate CA private key" + community.crypto.openssl_privatekey: + path: "{{ hashistack_ca_intermediate_key_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Intermediate CA | Create intermediate CA signing request" + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ hashistack_ca_intermediate_key_path }}" + common_name: "{{ omit if hashistack_ca_intermediate_common_name is not defined else hashistack_ca_intermediate_common_name }}" + organization_name: "{{ omit if hashistack_ca_intermediate_org_name is not defined else hashistack_ca_intermediate_org_name }}" + country_name: "{{ omit if hashistack_ca_intermediate_country is not defined else hashistack_ca_intermediate_country }}" + locality_name: "{{ omit if hashistack_ca_intermediate_locality is not defined else hashistack_ca_intermediate_locality }}" + state_or_province_name: "{{ omit if hashistack_ca_intermediate_state_or_province_name is not defined else hashistack_ca_intermediate_state_or_province_name }}" + email_address: "{{ omit if hashistack_ca_intermediate_email is not defined else hashistack_ca_intermediate_email }}" + basic_constraints: "{{ hashistack_ca_intermediate_basic_constraints }}" + basic_constraints_critical: true + name_constraints_permitted: "{{ hashistack_ca_intermediate_name_constraints_permitted if hashistack_ca_intermediate_name_constraints_permitted | length > 0 else omit }}" + name_constraints_critical: "{{ hashistack_ca_intermediate_name_constraints_critical }}" + key_usage: "{{ hashistack_ca_intermediate_key_usage }}" + key_usage_critical: true + use_common_name_for_san: false + select_crypto_backend: "{{ 'cryptography' if hashistack_ca_use_cryptography else 'auto' }}" + register: _hashistack_intermediate_ca_csr + + - name: "Intermediate CA | Create signed intermediate CA certificate from CSR" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_intermediate_cert_path }}" + csr_content: "{{ _hashistack_intermediate_ca_csr.csr }}" + ownca_path: "{{ hashistack_ca_root_cert_path }}" + ownca_privatekey_path: "{{ hashistack_ca_root_key_path }}" + provider: ownca + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + ownca_not_after: "+{{ hashistack_ca_intermediate_valid_for }}" diff --git a/roles/hashistack_ca/tasks/generate/generate_nomad.yml b/roles/hashistack_ca/tasks/generate/generate_nomad.yml new file mode 100644 index 0000000..20e9dc8 --- /dev/null +++ b/roles/hashistack_ca/tasks/generate/generate_nomad.yml @@ -0,0 +1,73 @@ +--- +# task/generate_nomad file for hashistack_ca +- name: "Nomad leaf certificates | Create certificate directory in for nomad servers" + ansible.builtin.file: + path: "{{ hashistack_ca_nomad_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + +- name: "Nomad leaf certificates | Create Nomad certificates" + block: + - name: "Nomad leaf certificates | Create Nomad certificate keys" + community.crypto.openssl_privatekey: + path: "{{ hashistack_ca_nomad_key_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Nomad leaf certificates | Create CSRs for Nomad servers" + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ hashistack_ca_nomad_key_path }}" + common_name: "{{ hashistack_ca_nomad_common_name }}" + subject_alt_name: "{{ hashistack_ca_nomad_csr_sans }}" + key_usage_critical: true + key_usage: + - Digital Signature + - Key Encipherment + - Key Agreement + extended_key_usage: + - TLS Web Server Authentication + - TLS Web Client Authentication + organization_name: "{{ hashistack_ca_nomad_org_name }}" + use_common_name_for_san: false + register: _hashistack_ca_nomad_csr + + - name: "Nomad leaf certificates | Sign certificates with internal CA" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_nomad_cert_path }}" + csr_content: "{{ _hashistack_ca_nomad_csr.csr }}" + provider: ownca + ownca_path: "{{ hashistack_ca_intermediate_cert_path }}" + ownca_privatekey_path: "{{ hashistack_ca_intermediate_key_path }}" + ownca_not_after: "+{{ hashistack_ca_leaf_valid_for }}" + ownca_not_before: "-1d" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" + + - name: "Nomad leaf certificates | Generate fullchain certificate" + block: + - name: "Nomad leaf certificates | Read content of root ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_root_key_path }}" + register: _hashistack_ca_root_crt + + - name: "Nomad leaf certificates | Read content of intermediate ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_crt + + - name: "Nomad leaf certificates | Read content of leaf certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_nomad_cert_path }}" + register: _hashistack_ca_nomad_crt + + - name: "Nomad leaf certificates | Concatenate certificates" + ansible.builtin.copy: + content: | + {{ _hashistack_ca_nomad_crt['content'] | b64decode }}{{ _hashistack_ca_intermediate_crt['content'] | b64decode }}{{ _hashistack_ca_root_crt['content'] | b64decode }} + dest: "{{ hashistack_ca_nomad_fullchain_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" diff --git a/roles/hashistack_ca/tasks/generate/generate_root.yml b/roles/hashistack_ca/tasks/generate/generate_root.yml new file mode 100644 index 0000000..f634316 --- /dev/null +++ b/roles/hashistack_ca/tasks/generate/generate_root.yml @@ -0,0 +1,54 @@ +--- +# task/generate_root file for hashistack_ca +- name: "Root CA | Create temporary cert directory in {{ hashistack_ca_directory }}" # noqa: run-once[task] + ansible.builtin.file: + path: "{{ hashistack_ca_root_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + +- name: "Root CA | Generate root Authority" + run_once: true + block: + - name: "Root CA | Create CA private key" + community.crypto.openssl_privatekey: + path: "{{ hashistack_ca_root_key_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Root CA | Create CA signing request" + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ hashistack_ca_root_key_path }}" + common_name: "{{ omit if hashistack_ca_root_common_name is not defined else hashistack_ca_root_common_name }}" + organization_name: "{{ omit if hashistack_ca_root_org_name is not defined else hashistack_ca_root_org_name }}" + country_name: "{{ omit if hashistack_ca_root_country is not defined else hashistack_ca_root_country }}" + locality_name: "{{ omit if hashistack_ca_root_locality is not defined else hashistack_ca_root_locality }}" + state_or_province_name: "{{ omit if hashistack_ca_root_state is not defined else hashistack_ca_root_state }}" + email_address: "{{ omit if hashistack_ca_root_email is not defined else hashistack_ca_root_email }}" + basic_constraints: "{{ hashistack_ca_root_basic_constraints }}" + basic_constraints_critical: true + key_usage: "{{ hashistack_ca_root_key_usage }}" + key_usage_critical: true + use_common_name_for_san: false + select_crypto_backend: "{{ 'cryptography' if hashistack_ca_use_cryptography else 'auto' }}" + register: _hashistack_root_ca_csr + + - name: "Root CA | Create self-signed CA certificate from CSR" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_root_cert_path }}" + csr_content: "{{ _hashistack_root_ca_csr.csr }}" + privatekey_path: "{{ hashistack_ca_root_key_path }}" + provider: selfsigned + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Root CA | Create self-signed CA certificate from CSR" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_root_cert_path }}" + csr_content: "{{ _hashistack_root_ca_csr.csr }}" + privatekey_path: "{{ hashistack_ca_root_key_path }}" + selfsigned_not_after: "+{{ hashistack_ca_root_valid_for }}" + provider: selfsigned + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" diff --git a/roles/hashistack_ca/tasks/generate/generate_vault.yml b/roles/hashistack_ca/tasks/generate/generate_vault.yml new file mode 100644 index 0000000..5bfd708 --- /dev/null +++ b/roles/hashistack_ca/tasks/generate/generate_vault.yml @@ -0,0 +1,73 @@ +--- +# task/generate_vault file for hashistack_ca +- name: "Vault leaf certificates | Create certificate directory in for vault servers" + ansible.builtin.file: + path: "{{ hashistack_ca_vault_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + +- name: "Vault leaf certificates | Create Vault certificates" + block: + - name: "Vault leaf certificates | Create Vault certificate keys" + community.crypto.openssl_privatekey: + path: "{{ hashistack_ca_vault_key_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + + - name: "Vault leaf certificates | Create CSRs for Vault servers" + community.crypto.openssl_csr_pipe: + privatekey_path: "{{ hashistack_ca_vault_key_path }}" + common_name: "{{ hashistack_ca_vault_common_name }}" + subject_alt_name: "{{ hashistack_ca_vault_csr_sans }}" + key_usage_critical: true + key_usage: + - Digital Signature + - Key Encipherment + - Key Agreement + extended_key_usage: + - TLS Web Server Authentication + - TLS Web Client Authentication + organization_name: "{{ hashistack_ca_vault_org_name }}" + use_common_name_for_san: false + register: _hashistack_ca_vault_csr + + - name: "Vault leaf certificates | Sign certificates with internal CA" + community.crypto.x509_certificate: + path: "{{ hashistack_ca_vault_cert_path }}" + csr_content: "{{ _hashistack_ca_vault_csr.csr }}" + provider: ownca + ownca_path: "{{ hashistack_ca_intermediate_cert_path }}" + ownca_privatekey_path: "{{ hashistack_ca_intermediate_key_path }}" + ownca_not_after: "+{{ hashistack_ca_leaf_valid_for }}" + ownca_not_before: "-1d" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" + + - name: "Vault leaf certificates | Generate fullchain certificate" + block: + - name: "Vault leaf certificates | Read content of root ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_root_key_path }}" + register: _hashistack_ca_root_crt + + - name: "Vault leaf certificates | Read content of intermediate ca certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_crt + + - name: "Vault leaf certificates | Read content of leaf certificate" + ansible.builtin.slurp: + src: "{{ hashistack_ca_vault_cert_path }}" + register: _hashistack_ca_vault_crt + + - name: "Vault leaf certificates | Concatenate certificates" + ansible.builtin.copy: + content: | + {{ _hashistack_ca_vault_crt['content'] | b64decode }}{{ _hashistack_ca_intermediate_crt['content'] | b64decode }}{{ _hashistack_ca_root_crt['content'] | b64decode }} + dest: "{{ hashistack_ca_vault_fullchain_path }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0644" diff --git a/roles/hashistack_ca/tasks/main.yml b/roles/hashistack_ca/tasks/main.yml new file mode 100644 index 0000000..00a8b62 --- /dev/null +++ b/roles/hashistack_ca/tasks/main.yml @@ -0,0 +1,47 @@ +--- +# task/main file for hashistack_ca +- name: "CA | Import generate_root.yml" + ansible.builtin.include_tasks: generate/generate_root.yml + when: hashistack_ca_generate_root + +- name: "CA | Import generate_intermediate.yml" + ansible.builtin.include_tasks: generate/generate_intermediate.yml + when: hashistack_ca_generate_intermediate + +- name: "CA | Import renew_root.yml" + ansible.builtin.include_tasks: renew/renew_root.yml + when: hashistack_ca_renew_root + +- name: "CA | Import renew_intermediate.yml" + ansible.builtin.include_tasks: renew/renew_intermediate.yml + when: hashistack_ca_renew_intermediate + +- name: "CA | Import prepare_ca_to_copy.yml" + ansible.builtin.include_tasks: prepare_ca_to_copy.yml + +- name: "CA | Import cleanup_backups.yml" + ansible.builtin.include_tasks: cleanup_backups.yml + +- name: "Consul leaf certificates | Import generate/generate_consul.yml" + ansible.builtin.include_tasks: generate/generate_consul.yml + when: + - hashistack_ca_generate_leaf + - "('consul_servers' in group_names) or ('consul_agents' in group_names)" + +- name: "Nomad leaf certificates | Import generate/generate_nomad.yml" + ansible.builtin.include_tasks: generate/generate_nomad.yml + when: + - hashistack_ca_generate_leaf + - "('nomad_servers' in group_names) or ('nomad_clients' in group_names)" + +- name: "Vault leaf certificates | Import generate/generate_vault.yml" + ansible.builtin.include_tasks: generate/generate_vault.yml + when: + - hashistack_ca_generate_leaf + - "'vault_servers' in group_names" + +- name: "Consul leaf certificates | Import renew_consul.yml" + ansible.builtin.include_tasks: renew/renew_consul.yml + when: + - hashistack_ca_renew_leaf + - "('consul_servers' in group_names) or ('consul_agents' in group_names)" diff --git a/roles/hashistack_ca/tasks/prepare_ca_to_copy.yml b/roles/hashistack_ca/tasks/prepare_ca_to_copy.yml new file mode 100644 index 0000000..70dd013 --- /dev/null +++ b/roles/hashistack_ca/tasks/prepare_ca_to_copy.yml @@ -0,0 +1,35 @@ +--- +# task/prepare_ca_to_copy file for hashistack_ca +- name: "CA | Check if CA directory exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_root_dir }}" + register: _hashistack_ca_root_dir + delegate_to: localhost + +- name: "CA | Find custom CA certificates to copy" + ansible.builtin.find: + paths: "{{ hashistack_ca_root_dir }}" + patterns: "*.crt" + register: hashistack_ca_root_dir_files + delegate_to: localhost + when: _hashistack_ca_root_dir.stat.exists and _hashistack_ca_root_dir.stat.isdir + +- name: "CA | Ensure public CA directory exists" + ansible.builtin.file: + path: "{{ hashistack_ca_public_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: 0755 + delegate_to: localhost + +- name: "CA | Copy root CA certificates" + ansible.builtin.copy: + src: "{{ item.path }}" + dest: "{{ hashistack_ca_public_dir }}/{{ item.path | basename }}" + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: 0644 + loop: "{{ hashistack_ca_root_dir_files.files }}" + delegate_to: localhost + when: not hashistack_ca_root_dir_files.skipped | default(False) diff --git a/roles/hashistack_ca/tasks/renew/renew_consul.yml b/roles/hashistack_ca/tasks/renew/renew_consul.yml new file mode 100644 index 0000000..b57188f --- /dev/null +++ b/roles/hashistack_ca/tasks/renew/renew_consul.yml @@ -0,0 +1,55 @@ +--- +# tasks/renew/renew_consul file for hashistack_ca +- name: "Consul leaf certificates | Check if certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_consul_cert_path }}" + register: _hashistack_ca_consul_cert_stat + +- name: "Consul leaf certificates | Check if intermediate CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_stat + +- name: "Consul leaf certificates | Check certificate for renewal" + when: + - _hashistack_ca_consul_cert_stat.stat.exists + - _hashistack_ca_consul_cert_stat.stat.isreg + - _hashistack_ca_intermediate_cert_stat.stat.exists + - _hashistack_ca_intermediate_cert_stat.stat.isreg + block: + - name: "Consul leaf certificates | Get certificate expiration date" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_consul_cert_path }}" + valid_at: + renew_threshold: "+{{ hashistack_ca_leaf_renew_threshold }}" + register: _hashistack_ca_consul_cert_info + + - name: "Intermediate CA | Get intermediate CA certificate info" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_info + + - name: "Consul leaf certificates | Check if certificate is expiring within the threshold" + ansible.builtin.set_fact: + _hashistack_cert_is_expiring_soon: "{{ not _hashistack_ca_consul_cert_info.valid_at.renew_threshold }}" + + - name: "Consul leaf certificates | Check if intermediate CA has been renewed" + ansible.builtin.set_fact: + _hashistack_ca_intermediate_renewed: "{{ _hashistack_ca_intermediate_cert_info.not_before > _hashistack_ca_consul_cert_info.not_before }}" + +- name: "Consul leaf certificates | Renew certificate if expiring soon or intermediate CA has been renewed" + when: + - _hashistack_cert_is_expiring_soon or _hashistack_ca_intermediate_renewed + block: + - name: "Consul leaf certificates | Remove old certificate before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_consul_cert_path }}" + state: absent + + - name: "Consul leaf certificates | Remove old certificate key before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_consul_key_path }}" + state: absent + + - name: "Consul leaf certificates | Generate new consul leaf certificate" + ansible.builtin.include_tasks: ../generate/generate_consul.yml diff --git a/roles/hashistack_ca/tasks/renew/renew_intermediate.yml b/roles/hashistack_ca/tasks/renew/renew_intermediate.yml new file mode 100644 index 0000000..fadb858 --- /dev/null +++ b/roles/hashistack_ca/tasks/renew/renew_intermediate.yml @@ -0,0 +1,80 @@ +--- +# tasks/renew/renew_intermediate file for hashistack_ca +- name: "Intermediate CA | Check if intermediate CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_stat + delegate_to: localhost + +- name: "Intermediate CA | Check if root CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_root_cert_path }}" + register: _hashistack_ca_root_cert_stat + delegate_to: localhost + +- name: "Intermediate CA | Check CA for renewal" + when: + - _hashistack_ca_intermediate_cert_stat.stat.exists + - _hashistack_ca_intermediate_cert_stat.stat.isreg + - _hashistack_ca_root_cert_stat.stat.exists + - _hashistack_ca_root_cert_stat.stat.isreg + delegate_to: localhost + block: + - name: "Intermediate CA | Get intermediate CA certificate expiration date" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_intermediate_cert_path }}" + valid_at: + renew_threshold: "+{{ hashistack_ca_intermediate_renew_threshold }}" + register: _hashistack_ca_intermediate_cert_info + + - name: "Root CA | Get root CA certificate info" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_root_cert_path }}" + register: _hashistack_ca_root_cert_info + + - name: "Intermediate CA | Check if intermediate CA certificate is expiring within the threshold" + ansible.builtin.set_fact: + _hashistack_ca_is_expiring_soon: "{{ not _hashistack_ca_intermediate_cert_info.valid_at.renew_threshold }}" + + - name: "Intermediate CA | Check if root CA has been renewed" + ansible.builtin.set_fact: + _hashistack_ca_root_renewed: "{{ _hashistack_ca_root_cert_info.not_before > _hashistack_ca_intermediate_cert_info.not_before }}" + +- name: "Intermediate CA | Renew CA if expiring soon or root CA has been renewed" + when: + - _hashistack_ca_is_expiring_soon or _hashistack_ca_root_renewed + delegate_to: localhost + block: + - name: "Intermediate CA | Create backup directory for intermediate CA" + ansible.builtin.file: + path: "{{ hashistack_ca_intermediate_backup_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + + - name: "Intermediate CA | Format expiration date for backup" + ansible.builtin.set_fact: + _hashistack_ca_intermediate_expiration_date: "{{ _hashistack_ca_intermediate_cert_info.not_after[:8] | regex_replace('^([0-9]{4})([0-9]{2})([0-9]{2})$', '\\1_\\2_\\3') }}" + + - name: "Intermediate CA | Backup existing intermediate CA certificate" + ansible.builtin.command: + cmd: mv {{ hashistack_ca_intermediate_cert_path }} {{ hashistack_ca_intermediate_backup_dir }}/intermediate_ca_expire_{{ _hashistack_ca_intermediate_expiration_date }}.crt + changed_when: false + + - name: "Intermediate CA | Backup existing intermediate CA key" + ansible.builtin.command: + cmd: mv {{ hashistack_ca_intermediate_key_path }} {{ hashistack_ca_intermediate_backup_dir }}/intermediate_ca_expire_{{ _hashistack_ca_intermediate_expiration_date }}.key + changed_when: false + + - name: "Intermediate CA | Generate new intermediate CA if backups were successful" + ansible.builtin.include_tasks: ../generate/generate_intermediate.yml + + - name: "Intermediate CA | Generate new consul leaf certificates" + ansible.builtin.include_tasks: ../renew/renew_consul.yml + + - name: "Intermediate CA | Generate new nomad leaf certificates" + ansible.builtin.include_tasks: ../renew/renew_nomad.yml + + - name: "Intermediate CA | Generate new vault leaf certificates" + ansible.builtin.include_tasks: ../renew/renew_vault.yml diff --git a/roles/hashistack_ca/tasks/renew/renew_nomad.yml b/roles/hashistack_ca/tasks/renew/renew_nomad.yml new file mode 100644 index 0000000..eb08c51 --- /dev/null +++ b/roles/hashistack_ca/tasks/renew/renew_nomad.yml @@ -0,0 +1,55 @@ +--- +# tasks/renew/renew_nomad file for hashistack_ca +- name: "Nomad leaf certificates | Check if certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_nomad_cert_path }}" + register: _hashistack_ca_nomad_cert_stat + +- name: "Nomad leaf certificates | Check if intermediate CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_stat + +- name: "Nomad leaf certificates | Check certificate for renewal" + when: + - _hashistack_ca_nomad_cert_stat.stat.exists + - _hashistack_ca_nomad_cert_stat.stat.isreg + - _hashistack_ca_intermediate_cert_stat.stat.exists + - _hashistack_ca_intermediate_cert_stat.stat.isreg + block: + - name: "Nomad leaf certificates | Get certificate expiration date" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_nomad_cert_path }}" + valid_at: + renew_threshold: "+{{ hashistack_ca_leaf_renew_threshold }}" + register: _hashistack_ca_nomad_cert_info + + - name: "Intermediate CA | Get intermediate CA certificate info" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_info + + - name: "Nomad leaf certificates | Check if certificate is expiring within the threshold" + ansible.builtin.set_fact: + _hashistack_cert_is_expiring_soon: "{{ not _hashistack_ca_nomad_cert_info.valid_at.renew_threshold }}" + + - name: "Nomad leaf certificates | Check if intermediate CA has been renewed" + ansible.builtin.set_fact: + _hashistack_ca_intermediate_renewed: "{{ _hashistack_ca_intermediate_cert_info.not_before > _hashistack_ca_nomad_cert_info.not_before }}" + +- name: "Nomad leaf certificates | Renew certificate if expiring soon or intermediate CA has been renewed" + when: + - _hashistack_cert_is_expiring_soon or _hashistack_ca_intermediate_renewed + block: + - name: "Nomad leaf certificates | Remove old certificate before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_nomad_cert_path }}" + state: absent + + - name: "Nomad leaf certificates | Remove old certificate key before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_nomad_key_path }}" + state: absent + + - name: "Nomad leaf certificates | Generate new nomad leaf certificate" + ansible.builtin.include_tasks: ../generate/generate_nomad.yml diff --git a/roles/hashistack_ca/tasks/renew/renew_root.yml b/roles/hashistack_ca/tasks/renew/renew_root.yml new file mode 100644 index 0000000..127046a --- /dev/null +++ b/roles/hashistack_ca/tasks/renew/renew_root.yml @@ -0,0 +1,58 @@ +--- +# tasks/renew/renew_root file for hashistack_ca +- name: "Root CA | Check if root CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_root_cert_path }}" + register: _hashistack_ca_root_cert_stat + delegate_to: localhost + +- name: "Root CA | Check CA for renewal" + when: + - _hashistack_ca_root_cert_stat.stat.exists + - _hashistack_ca_root_cert_stat.stat.isreg + delegate_to: localhost + block: + - name: "Root CA | Get root CA certificate expiration date" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_root_cert_path }}" + valid_at: + renew_threshold: "+{{ hashistack_ca_root_renew_threshold }}" + register: _hashistack_ca_root_cert_info + + - name: "Root CA | Check if root CA certificate is expiring within the threshold" + ansible.builtin.set_fact: + _hashistack_ca_is_expiring_soon: "{{ not _hashistack_ca_root_cert_info.valid_at.renew_threshold }}" + +- name: "Root CA | Renew CA if expiring soon" + when: + - _hashistack_ca_is_expiring_soon + delegate_to: localhost + block: + - name: "Root CA | Create backup directory for root CA" + ansible.builtin.file: + path: "{{ hashistack_ca_root_backup_dir }}" + state: directory + owner: "{{ hashistack_ca_directory_owner }}" + group: "{{ hashistack_ca_directory_owner }}" + mode: "0755" + + - name: "Root CA | Format expiration date for backup" + ansible.builtin.set_fact: + _hashistack_ca_root_expiration_date: "{{ _hashistack_ca_root_cert_info.not_after[:8] | regex_replace('^([0-9]{4})([0-9]{2})([0-9]{2})$', '\\1_\\2_\\3') }}" + + - name: "Root CA | Rename existing root CA certificate" + ansible.builtin.command: + cmd: mv {{ hashistack_ca_root_cert_path }} {{ hashistack_ca_root_backup_dir }}/ca_expire_{{ _hashistack_ca_root_expiration_date }}.crt + changed_when: false + + - name: "Root CA | Remove existing root CA key" + ansible.builtin.file: + path: "{{ hashistack_ca_root_key_path }}" + state: absent + changed_when: false + + - name: "Root CA | Generate new root CA if renaming was successful" + ansible.builtin.include_tasks: ../generate/generate_root.yml + + - name: "Root CA | Generate new intermediate CA" + ansible.builtin.include_tasks: ../renew/renew_intermediate.yml diff --git a/roles/hashistack_ca/tasks/renew/renew_vault.yml b/roles/hashistack_ca/tasks/renew/renew_vault.yml new file mode 100644 index 0000000..cd9596a --- /dev/null +++ b/roles/hashistack_ca/tasks/renew/renew_vault.yml @@ -0,0 +1,55 @@ +--- +# tasks/renew/renew_vault file for hashistack_ca +- name: "Vault leaf certificates | Check if certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_vault_cert_path }}" + register: _hashistack_ca_vault_cert_stat + +- name: "Vault leaf certificates | Check if intermediate CA certificate exists" + ansible.builtin.stat: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_stat + +- name: "Vault leaf certificates | Check certificate for renewal" + when: + - _hashistack_ca_vault_cert_stat.stat.exists + - _hashistack_ca_vault_cert_stat.stat.isreg + - _hashistack_ca_intermediate_cert_stat.stat.exists + - _hashistack_ca_intermediate_cert_stat.stat.isreg + block: + - name: "Vault leaf certificates | Get certificate expiration date" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_vault_cert_path }}" + valid_at: + renew_threshold: "+{{ hashistack_ca_leaf_renew_threshold }}" + register: _hashistack_ca_vault_cert_info + + - name: "Intermediate CA | Get intermediate CA certificate info" + community.crypto.x509_certificate_info: + path: "{{ hashistack_ca_intermediate_cert_path }}" + register: _hashistack_ca_intermediate_cert_info + + - name: "Vault leaf certificates | Check if certificate is expiring within the threshold" + ansible.builtin.set_fact: + _hashistack_cert_is_expiring_soon: "{{ not _hashistack_ca_vault_cert_info.valid_at.renew_threshold }}" + + - name: "Vault leaf certificates | Check if intermediate CA has been renewed" + ansible.builtin.set_fact: + _hashistack_ca_intermediate_renewed: "{{ _hashistack_ca_intermediate_cert_info.not_before > _hashistack_ca_vault_cert_info.not_before }}" + +- name: "Vault leaf certificates | Renew certificate if expiring soon or intermediate CA has been renewed" + when: + - _hashistack_cert_is_expiring_soon or _hashistack_ca_intermediate_renewed + block: + - name: "Vault leaf certificates | Remove old certificate before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_vault_cert_path }}" + state: absent + + - name: "Vault leaf certificates | Remove old certificate key before renewal" + ansible.builtin.file: + path: "{{ hashistack_ca_vault_key_path }}" + state: absent + + - name: "Vault leaf certificates | Generate new vault leaf certificate" + ansible.builtin.include_tasks: ../generate/generate_vault.yml diff --git a/roles/hashistack_ca/vars/main.yml b/roles/hashistack_ca/vars/main.yml new file mode 100644 index 0000000..290cdc9 --- /dev/null +++ b/roles/hashistack_ca/vars/main.yml @@ -0,0 +1,39 @@ +--- +# vars file for hashistack_ca +hashistack_ca_action_list: "{{ hashistack_ca_action.split(',') }}" + +# possible actions +hashistack_ca_generate_root: "{{ 'root_ca' in hashistack_ca_action_list }}" +hashistack_ca_generate_intermediate: "{{ 'int_ca' in hashistack_ca_action_list }}" +hashistack_ca_generate_leaf: "{{ 'leaf_cert' in hashistack_ca_action_list }}" +hashistack_ca_renew_root: "{{ 'renew_root' in hashistack_ca_action_list }}" +hashistack_ca_renew_intermediate: "{{ 'renew_int' in hashistack_ca_action_list }}" +hashistack_ca_renew_leaf: "{{ 'renew_leaf' in hashistack_ca_action_list }}" + +hashistack_ca_public_dir: "{{ hashistack_ca_directory }}/ca" + +hashistack_ca_root_dir: "{{ hashistack_ca_directory }}/root" +hashistack_ca_root_backup_dir: "{{ hashistack_ca_root_dir }}/backup" +hashistack_ca_root_key_path: "{{ hashistack_ca_root_dir }}/ca.key" +hashistack_ca_root_cert_path: "{{ hashistack_ca_root_dir }}/ca.crt" + +hashistack_ca_intermediate_dir: "{{ hashistack_ca_directory }}/intermediate" +hashistack_ca_intermediate_backup_dir: "{{ hashistack_ca_intermediate_dir }}/backup" +hashistack_ca_intermediate_key_path: "{{ hashistack_ca_intermediate_dir }}/ca.key" +hashistack_ca_intermediate_csr_path: "{{ hashistack_ca_intermediate_dir }}/ca.csr" +hashistack_ca_intermediate_cert_path: "{{ hashistack_ca_intermediate_dir }}/ca.crt" + +hashistack_ca_consul_dir: "{{ hashistack_ca_directory }}/consul/{{ inventory_hostname }}" +hashistack_ca_consul_key_path: "{{ hashistack_ca_consul_dir }}/cert.key" +hashistack_ca_consul_cert_path: "{{ hashistack_ca_consul_dir }}/cert.crt" +hashistack_ca_consul_fullchain_path: "{{ hashistack_ca_consul_dir }}/fullchain.crt" + +hashistack_ca_nomad_dir: "{{ hashistack_ca_directory }}/nomad/{{ inventory_hostname }}" +hashistack_ca_nomad_key_path: "{{ hashistack_ca_nomad_dir }}/cert.key" +hashistack_ca_nomad_cert_path: "{{ hashistack_ca_nomad_dir }}/cert.crt" +hashistack_ca_nomad_fullchain_path: "{{ hashistack_ca_nomad_dir }}/fullchain.crt" + +hashistack_ca_vault_dir: "{{ hashistack_ca_directory }}/vault/{{ inventory_hostname }}" +hashistack_ca_vault_key_path: "{{ hashistack_ca_vault_dir }}/cert.key" +hashistack_ca_vault_cert_path: "{{ hashistack_ca_vault_dir }}/cert.crt" +hashistack_ca_vault_fullchain_path: "{{ hashistack_ca_vault_dir }}/fullchain.crt" diff --git a/roles/nomad/defaults/main.yml b/roles/nomad/defaults/main.yml new file mode 100644 index 0000000..fd66ceb --- /dev/null +++ b/roles/nomad/defaults/main.yml @@ -0,0 +1,200 @@ +--- +# defaults file for hashicorp_nomad + +nomad_version: "latest" +nomad_start_service: true +nomad_config_dir: "/etc/nomad.d" +nomad_data_dir: "/opt/nomad" +nomad_certs_dir: "{{ nomad_config_dir }}/tls" +nomad_logs_dir: "/var/log/nomad" + +nomad_extra_files: false +nomad_extra_files_list: [] + +nomad_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. + +nomad_extra_configuration: {} + +########### +# general # +########### + +nomad_region: global +nomad_datacenter: dc1 + +######################### +# address configuration # +######################### + +nomad_bind_addr: "0.0.0.0" +nomad_advertise_addr: "{{ ansible_default_ipv4.address }}" +nomad_address_configuration: + bind_addr: "{{ nomad_bind_addr }}" + addresses: + http: "{{ nomad_advertise_addr }}" + rpc: "{{ nomad_advertise_addr }}" + serf: "{{ nomad_advertise_addr }}" + advertise: + http: "{{ nomad_advertise_addr }}" + rpc: "{{ nomad_advertise_addr }}" + serf: "{{ nomad_advertise_addr }}" + ports: + http: 4646 + rpc: 4647 + serf: 4648 + +########################### +# autopilot configuration # +########################### + +nomad_autopilot_configuration: {} + +####################### +# leave configuration # +####################### + +nomad_leave_on_interrupt: false +nomad_leave_on_terminate: false + +######################## +# server configuration # +######################## + +nomad_enable_server: true +nomad_server_bootstrap_expect: 1 +nomad_server_configuration: + enabled: "{{ nomad_enable_server }}" + data_dir: "{{ nomad_data_dir }}/server" + encrypt: "{{ 'mysupersecretgossipencryptionkey'|b64encode }}" + server_join: + retry_join: + - "{{ ansible_default_ipv4.address }}" + +############################## +# client configuration # +############################## + +nomad_enable_client: false +nomad_client_configuration: + enabled: "{{ nomad_enable_client }}" + state_dir: "{{ nomad_data_dir }}/client" + cni_path: "/opt/cni/bin" + bridge_network_name: nomad + bridge_network_subnet: "172.26.64.0/20" + +#################### +# ui configuration # +#################### + +nomad_ui_configuration: + enabled: "{{ nomad_enable_server }}" + +######################### +# drivers configuration # +######################### + +nomad_driver_enable_docker: true +nomad_driver_enable_podman: false +nomad_driver_enable_raw_exec: false +nomad_driver_enable_java: false +nomad_driver_enable_qemu: false + +nomad_driver_configuration: + raw_exec: + enabled: false + +nomad_driver_extra_configuration: {} + +########### +# logging # +########### + +nomad_log_level: info +nomad_enable_log_to_file: false +nomad_log_to_file_configuration: + log_file: "{{ nomad_logs_dir }}/nomad.log" + log_rotate_duration: 24h + log_rotate_max_files: 30 + +##################### +# ACL configuration # +##################### + +nomad_acl_configuration: + enabled: false + token_ttl: 30s + policy_ttl: 60s + role_ttl: 60s + +################ +# internal tls # +################ + +nomad_enable_tls: false +nomad_tls_configuration: + http: true + rpc: true + ca_file: "/etc/ssl/certs/ca-certificates.crt" + cert_file: "{{ nomad_certs_dir }}/cert.pem" + key_file: "{{ nomad_certs_dir }}/key.pem" + verify_server_hostname: true + +nomad_certificates_extra_files_dir: + [] + # - src: "" + # dest: "{{ nomad_certs_dir }}" + +########################### +# telemetry configuration # +########################### + +nomad_telemetry_configuration: + collection_interval: 10s + disable_hostname: false + use_node_name: false + publish_allocation_metrics: false + publish_node_metrics: false + prefix_filter: [] + disable_dispatched_job_summary_metrics: false + prometheus_metrics: false + +###################### +# consul integration # +###################### + +nomad_enable_consul_integration: false +nomad_consul_integration_configuration: + address: "127.0.0.1:8500" + auto_advertise: true + ssl: false + token: "" + tags: [] + +nomad_consul_integration_tls_configuration: + ca_file: "/etc/ssl/certs/ca-certificates.crt" + +nomad_consul_integration_server_configuration: + server_auto_join: true + +nomad_consul_integration_client_configuration: + client_auto_join: true + grpc_address: "127.0.0.1:8502" + +nomad_consul_integration_client_tls_configuration: + grpc_ca_file: "/etc/ssl/certs/ca-certificates.crt" + +############################ +# nomad vault integration # +############################ + +nomad_enable_vault_integration: false +nomad_vault_integration_configuration: {} diff --git a/roles/nomad/handlers/main.yml b/roles/nomad/handlers/main.yml new file mode 100644 index 0000000..a4216c6 --- /dev/null +++ b/roles/nomad/handlers/main.yml @@ -0,0 +1,2 @@ +--- +# handlers file for hashicorp_nomad diff --git a/roles/nomad/meta/main.yml b/roles/nomad/meta/main.yml new file mode 100644 index 0000000..cc6aa7f --- /dev/null +++ b/roles/nomad/meta/main.yml @@ -0,0 +1,26 @@ +--- +# meta file for hashicorp_nomad +galaxy_info: + namespace: "ednz_cloud" + role_name: "hashicorp_nomad" + author: "Bertrand Lanson" + description: "Install and configure hashicorp nomad 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" + - "nomad" + +dependencies: [] diff --git a/roles/nomad/tasks/configure.yml b/roles/nomad/tasks/configure.yml new file mode 100644 index 0000000..0de7a17 --- /dev/null +++ b/roles/nomad/tasks/configure.yml @@ -0,0 +1,74 @@ +--- +# task/configure file for hashicorp_nomad +- name: "Nomad | Create nomad.env" + ansible.builtin.template: + src: nomad.env.j2 + dest: "{{ nomad_config_dir }}/nomad.env" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0600" + register: _nomad_env_file + +- name: "Nomad | Copy nomad.json template" + ansible.builtin.template: + src: nomad.json.j2 + dest: "{{ nomad_config_dir }}/nomad.json" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0600" + register: _nomad_config_file + +- name: "Nomad | Set restart-check variable" + ansible.builtin.set_fact: + _nomad_service_need_restart: true + when: _nomad_env_file.changed or + _nomad_config_file.changed + +- name: "Nomad | Copy extra configuration files" + when: nomad_extra_files + block: + - name: "Nomad | Get extra file types" + ansible.builtin.stat: + path: "{{ item.src }}" + loop: "{{ nomad_extra_files_list }}" + register: nomad_extra_file_stat + delegate_to: localhost + + - name: "Nomad | Set list for file sources" + vars: + _nomad_file_sources: [] + ansible.builtin.set_fact: + _nomad_file_sources: "{{ _nomad_file_sources + [item.item] }}" + when: item.stat.isreg + loop: "{{ nomad_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Nomad | Set list for directory sources" + vars: + _nomad_dir_sources: [] + ansible.builtin.set_fact: + _nomad_dir_sources: "{{ _nomad_dir_sources + [item.item] }}" + when: item.stat.isdir + loop: "{{ nomad_extra_file_stat.results }}" + loop_control: + loop_var: item + delegate_to: localhost + + - name: "Nomad | Template extra file sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ item.dest | regex_replace('\\.j2$', '') }}" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0700" + loop: "{{ _nomad_file_sources }}" + when: _nomad_file_sources is defined + + - name: "Nomad | Template extra directory sources" + ansible.builtin.include_tasks: recursive_copy_extra_dirs.yml + loop: "{{ _nomad_dir_sources }}" + loop_control: + loop_var: dir_source_item + when: _nomad_dir_sources is defined diff --git a/roles/nomad/tasks/install.yml b/roles/nomad/tasks/install.yml new file mode 100644 index 0000000..200bcb4 --- /dev/null +++ b/roles/nomad/tasks/install.yml @@ -0,0 +1,141 @@ +--- +# task/install file for nomad +- name: "Nomad | Get latest release of nomad" + when: nomad_version == 'latest' + block: + - name: "Nomad | Get latest nomad release from github api" + ansible.builtin.uri: + url: "{{ nomad_github_api }}/hashicorp/nomad/releases/latest" + return_content: true + register: _nomad_latest_release + + - name: "Nomad | Set wanted nomad version to latest tag" + ansible.builtin.set_fact: + _nomad_wanted_version: "{{ _nomad_latest_release.json['tag_name']|regex_replace('v', '') }}" + +- name: "Nomad | Set wanted nomad version to {{ nomad_version }}" + ansible.builtin.set_fact: + _nomad_wanted_version: "{{ nomad_version|regex_replace('v', '') }}" + when: nomad_version != 'latest' + +- name: "Nomad | Get current nomad version" + block: + - name: "Nomad | Stat nomad version file" + ansible.builtin.stat: + path: "{{ nomad_config_dir }}/.version" + changed_when: false + check_mode: false + register: _nomad_version_file + + - name: "Nomad | Get current nomad version" + ansible.builtin.slurp: + src: "{{ _nomad_version_file.stat.path }}" + when: + - _nomad_version_file.stat.exists + - _nomad_version_file.stat.isreg + register: _nomad_current_version + +- name: "Nomad | Download and install nomad binary" + when: _nomad_current_version is not defined + or _nomad_wanted_version != (_nomad_current_version.content|default('')|b64decode) + block: + - name: "Nomad | Set nomad package name to download" + ansible.builtin.set_fact: + _nomad_package_name: >- + nomad_{{ _nomad_wanted_version }}_linux_{{ nomad_deb_architecture_map[ansible_architecture] }}.zip + _nomad_shasum_file_name: >- + nomad_{{ _nomad_wanted_version }}_SHA256SUMS + + - name: "Nomad | Download checksum file for nomad archive" + ansible.builtin.get_url: + url: "{{ nomad_repository_url }}/{{ _nomad_wanted_version }}/{{ _nomad_shasum_file_name }}" + dest: "/tmp/{{ _nomad_shasum_file_name }}" + mode: "0644" + register: _nomad_checksum_file + until: _nomad_checksum_file is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Nomad | Extract correct checksum from checksum file" + ansible.builtin.command: + cmd: 'grep "{{ _nomad_package_name }}" /tmp/{{ _nomad_shasum_file_name }}' + changed_when: false + register: _nomad_expected_checksum_line + + - name: "Nomad | Parse the expected checksum" + ansible.builtin.set_fact: + _nomad_expected_checksum: "{{ _nomad_expected_checksum_line.stdout.split()[0] }}" + + - name: "Nomad | Download nomad binary archive" + ansible.builtin.get_url: + url: "{{ nomad_repository_url }}/{{ _nomad_wanted_version }}/{{ _nomad_package_name }}" + dest: "/tmp/{{ _nomad_package_name }}" + mode: "0644" + checksum: "sha256:{{ _nomad_expected_checksum }}" + register: _nomad_binary_archive + until: _nomad_binary_archive is succeeded + retries: 5 + delay: 2 + check_mode: false + + - name: "Nomad | Create temporary directory for archive decompression" + ansible.builtin.file: + path: /tmp/nomad + state: directory + mode: "0755" + + - name: "Nomad | Unpack nomad archive" + ansible.builtin.unarchive: + src: "/tmp/{{ _nomad_package_name }}" + dest: "/tmp/nomad" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0755" + remote_src: true + + - name: "Nomad | Copy nomad binary to {{ nomad_binary_path }}" + ansible.builtin.copy: + src: /tmp/nomad/nomad + dest: "{{ nomad_binary_path }}" + owner: root + group: root + mode: "0755" + remote_src: true + force: true + + - name: "Nomad | Update nomad version file" + ansible.builtin.copy: + content: "{{ _nomad_wanted_version }}" + dest: "{{ nomad_config_dir }}/.version" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0600" + + - name: "Nomad | Set restart-check variable" + ansible.builtin.set_fact: + _nomad_service_need_restart: true + + - name: "Nomad | Cleanup temporary directory" + ansible.builtin.file: + path: "{{ item }}" + state: absent + loop: + - /tmp/nomad + - /tmp/{{ _nomad_package_name }} + - /tmp/{{ _nomad_shasum_file_name }} + +- name: "Nomad | Copy systemd service file for nomad" + ansible.builtin.template: + src: "nomad.service.j2" + dest: "/etc/systemd/system/nomad.service" + owner: root + group: root + mode: "0644" + register: _nomad_unit_file + +- name: "Nomad | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _nomad_service_need_reload: true + _nomad_service_need_restart: true + when: _nomad_unit_file.changed # noqa: no-handler diff --git a/roles/nomad/tasks/main.yml b/roles/nomad/tasks/main.yml new file mode 100644 index 0000000..4575a15 --- /dev/null +++ b/roles/nomad/tasks/main.yml @@ -0,0 +1,43 @@ +--- +# task/main file for hashicorp_nomad +- name: "Nomad | Set reload-check & restart-check variable" + ansible.builtin.set_fact: + _nomad_service_need_reload: false + _nomad_service_need_restart: false + +- name: "Nomad | Import merge_variables.yml" + ansible.builtin.include_tasks: merge_variables.yml + +- name: "Nomad | Import prerequisites.yml" + ansible.builtin.include_tasks: prerequisites.yml + +- name: "Nomad | Import install.yml" + ansible.builtin.include_tasks: install.yml + +- name: "Nomad | Import configure.yml" + ansible.builtin.include_tasks: configure.yml + +- name: "Nomad | Populate service facts" + ansible.builtin.service_facts: + +- name: "Nomad | Set restart-check variable" + ansible.builtin.set_fact: + _nomad_service_need_restart: true + when: ansible_facts.services[nomad_service_name~'.service'].state != 'running' + +- name: "Nomad | Enable service: {{ nomad_service_name }}" + ansible.builtin.service: + name: "{{ nomad_service_name }}" + enabled: true + +- name: "Nomad | Reload systemd daemon" + ansible.builtin.systemd: + daemon_reload: true + when: _nomad_service_need_reload + +- name: "Nomad | Start service: {{ nomad_service_name }}" + ansible.builtin.service: + name: "{{ nomad_service_name }}" + state: restarted + throttle: 1 + when: _nomad_service_need_restart diff --git a/playbooks/tasks/nomad/nomad_vars.yml b/roles/nomad/tasks/merge_variables.yml similarity index 70% rename from playbooks/tasks/nomad/nomad_vars.yml rename to roles/nomad/tasks/merge_variables.yml index 97c6af2..f4712ce 100644 --- a/playbooks/tasks/nomad/nomad_vars.yml +++ b/roles/nomad/tasks/merge_variables.yml @@ -1,30 +1,25 @@ --- -# hashistack configuration merging for nomad +# task/merge_variables file for hashicorp_nomad - name: "Nomad | Merge stringified configuration" vars: - _config_to_merge: "{{ hashicorp_nomad_configuration_string }}" + _config_to_merge: "{{ nomad_configuration_string }}" ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | + nomad_configuration: "{{ + nomad_configuration | combine(_config_to_merge|from_yaml, recursive=true) }}" - when: - - hashicorp_nomad_configuration_string is defined - - "'nomad_servers' in group_names" - name: "Nomad | Merge addresses configuration" vars: _config_to_merge: "{{ nomad_address_configuration }}" ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | + nomad_configuration: "{{ + nomad_configuration | combine(_config_to_merge, recursive=true) }}" - when: nomad_address_configuration is defined - name: "Nomad | Merge consul integration configuration" when: - - enable_consul | bool - nomad_enable_consul_integration | bool block: - name: "Nomad | Merge consul tls configuration" @@ -84,21 +79,32 @@ _config_to_merge: consul: "{{ nomad_consul_integration_configuration }}" ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | + nomad_configuration: "{{ + nomad_configuration | combine(_config_to_merge, recursive=true) }}" - name: "Nomad | Merge TLS configuration" - vars: - _config_to_merge: - tls: "{{ nomad_tls_configuration }}" - ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | - combine(_config_to_merge, recursive=true) - }}" - when: nomad_enable_tls + when: nomad_enable_tls | bool + block: + - name: "Nomad | Merge TLS configuration" + vars: + _config_to_merge: + tls: "{{ nomad_tls_configuration }}" + ansible.builtin.set_fact: + nomad_configuration: "{{ + nomad_configuration | + combine(_config_to_merge, recursive=true) + }}" + + - name: "Nomad | Add certificates directory to extra_files_dir" + ansible.builtin.set_fact: + nomad_extra_files_list: "{{ + nomad_extra_files_list + + nomad_certificates_extra_files_dir + | unique + | sort + }}" - name: "Nomad | Merge plugin configuration" vars: @@ -108,22 +114,37 @@ combine(nomad_driver_extra_configuration, recursive=true) }}" ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | + nomad_configuration: "{{ + nomad_configuration | combine(_config_to_merge, recursive=true) }}" - when: "'nomad_clients' in group_names" + when: nomad_enable_client | bool - name: "Nomad | Merge extra configuration settings" vars: _config_to_merge: "{{ nomad_extra_configuration }}" ansible.builtin.set_fact: - hashicorp_nomad_configuration: "{{ - hashicorp_nomad_configuration | + nomad_configuration: "{{ + nomad_configuration | combine(_config_to_merge, recursive=true) }}" - when: nomad_extra_configuration is defined -- name: "Print nomad configuration" - ansible.builtin.debug: - msg: "{{ hashicorp_nomad_configuration }}" +- name: "Nomad | Merge log to file configuration" + vars: + _config_to_merge: "{{ nomad_log_to_file_configuration }}" + ansible.builtin.set_fact: + nomad_configuration: "{{ + nomad_configuration | + combine(_config_to_merge, recursive=true) + }}" + when: nomad_enable_log_to_file + +- name: "Nomad | Merge telemetry configuration" + vars: + _config_to_merge: + telemetry: "{{ nomad_telemetry_configuration }}" + ansible.builtin.set_fact: + nomad_configuration: "{{ + nomad_configuration | + combine(_config_to_merge, recursive=true) + }}" diff --git a/roles/nomad/tasks/prerequisites.yml b/roles/nomad/tasks/prerequisites.yml new file mode 100644 index 0000000..3928cf5 --- /dev/null +++ b/roles/nomad/tasks/prerequisites.yml @@ -0,0 +1,46 @@ +--- +# task/prerequisites file for hashicorp_nomad +- name: "Nomad | Create group {{ nomad_group }}" + ansible.builtin.group: + name: "{{ nomad_user }}" + state: present + +- name: "Nomad | Create user {{ nomad_user }}" + ansible.builtin.user: + name: "{{ nomad_user }}" + group: "{{ nomad_group }}" + shell: /bin/false + state: present + +- name: "Nomad | Create directory {{ nomad_config_dir }}" + ansible.builtin.file: + path: "{{ nomad_config_dir }}" + state: directory + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0755" + +- name: "Nomad | Create directory {{ nomad_data_dir }}" + ansible.builtin.file: + path: "{{ nomad_data_dir }}" + state: directory + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0755" + +- name: "Nomad | Create directory {{ nomad_certs_dir }}" + ansible.builtin.file: + path: "{{ nomad_certs_dir }}" + state: directory + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0755" + +- name: "Nomad | Create directory {{ nomad_logs_dir }}" + ansible.builtin.file: + path: "{{ nomad_logs_dir }}" + state: directory + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0755" + when: nomad_enable_log_to_file diff --git a/roles/nomad/tasks/recursive_copy_extra_dirs.yml b/roles/nomad/tasks/recursive_copy_extra_dirs.yml new file mode 100644 index 0000000..2424d9e --- /dev/null +++ b/roles/nomad/tasks/recursive_copy_extra_dirs.yml @@ -0,0 +1,27 @@ +--- +# task/recursive_copy_extra_dirs file for hashicorp_nomad +- name: "Nomad | Ensure destination directory exists" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}" + recurse: true + state: directory + mode: "0775" + +- name: "Nomad | Create extra directory sources" + ansible.builtin.file: + path: "{{ dir_source_item.dest }}/{{ item.path }}" + recurse: true + state: directory + mode: "0775" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'directory' + +- name: "Nomad | Template extra directory sources" + ansible.builtin.template: + src: "{{ item.src }}" + dest: "{{ dir_source_item.dest }}/{{ item.path | regex_replace('\\.j2$', '') }}" + owner: "{{ nomad_user }}" + group: "{{ nomad_group }}" + mode: "0700" + with_community.general.filetree: "{{ dir_source_item.src }}/" + when: item.state == 'file' diff --git a/roles/nomad/templates/nomad.env.j2 b/roles/nomad/templates/nomad.env.j2 new file mode 100644 index 0000000..67798c9 --- /dev/null +++ b/roles/nomad/templates/nomad.env.j2 @@ -0,0 +1,4 @@ +# {{ ansible_managed }} +{% for item in nomad_env_variables %} +{{ item }}="{{ nomad_env_variables[item] }}" +{% endfor %} diff --git a/roles/nomad/templates/nomad.json.j2 b/roles/nomad/templates/nomad.json.j2 new file mode 100644 index 0000000..c0bf3da --- /dev/null +++ b/roles/nomad/templates/nomad.json.j2 @@ -0,0 +1 @@ +{{ nomad_configuration|to_nice_json }} diff --git a/roles/nomad/templates/nomad.service.j2 b/roles/nomad/templates/nomad.service.j2 new file mode 100644 index 0000000..e5a1570 --- /dev/null +++ b/roles/nomad/templates/nomad.service.j2 @@ -0,0 +1,33 @@ +[Unit] +Description=Nomad +Documentation=https://developer.hashicorp.com/nomad/docs +Wants=network-online.target +After=network-online.target +ConditionFileNotEmpty={{ nomad_config_dir }}/nomad.json +{% if nomad_configuration.consul.address is defined %} +Wants=consul.service +After=consul.service +{% endif %} + +[Service] +EnvironmentFile=-{{ nomad_config_dir }}/nomad.env +{% if not (nomad_configuration.client.enabled is defined and nomad_configuration.client.enabled) %} +User={{ nomad_user }} +Group={{ nomad_group }} +{% else %} +User=root +Group=root +{% endif %} +ExecStart={{ nomad_binary_path }} agent -config {{ nomad_config_dir }}/nomad.json +ExecReload=/bin/kill -HUP $MAINPID +KillMode=process +KillSignal=SIGINT +Restart=on-failure +LimitNOFILE=65536 +LimitNPROC=infinity +RestartSec=2 +TasksMax=infinity +OOMScoreAdjust=-1000 + +[Install] +WantedBy=multi-user.target diff --git a/roles/nomad/vars/main.yml b/roles/nomad/vars/main.yml new file mode 100644 index 0000000..b30f7d5 --- /dev/null +++ b/roles/nomad/vars/main.yml @@ -0,0 +1,32 @@ +--- +# vars file for hashicorp_nomad +nomad_user: nomad +nomad_group: nomad +nomad_binary_path: /usr/local/bin/nomad +nomad_deb_architecture_map: + x86_64: "amd64" + aarch64: "arm64" + armv7l: "arm" + armv6l: "arm" +nomad_architecture: "{{ nomad_deb_architecture_map[ansible_architecture] | default(ansible_architecture) }}" +nomad_service_name: "nomad" +nomad_github_api: https://api.github.com/repos +nomad_github_project: hashicorp/nomad +nomad_github_url: https://github.com +nomad_repository_url: https://releases.hashicorp.com/nomad + +nomad_configuration: + datacenter: "{{ nomad_datacenter }}" + region: "{{ nomad_region }}" + data_dir: "{{ nomad_data_dir }}" + leave_on_interrupt: "{{ nomad_leave_on_interrupt }}" + leave_on_terminate: "{{ nomad_leave_on_terminate }}" + acl: "{{ nomad_acl_configuration }}" + server: "{{ nomad_server_configuration }}" + client: "{{ nomad_client_configuration }}" + ui: "{{ nomad_ui_configuration }}" + log_level: "{{ nomad_log_level }}" + +nomad_configuration_string: | + server: + bootstrap_expect: {{ nomad_server_bootstrap_expect }} diff --git a/roles/requirements.yml b/roles/requirements.yml index 15c4e7e..84bdfb1 100644 --- a/roles/requirements.yml +++ b/roles/requirements.yml @@ -24,10 +24,10 @@ roles: version: main - name: ednz_cloud.hashicorp_nomad src: https://github.com/ednz-cloud/hashicorp_nomad.git - version: v0.4.0 + version: v0.4.1 - name: ednz_cloud.hashicorp_consul src: https://github.com/ednz-cloud/hashicorp_consul.git - version: v0.2.0 + version: v0.2.1 - name: ednz_cloud.hashicorp_vault src: https://github.com/ednz-cloud/hashicorp_vault.git - version: v0.2.0 + version: v0.2.1 diff --git a/roles/vault/defaults/main.yml b/roles/vault/defaults/main.yml new file mode 100644 index 0000000..5b9f5d9 --- /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..61af80c --- /dev/null +++ b/roles/vault/tasks/merge_variables.yml @@ -0,0 +1,77 @@ +--- +# 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 | + zip(vault_tls_listener_configuration) | + map('combine', list_merge='append', recursive=true) | + list + }} + 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 + + vault_extra_listener_configuration + }} + + - 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 }}"